From 1f17a38fa6d155938603bd0e7bca9a984ad5271b Mon Sep 17 00:00:00 2001 From: Florent Hernandez Date: Tue, 14 Jun 2022 17:03:37 +0200 Subject: [PATCH] PLANET-6652: Extract search filters (#1691) * PLANET-6552: Extract search filters - Extract search filters to their own classes - Use consistent taxonomy - Remove tags in search template - Add p4_action type to search filters and template - Remove act pages filter and various exceptions - Reduce label definition code --- src/ElasticSearch.php | 50 ++---- src/Search.php | 255 +++++++++------------------- src/Search/Filters/ActionTypes.php | 44 +++++ src/Search/Filters/Categories.php | 39 +++++ src/Search/Filters/ContentTypes.php | 113 ++++++++++++ src/Search/Filters/PostTypes.php | 53 ++++++ src/Search/Filters/Tags.php | 48 ++++++ templates/search.twig | 80 +++------ 8 files changed, 411 insertions(+), 271 deletions(-) create mode 100644 src/Search/Filters/ActionTypes.php create mode 100644 src/Search/Filters/Categories.php create mode 100644 src/Search/Filters/ContentTypes.php create mode 100644 src/Search/Filters/PostTypes.php create mode 100644 src/Search/Filters/Tags.php diff --git a/src/ElasticSearch.php b/src/ElasticSearch.php index e173039366..d239c663e8 100644 --- a/src/ElasticSearch.php +++ b/src/ElasticSearch.php @@ -27,30 +27,13 @@ public function set_filters_args( &$args ) { case 'cat': case 'tag': case 'ptype': + case 'atype': break; case 'ctype': switch ( $filter['id'] ) { case 0: case 1: - break; case 2: - // Workaround for making 'post_parent__not_in' to work with ES. - add_filter( - 'ep_formatted_args', - function ( $formatted_args ) use ( $args ) { - // Make sure it is not an Action page. - if ( ! empty( $args['post_parent__not_in'] ) ) { - $formatted_args['post_filter']['bool']['must_not'][] = [ - 'terms' => [ - 'post_parent' => array_values( (array) $args['post_parent__not_in'] ), - ], - ]; - } - return $formatted_args; - }, - 10, - 1 - ); break; case 3: case 4: @@ -72,6 +55,7 @@ function ( $formatted_args ) use ( $args ) { ); break; case 5: + case 6: break; default: throw new UnexpectedValueException( 'Unexpected content type!' ); @@ -174,21 +158,6 @@ public function set_results_weight( $formatted_args ) { ], ); - $act_page = planet4_get_option( 'act_page', null ); - if ( $act_page ) { - array_push( - $formatted_args['query']['function_score']['functions'], - [ - 'filter' => [ - 'term' => [ - 'post_parent' => esc_sql( $act_page ), - ], - ], - 'weight' => self::DEFAULT_ACTION_WEIGHT, - ], - ); - } - // Specify how the computed scores are combined. $formatted_args['query']['function_score']['score_mode'] = 'sum'; @@ -212,31 +181,36 @@ public function add_aggregations( $formatted_args ) { 'with_post_filter' => [ 'filter' => $formatted_args['post_filter'], 'aggs' => [ - 'post_type' => [ + 'post_type' => [ 'terms' => [ 'field' => 'post_type.raw', ], ], - 'post_parent' => [ + 'post_parent' => [ 'terms' => [ 'field' => 'post_parent', ], ], - 'categories' => [ + 'categories' => [ 'terms' => [ 'field' => 'terms.category.term_id', ], ], - 'tags' => [ + 'tags' => [ 'terms' => [ 'field' => 'terms.post_tag.term_id', ], ], - 'p4-page-type' => [ + 'p4-page-type' => [ 'terms' => [ 'field' => 'terms.p4-page-type.term_id', ], ], + ActionPage::TAXONOMY => [ + 'terms' => [ + 'field' => 'terms.' . ActionPage::TAXONOMY . '.term_id', + ], + ], ], ], ]; diff --git a/src/Search.php b/src/Search.php index 34eea89282..1879a477b0 100644 --- a/src/Search.php +++ b/src/Search.php @@ -9,6 +9,7 @@ use UnexpectedValueException; use WP_Query; use WPML_Post_Element; +use P4\MasterTheme\Features\ActionPostType; /** @@ -532,6 +533,10 @@ private static function get_post_types() { $types[] = PostArchive::POST_TYPE; } + if ( ActionPostType::is_active() ) { + $types[] = ActionPage::POST_TYPE; + } + return $types; } @@ -613,24 +618,25 @@ public function set_filters_args( &$args ) { 'terms' => $filter['id'], ]; break; + case 'atype': + // This taxonomy is used only for Posts. + $args['post_type'] = ActionPage::POST_TYPE; + $args['tax_query'][] = [ + 'taxonomy' => ActionPage::TAXONOMY, + 'field' => 'term_id', + 'terms' => $filter['id'], + ]; + break; case 'ctype': switch ( $filter['id'] ) { - case 0: - $args['post_type'] = 'page'; - $args['post_status'] = 'publish'; - $act_page = planet4_get_option( 'act_page', 0 ); - $args['post_parent'] = esc_sql( $act_page ); - break; case 1: $args['post_type'] = 'attachment'; $args['post_status'] = 'inherit'; $args['post_mime_type'] = self::DOCUMENT_TYPES; break; case 2: - $args['post_type'] = 'page'; - $args['post_status'] = 'publish'; - $act_page = planet4_get_option( 'act_page', 0 ); - $args['post_parent__not_in'][] = esc_sql( $act_page ); + $args['post_type'] = 'page'; + $args['post_status'] = 'publish'; break; case 3: $args['post_type'] = 'post'; @@ -643,6 +649,10 @@ public function set_filters_args( &$args ) { case 5: $args['post_type'] = 'archive'; break; + case 6: + $args['post_type'] = ActionPage::POST_TYPE; + $args['post_status'] = 'publish'; + break; default: throw new UnexpectedValueException( 'Unexpected content type!' ); } @@ -727,85 +737,27 @@ protected function set_general_context( &$context ) { * @throws UnexpectedValueException When filter type is not recognized. */ protected function set_filters_context( &$context ) { - // Retrieve P4 settings in order to check that we add only categories that are children of the Issues category. - $options = get_option( 'planet4_options' ); - - // Category <-> Issue. - // Consider Issues that have multiple Categories. - $categories = get_categories( [ 'child_of' => $options['issues_parent_category'] ] ); - if ( $categories ) { - foreach ( $categories as $category ) { - $context['categories'][ $category->term_id ] = [ - 'term_id' => $category->term_id, - 'name' => $category->name, - 'results' => 0, - ]; - } - } - - // Tag <-> Campaign. - $tags = get_terms( - [ - 'taxonomy' => 'post_tag', - 'hide_empty' => false, - ] + // Categories. + $context['categories'] = Search\Filters\Categories::get_filters(); + uasort( $context['categories'], fn ( $a, $b ) => strcmp( $a['name'], $b['name'] ) ); + + // Post Types. + $context['post_types'] = Search\Filters\PostTypes::get_filters(); + + // Action Types. + $context['action_types'] = ActionPostType::is_active() + ? Search\Filters\ActionTypes::get_filters() + : []; + + // Content Types. + $context['content_types'] = Search\Filters\ContentTypes::get_filters( + self::should_include_archive(), + ActionPostType::is_active() ); - if ( $tags ) { - foreach ( (array) $tags as $tag ) { - // Tag filters. - $context['tags'][ $tag->term_id ] = [ - 'term_id' => $tag->term_id, - 'name' => $tag->name, - 'results' => 0, - ]; - } - } - // Page Type <-> Category. - $page_types = get_terms( - [ - 'taxonomy' => 'p4-page-type', - 'hide_empty' => false, - ] - ); - if ( $page_types ) { - foreach ( (array) $page_types as $page_type ) { - // p4-page-type filters. - $context['page_types'][ $page_type->term_id ] = [ - 'term_id' => $page_type->term_id, - 'name' => $page_type->name, - 'results' => 0, - ]; - } - } - - // Post Type (+Action) <-> Content Type. - $context['content_types']['0'] = [ - 'name' => __( 'Action', 'planet4-master-theme' ), - 'results' => 0, - ]; - $context['content_types']['4'] = [ - 'name' => __( 'Campaign', 'planet4-master-theme' ), - 'results' => 0, - ]; - $context['content_types']['1'] = [ - 'name' => __( 'Document', 'planet4-master-theme' ), - 'results' => 0, - ]; - $context['content_types']['2'] = [ - 'name' => __( 'Page', 'planet4-master-theme' ), - 'results' => 0, - ]; - $context['content_types']['3'] = [ - 'name' => __( 'Post', 'planet4-master-theme' ), - 'results' => 0, - ]; - if ( self::should_include_archive() ) { - $context['content_types']['5'] = [ - 'name' => __( 'Archive', 'planet4-master-theme' ), - 'results' => 0, - ]; - } + // Tag <-> Campaign. + $context['tags'] = Search\Filters\Tags::get_filters(); + uasort( $context['tags'], fn ( $a, $b ) => strcmp( $a['name'], $b['name'] ) ); // Keep track of which filters are already checked. if ( $this->filters ) { @@ -819,45 +771,25 @@ protected function set_filters_context( &$context ) { $context['tags'][ $filter['id'] ]['checked'] = 'checked'; break; case 'ptype': - $context['page_types'][ $filter['id'] ]['checked'] = 'checked'; + $context['post_types'][ $filter['id'] ]['checked'] = 'checked'; break; case 'ctype': $context['content_types'][ $filter['id'] ]['checked'] = 'checked'; break; + case 'atype': + $context['action_types'][ $filter['id'] ]['checked'] = 'checked'; + break; default: throw new UnexpectedValueException( 'Unexpected filter!' ); } } } } - - // Sort associative array with filters alphabetically. - if ( $context['categories'] ?? false ) { - uasort( - $context['categories'], - function ( $a, $b ) { - return strcmp( $a['name'], $b['name'] ); - } - ); - } - if ( $context['tags'] ?? false ) { - uasort( - $context['tags'], - function ( $a, $b ) { - return strcmp( $a['name'], $b['name'] ); - } - ); - } } /** * Sets the context for the results of the Search. * - * Categories are Issues. - * Tags are Campaigns. - * Page types are Categories. - * Post_types are Content Types. - * * @param array $context Associative array with the data to be passed to the view. */ protected function set_results_context( &$context ) { @@ -873,103 +805,68 @@ protected function set_results_context( &$context ) { // Set default thumbnail. $context['dummy_thumbnail'] = get_template_directory_uri() . self::DUMMY_THUMBNAIL; - $act_page_count = null; - if ( ! empty( $this->aggregations ) ) { $aggs = $this->aggregations['with_post_filter']; - foreach ( $aggs['post_parent']['buckets'] as $parent_agg ) { - if ( ! empty( $options['act_page'] ) && $parent_agg['key'] === (int) $options['act_page'] ) { - $act_page_count = $parent_agg['doc_count']; - $context['content_types']['0']['results'] = $act_page_count; - } - } - foreach ( $aggs['post_type']['buckets'] as $post_type_agg ) { - if ( 'page' === $post_type_agg['key'] ) { - // We show act pages as a separate item, so subtract there count from the other pages. - // But counts can be off in ES so don't use lower than 0. - $context['content_types']['2']['results'] = max( - 0, - $post_type_agg['doc_count'] - $act_page_count - ); - } if ( 'attachment' === $post_type_agg['key'] ) { $context['content_types']['1']['results'] = $post_type_agg['doc_count']; } - if ( 'post' === $post_type_agg['key'] ) { + if ( Search\Filters\ContentTypes::PAGE === $post_type_agg['key'] ) { + $context['content_types']['2']['results'] = $post_type_agg['doc_count']; + } + if ( Search\Filters\ContentTypes::POST === $post_type_agg['key'] ) { $context['content_types']['3']['results'] = $post_type_agg['doc_count']; } - if ( 'campaign' === $post_type_agg['key'] ) { + if ( Search\Filters\ContentTypes::CAMPAIGN === $post_type_agg['key'] ) { $context['content_types']['4']['results'] = $post_type_agg['doc_count']; } - if ( 'archive' === $post_type_agg['key'] && self::should_include_archive() ) { + if ( Search\Filters\ContentTypes::ARCHIVE === $post_type_agg['key'] && self::should_include_archive() ) { $context['content_types']['5']['results'] = $post_type_agg['doc_count']; } + if ( Search\Filters\ContentTypes::ACTION === $post_type_agg['key'] ) { + $context['content_types']['6']['results'] = $post_type_agg['doc_count']; + } } foreach ( $aggs['p4-page-type']['buckets'] as $p4_post_type_agg ) { - $p4_post_type_id = (int) $p4_post_type_agg['key']; - - $p4_post_type = self::get_p4_post_type( $p4_post_type_id ); + if ( ! isset( $context['post_types'][ $p4_post_type_agg['key'] ] ) ) { + continue; + } + $context['post_types'][ $p4_post_type_agg['key'] ]['results'] = $p4_post_type_agg['doc_count']; + } - $context['page_types'][ $p4_post_type_id ] = [ - 'term_id' => $p4_post_type_id, - 'name' => $p4_post_type->name ?? null, - 'results' => $p4_post_type_agg['doc_count'], - ]; + foreach ( $aggs[ ActionPage::TAXONOMY ]['buckets'] as $p4_action_type_agg ) { + if ( ! isset( $context['action_types'][ $p4_action_type_agg['key'] ] ) ) { + continue; + } + $context['action_types'][ $p4_action_type_agg['key'] ]['results'] = $p4_action_type_agg['doc_count']; } foreach ( $aggs['categories']['buckets'] as $category_agg ) { - // TODO get the parent from ES so no fetch is needed here. - $category = get_category( $category_agg['key'] ); - - // Category <-> Issue. - // Consider Issues that have multiple Categories. - if ( $category->parent === (int) $options['issues_parent_category'] ) { - $context['categories'][ $category->term_id ]['results'] = $category_agg['doc_count']; + if ( ! isset( $context['categories'][ $category_agg['key'] ] ) ) { + continue; } + $context['categories'][ $category_agg['key'] ]['results'] = $category_agg['doc_count']; } foreach ( $aggs['tags']['buckets'] as $tag_agg ) { - // Tag filters. - $tag = get_tag( $tag_agg['key'] ); - - $context['tags'][ $tag->term_id ]['results'] = $tag_agg['doc_count']; + if ( ! isset( $context['tags'][ $tag_agg['key'] ] ) ) { + continue; + } + $context['tags'][ $tag_agg['key'] ]['results'] = $tag_agg['doc_count']; } } + $content_types = Search\Filters\ContentTypes::get_filters(); foreach ( (array) $posts as $post ) { - // Post Type (+Action) <-> Content Type. - switch ( $post->post_type ) { - case 'page': - if ( ! empty( $options['act_page'] ) && $post->post_parent === (int) $options['act_page'] ) { - $content_type_text = __( 'ACTION', 'planet4-master-theme' ); - $content_type = 'action'; - } else { - $content_type_text = __( 'PAGE', 'planet4-master-theme' ); - $content_type = 'page'; - } - break; - case 'campaign': - $content_type_text = __( 'CAMPAIGN', 'planet4-master-theme' ); - $content_type = 'campaign'; - break; - case 'attachment': - $content_type_text = __( 'DOCUMENT', 'planet4-master-theme' ); - $content_type = 'document'; - break; - case 'post': - $content_type_text = __( 'POST', 'planet4-master-theme' ); - $content_type = 'post'; - break; - case PostArchive::POST_TYPE: - $content_type_text = __( 'Archive', 'planet4-master-theme' ); - $content_type = 'archive'; - break; - default: - continue 2; // Ignore other post_types and continue with next $post. + $type = $content_types[ $post->post_type ] ?? null; + if ( ! $type ) { + continue; } + $content_type_text = $type ? $type['label'] : $post->post_type; + $content_type = 'attachment' === $post->post_type ? 'document' : $post->post_type; + if ( ! isset( $post->ID ) ) { $post->ID = $post->link; } diff --git a/src/Search/Filters/ActionTypes.php b/src/Search/Filters/ActionTypes.php new file mode 100644 index 0000000000..62960383c3 --- /dev/null +++ b/src/Search/Filters/ActionTypes.php @@ -0,0 +1,44 @@ + + */ + public static function get_all(): array { + return get_terms( + [ + 'taxonomy' => ActionPage::TAXONOMY, + 'hide_empty' => false, + ] + ); + } + + /** + * @return array{int, array{term_id: int, name: string, results: int}} + */ + public static function get_filters(): array { + $types = self::get_all(); + $filters = []; + + foreach ( $types as $type ) { + $filters[ $type->term_id ] = [ + 'term_id' => $type->term_id, + 'name' => $type->name, + 'results' => 0, + ]; + } + + return $filters; + } +} diff --git a/src/Search/Filters/Categories.php b/src/Search/Filters/Categories.php new file mode 100644 index 0000000000..de691c5720 --- /dev/null +++ b/src/Search/Filters/Categories.php @@ -0,0 +1,39 @@ + + */ + public static function get_all(): array { + return get_categories(); + } + + /** + * @return array{int, array{term_id: int, name: string, results: int}} + */ + public static function get_filters(): array { + $categories = self::get_all(); + $filters = []; + + foreach ( $categories as $category ) { + if ( 'uncategorised' === $category->slug ) { + continue; + } + + $filters[ $category->term_id ] = [ + 'term_id' => $category->term_id, + 'name' => $category->name, + 'results' => 0, + ]; + } + + return $filters; + } +} diff --git a/src/Search/Filters/ContentTypes.php b/src/Search/Filters/ContentTypes.php new file mode 100644 index 0000000000..9f36a5d6e7 --- /dev/null +++ b/src/Search/Filters/ContentTypes.php @@ -0,0 +1,113 @@ + + */ + public static function get_all(): array { + $config = self::get_config(); + $types = get_post_types( + [ + 'public' => true, + 'exclude_from_search' => false, + ], + false + ); + + return array_filter( + $types, + function ( $type ) use ( $config ) { + return isset( $config[ $type->name ] ); + } + ); + } + + /** + * @param bool $include_archive Include archive type. + * @param bool $include_action Include p4_action type. + * + * @return array{string, array{id: int, name: string, results: int}} + */ + public static function get_filters( + bool $include_archive = false, + bool $include_action = false + ): array { + $types = self::get_all(); + $config = self::get_config(); + + foreach ( $types as $name => $type ) { + if ( self::ARCHIVE === $name && ! $include_archive ) { + continue; + } + + if ( self::ACTION === $name && ! $include_action ) { + continue; + } + + $type_data = $config[ $type->name ] ?? null; + if ( ! $type_data ) { + continue; + } + + $filters[ $type_data['id'] ] = [ + 'id' => $type_data['id'], + 'name' => $type_data['label'], + 'results' => 0, + ]; + } + + return $filters; + } + + /** + * Get all content type config for search + * + * @return array{string, array{id: int, label: string}} + */ + public static function get_config(): array { + return [ + self::ATTACHMENT => [ + 'id' => 1, + 'label' => __( 'Document', 'planet4-master-theme' ), + ], + self::PAGE => [ + 'id' => 2, + 'label' => __( 'Page', 'planet4-master-theme' ), + ], + self::POST => [ + 'id' => 3, + 'label' => __( 'Post', 'planet4-master-theme' ), + ], + self::CAMPAIGN => [ + 'id' => 4, + 'label' => __( 'Campaign', 'planet4-master-theme' ), + ], + self::ARCHIVE => [ + 'id' => 5, + 'label' => __( 'Archive', 'planet4-master-theme' ), + ], + self::ACTION => [ + 'id' => 6, + 'label' => __( 'Action', 'planet4-master-theme' ), + ], + ]; + } +} diff --git a/src/Search/Filters/PostTypes.php b/src/Search/Filters/PostTypes.php new file mode 100644 index 0000000000..1f04dd299d --- /dev/null +++ b/src/Search/Filters/PostTypes.php @@ -0,0 +1,53 @@ + Posts Types` + */ +class PostTypes { + /** + * @return WP_Term[] + */ + public static function get_all(): array { + $types = get_terms( + [ + 'taxonomy' => 'p4-page-type', + 'hide_empty' => false, + ] + ); + + if ( is_wp_error( $types ) + || ! is_array( $types ) + || empty( $types ) + || ! ( $types[0] instanceof WP_Term ) + ) { + return []; + } + + return $types; + } + + /** + * @return array{int, array{term_id: int, name: string, results: int}} + */ + public static function get_filters(): array { + $post_types = self::get_all(); + $filters = []; + + foreach ( $post_types as $post_type ) { + $filters[ $post_type->term_id ] = [ + 'term_id' => $post_type->term_id, + 'name' => $post_type->name, + 'results' => 0, + ]; + } + + return $filters; + } +} diff --git a/src/Search/Filters/Tags.php b/src/Search/Filters/Tags.php new file mode 100644 index 0000000000..6762d4ae3f --- /dev/null +++ b/src/Search/Filters/Tags.php @@ -0,0 +1,48 @@ + 'post_tag', + 'hide_empty' => false, + ] + ); + + if ( is_wp_error( $tags ) ) { + return []; + } + + return $tags; + } + + /** + * @return array{int, array{term_id: int, name: string, results: int}} + */ + public static function get_filters(): array { + $tags = self::get_all(); + $filters = []; + + foreach ( $tags as $tag ) { + $filters[ $tag->term_id ] = [ + 'term_id' => $tag->term_id, + 'name' => $tag->name, + 'results' => 0, + ]; + } + + return $filters; + } +} diff --git a/templates/search.twig b/templates/search.twig index f5971592d2..b9beb126aa 100644 --- a/templates/search.twig +++ b/templates/search.twig @@ -165,37 +165,6 @@ {% endif %} -
- {% if ( tags|length > 0 ) %} -
- - {{ __( 'Campaign', 'planet4-master-theme' ) }} - -
-
    - {% for tag in tags %} - {% if ( tag.results > 0 ) %} -
  • - -
  • - {% endif %} - {% endfor %} -
-
-
- {% endif %} -
@@ -251,26 +220,27 @@
{% endif %} - {% if ( tags|length > 0 ) %} + {% if ( post_types|length > 0 ) %}
- -
+
    - {% for tag in tags %} - {% if ( tag.results > 0 ) %} + {% for post_type in post_types %} + {% if ( post_type.results > 0 ) %}
  • {% endif %} @@ -279,28 +249,30 @@
{% endif %} - {% if ( page_types|length > 0 ) %} + {% if ( action_types|length > 0 ) %}
- -
+
    - {% for page_type in page_types %} + {% for id, action_type in action_types %} + {% if ( action_type.results > 0 ) %}
  • + {% endif %} {% endfor %}