From 9bd771ddf474ab90b6e068ab9677617ea85d6d33 Mon Sep 17 00:00:00 2001
From: lithrel <lithrel@randomdomainname.net>
Date: Wed, 27 Apr 2022 08:51:19 +0200
Subject: [PATCH] PLANET-6552: Extract search filters

Extract search filters to their own classes
Use consistent taxonomy
---
 src/Search.php                      | 142 +++++-----------------------
 src/Search/Filters/Categories.php   |  39 ++++++++
 src/Search/Filters/ContentTypes.php | 116 +++++++++++++++++++++++
 src/Search/Filters/PostTypes.php    |  53 +++++++++++
 src/Search/Filters/Tags.php         |  48 ++++++++++
 templates/search.twig               |  16 ++--
 6 files changed, 288 insertions(+), 126 deletions(-)
 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/Search.php b/src/Search.php
index 34eea89282..c81ac4cda4 100644
--- a/src/Search.php
+++ b/src/Search.php
@@ -727,85 +727,21 @@ 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' );
+		// Categories.
+		$context['categories'] = Search\Filters\Categories::get_filters();
+		uasort( $context['categories'], fn ( $a, $b ) => strcmp( $a['name'], $b['name'] ) );
 
-		// 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,
-				];
-			}
-		}
+		// Post Types.
+		$context['post_types'] = Search\Filters\PostTypes::get_filters();
 
-		// Tag <-> Campaign.
-		$tags = get_terms(
-			[
-				'taxonomy'   => 'post_tag',
-				'hide_empty' => false,
-			]
+		// Content Types.
+		$context['content_types'] = Search\Filters\ContentTypes::get_filters(
+			self::should_include_archive()
 		);
-		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,7 +755,7 @@ 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';
@@ -830,34 +766,11 @@ protected function set_filters_context( &$context ) {
 				}
 			}
 		}
-
-		// 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 ) {
@@ -908,33 +821,24 @@ protected function set_results_context( &$context ) {
 			}
 
 			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 );
-
-				$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'],
-				];
+				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'];
 			}
 
 			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'];
 			}
 		}
 
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 @@
+<?php
+
+declare(strict_types=1);
+
+namespace P4\MasterTheme\Search\Filters;
+
+/**
+ * Categories used for search.
+ */
+class Categories {
+	/**
+	 * @return array<WP_Term>
+	 */
+	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..854d9e1439
--- /dev/null
+++ b/src/Search/Filters/ContentTypes.php
@@ -0,0 +1,116 @@
+<?php
+
+declare(strict_types=1);
+
+namespace P4\MasterTheme\Search\Filters;
+
+/**
+ * Content type used for search.
+ * Native types (post, page, attachment, etc.).
+ * Custom types (p4_action, etc.).
+ */
+class ContentTypes {
+	public const ACT_PAGE   = 'action';
+	public const POST       = 'post';
+	public const PAGE       = 'page';
+	public const CAMPAIGN   = 'campaign';
+	public const ATTACHMENT = 'attachment';
+	public const ARCHIVE    = 'archive';
+
+	/**
+	 * Get all content types.
+	 *
+	 * @return array<WP_Post_Type>
+	 */
+	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.
+	 *
+	 * @return array{string, array{id: int, name: string, results: int}}
+	 */
+	public static function get_filters(
+		bool $include_archive = false
+	): array {
+		$types  = self::get_all();
+		$config = self::get_config();
+
+		// ACT_PAGE are sub-pages of the Act page and not a real type, adding manually.
+		$filters = [
+			0 => [
+				'id'      => 0,
+				'name'    => __( 'Action', 'planet4-master-theme' ),
+				'results' => 0,
+			],
+		];
+
+		foreach ( $types as $name => $type ) {
+			if ( self::ARCHIVE === $name && ! $include_archive ) {
+				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::ACT_PAGE   => [
+				'id'    => 0,
+				'label' => __( 'Action', 'planet4-master-theme' ),
+			],
+			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' ),
+			],
+		];
+	}
+}
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 @@
+<?php
+
+declare(strict_types=1);
+
+namespace P4\MasterTheme\Search\Filters;
+
+use WP_Term;
+
+/**
+ * Post types used for search (Press release, News, Report, etc.).
+ * Configured in `Posts > 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 @@
+<?php
+
+declare(strict_types=1);
+
+namespace P4\MasterTheme\Search\Filters;
+
+use WP_Term;
+
+/**
+ * Tags used for search.
+ */
+class Tags {
+	/**
+	 * @return WP_Term[]
+	 */
+	public static function get_all(): array {
+		$tags = get_terms(
+			[
+				'taxonomy'   => '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..1f149bcf4b 100644
--- a/templates/search.twig
+++ b/templates/search.twig
@@ -279,28 +279,30 @@
 									</div>
 								</div>
 							{% endif %}
-							{% if ( page_types|length > 0 ) %}
+							{% if ( post_types|length > 0 ) %}
 								<div class="filteritem">
 									<a data-bs-toggle="collapse" href="#item-category" class="{{ collapsed }}" aria-expanded="{{ expanded }}">
 										{{ __( 'Post Type', 'planet4-master-theme' ) }} <span></span>
 									</a>
 									<div id="item-category" class="collapse {{ show }}" role="tabpanel">
 										<ul class="list-unstyled">
-											{% for page_type in page_types %}
+											{% for post_type in post_types %}
+												{% if ( post_type.results > 0 ) %}
 												<li>
 													<label class="custom-control">
 														<input
 															type="checkbox"
-															name="f[ptype][{{ page_type.name }}]"
-															value="{{ page_type.term_id }}"
+															name="f[ptype][{{ post_type.name }}]"
+															value="{{ post_type.term_id }}"
 															class="p4-custom-control-input"
 															data-ga-category="Search Page"
 															data-ga-action="Post Type Filter"
-															data-ga-label="{{ page_type.name|e('wp_kses_post')|raw }}"
-															{{ page_type.checked }} {{ page_type.results > 0 ? '' : 'disabled' }} />
-														<span class="custom-control-description">{{ page_type.name|e('wp_kses_post')|raw }} ({{ page_type.results }})</span>
+															data-ga-label="{{ post_type.name|e('wp_kses_post')|raw }}"
+															{{ post_type.checked }} {{ post_type.results > 0 ? '' : 'disabled' }} />
+														<span class="custom-control-description">{{ post_type.name|e('wp_kses_post')|raw }} ({{ post_type.results }})</span>
 													</label>
 												</li>
+												{% endif %}
 											{% endfor %}
 										</ul>
 									</div>