renderer = $renderer; } public function register(): void { (new LoopController($this->renderer))->register(); add_filter('zadex/editor/config', [$this, 'editor_config'], 10, 2); add_filter('kses_allowed_protocols', [self::class, 'allow_off_canvas_links']); // wp_allowed_protocols() caches its list and stops filtering after `wp_loaded`: prime it before. add_action('init', static function (): void { if (function_exists('wp_allowed_protocols')) { wp_allowed_protocols(); } }, 999); } /** * Off-canvas trigger links `#zx-off-canvas:` (T120): esc_url() / esc_url_raw() read `#zx-off-canvas` as a URL * scheme and would drop the link. Allowing that pseudo-scheme keeps them; it is a same-page fragment and can * never run script. (wp_allowed_protocols() applies the filter until `wp_loaded`, so it is added on boot and primed on `init`.) */ public static function allow_off_canvas_links($protocols): array { $protocols = is_array($protocols) ? $protocols : []; $protocols[] = '#zx-off-canvas'; return array_values(array_unique($protocols)); } public function editor_config(array $config, int $post_id = 0): array { $data = is_array($config['data'] ?? null) ? $config['data'] : []; $data['loopTemplates'] = self::template_options(); // One-item option list (the editor's `data` holds option lists): the "Create new" link of the template selects. $data['loopTemplateNew'] = [['value' => class_exists('\Zadex\Admin\ThemeBuilderPage') ? \Zadex\Admin\ThemeBuilderPage::url('loop-item') : '', 'label' => __('Create new', 'zadex')]]; $data['loopPostTypes'] = self::post_type_options(); $data['loopTaxonomies'] = self::taxonomy_options(); $config['data'] = $data; return $config; } /** @return array */ public static function template_options(): array { $out = [['value' => '', 'label' => __('— Select —', 'zadex')]]; if (!function_exists('get_posts')) { return $out; } $posts = get_posts([ 'post_type' => TemplatePost::POST_TYPE, 'post_status' => ['publish', 'draft', 'private'], 'numberposts' => 200, 'orderby' => 'title', 'order' => 'ASC', // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- loop item templates are identified by their type meta; bounded query. 'meta_key' => TemplatePost::META_TYPE, 'meta_value' => 'loop-item', // phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value ]); foreach (is_array($posts) ? $posts : [] as $post) { $title = (string) ($post->post_title ?? ''); $label = ($title !== '' ? $title : '#' . $post->ID) . (($post->post_status ?? '') !== 'publish' ? ' (' . __('draft', 'zadex') . ')' : ''); $out[] = ['value' => (string) $post->ID, 'label' => $label]; } return $out; } /** @return array */ public static function post_type_options(): array { $out = []; if (function_exists('get_post_types')) { foreach ((array) get_post_types(['public' => true], 'objects') as $name => $object) { if (in_array($name, ['attachment', TemplatePost::POST_TYPE], true)) { continue; } $label = is_object($object) && isset($object->labels->name) ? (string) $object->labels->name : (string) $name; $out[] = ['value' => (string) $name, 'label' => $label]; } } if (!$out) { $out = [['value' => 'post', 'label' => __('Posts', 'zadex')], ['value' => 'page', 'label' => __('Pages', 'zadex')]]; } return (array) apply_filters('zadex/loop/post_types', $out); } /** @return array */ public static function taxonomy_options(): array { $out = []; if (function_exists('get_taxonomies')) { foreach ((array) get_taxonomies(['public' => true], 'objects') as $name => $object) { if ($name === 'post_format') { continue; } $label = is_object($object) && isset($object->labels->name) ? (string) $object->labels->name : (string) $name; $out[] = ['value' => (string) $name, 'label' => $label . ' (' . $name . ')']; } } if (!$out) { $out = [['value' => 'category', 'label' => __('Categories', 'zadex')], ['value' => 'post_tag', 'label' => __('Tags', 'zadex')]]; } return (array) apply_filters('zadex/loop/taxonomies', $out); } }