widgets = $widgets; } public static function register_post_type(): void { register_post_type(self::CPT, [ 'label' => __('Zadex templates', 'zadex'), 'public' => false, 'publicly_queryable' => false, 'exclude_from_search' => true, 'show_ui' => false, 'show_in_menu' => false, 'show_in_nav_menus' => false, 'show_in_rest' => false, 'rewrite' => false, 'query_var' => false, 'has_archive' => false, 'hierarchical' => false, 'supports' => ['title', 'thumbnail', 'author'], 'capability_type' => 'post', 'map_meta_cap' => true, ]); } public static function sanitize_type($value): string { return is_string($value) && in_array($value, self::TYPES, true) ? $value : 'section'; } /** @return string[] lower-case, trimmed, unique, at most MAX_TAGS. */ public static function sanitize_tags($value): array { if (is_string($value)) { $value = explode(',', $value); } if (!is_array($value)) { return []; } $out = []; foreach ($value as $tag) { if (!is_scalar($tag)) { continue; } $tag = strtolower(trim(sanitize_text_field((string) $tag))); if ($tag !== '' && mb_strlen($tag) <= 40 && !in_array($tag, $out, true)) { $out[] = $tag; } } return array_slice($out, 0, self::MAX_TAGS); } /** * Creates a template. Elements are sanitized through the widget manifests. * * @return int|WP_Error new post id */ public function create(string $title, string $type, array $elements, string $css = '', ?array $page_settings = null, array $tags = []) { $title = trim(sanitize_text_field($title)); if ($title === '') { return new WP_Error('zadex_bad_request', __('A template needs a name.', 'zadex'), ['status' => 400]); } $sanitizer = new Sanitizer($this->widgets); $clean = $sanitizer->sanitize_elements($elements); if (!$clean) { return new WP_Error('zadex_bad_request', __('The template has no valid elements.', 'zadex'), ['status' => 400]); } $id = wp_insert_post(wp_slash([ 'post_type' => self::CPT, 'post_title' => $title, 'post_status' => 'publish', 'post_content' => '', ]), true); if (is_wp_error($id)) { return $id; } $id = (int) $id; $type = self::sanitize_type($type); update_post_meta($id, self::META_TYPE, $type); update_post_meta($id, self::META_TAGS, self::sanitize_tags($tags)); update_post_meta($id, Document::META_DATA, wp_slash(wp_json_encode(['version' => Document::VERSION, 'elements' => $clean], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES))); update_post_meta($id, Document::META_CSS, wp_slash(Document::sanitize_css($css))); if ($type === 'page' && $page_settings !== null) { update_post_meta($id, Document::META_PAGE, wp_slash(wp_json_encode($sanitizer->sanitize_settings_generic($page_settings), JSON_UNESCAPED_UNICODE))); } do_action('zadex/library/saved', $id, $type); return $id; } /** True when $id is a library item. */ public function exists(int $id): bool { $post = $id > 0 ? get_post($id) : null; return $post !== null && (string) ($post->post_type ?? '') === self::CPT && (string) ($post->post_status ?? '') !== 'trash'; } /** @return array> summaries, newest first */ public function all(string $type = ''): array { $args = [ 'post_type' => self::CPT, 'post_status' => 'publish', 'posts_per_page' => 200, 'orderby' => 'date', 'order' => 'DESC', 'no_found_rows' => true, ]; if (in_array($type, self::TYPES, true)) { // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- library items are filtered by their type meta. $args['meta_query'] = [['key' => self::META_TYPE, 'value' => $type]]; } $out = []; foreach (get_posts($args) as $post) { $item = $this->summary($post); if ($type === '' || $item['type'] === $type) { $out[] = $item; } } return $out; } /** @return array */ public function summary($post): array { $id = (int) $post->ID; $tags = get_post_meta($id, self::META_TAGS, true); $thumb = function_exists('get_the_post_thumbnail_url') ? get_the_post_thumbnail_url($id, 'medium') : ''; $author = function_exists('get_userdata') ? get_userdata((int) ($post->post_author ?? 0)) : null; return [ 'id' => $id, 'title' => (string) $post->post_title, 'type' => self::sanitize_type(get_post_meta($id, self::META_TYPE, true)), 'tags' => self::sanitize_tags(is_array($tags) ? $tags : []), 'thumbnail' => is_string($thumb) ? $thumb : '', 'date' => (string) ($post->post_date_gmt ?? $post->post_modified_gmt ?? ''), 'author' => $author ? (string) $author->display_name : '', ]; } /** Full template: summary + elements (+ page settings for page templates). */ public function get(int $id): ?array { if (!$this->exists($id)) { return null; } $post = get_post($id); $data = Document::get_data($id); $item = $this->summary($post); $item['elements'] = $data['elements']; $item['css'] = Document::get_css($id); $page = Document::get_page_settings($id); $item['pageSettings'] = $page ? $page : (object) []; return $item; } public function delete(int $id): bool { if (!$this->exists($id)) { return false; } return (bool) wp_delete_post($id, true); } }