diff --git a/classmap.php b/classmap.php index a17780d2..54997093 100644 --- a/classmap.php +++ b/classmap.php @@ -47,6 +47,10 @@ require_once ALGOLIA_PATH . 'includes/class-algolia-styles.php'; require_once ALGOLIA_PATH . 'includes/class-algolia-scripts.php'; +require_once ALGOLIA_PATH . 'includes/indices/settings/interface-algolia-index-settings.php'; +require_once ALGOLIA_PATH . 'includes/indices/settings/class-algolia-primary-index-settings.php'; +require_once ALGOLIA_PATH . 'includes/indices/settings/class-algolia-index-settings-decorator.php'; +require_once ALGOLIA_PATH . 'includes/indices/settings/class-algolia-replica-index-settings.php'; require_once ALGOLIA_PATH . 'includes/indices/class-algolia-index.php'; require_once ALGOLIA_PATH . 'includes/indices/class-algolia-index-replica.php'; require_once ALGOLIA_PATH . 'includes/indices/class-algolia-searchable-posts-index.php'; diff --git a/includes/indices/class-algolia-index.php b/includes/indices/class-algolia-index.php index 90e2abf9..c3b51e8a 100644 --- a/includes/indices/class-algolia-index.php +++ b/includes/indices/class-algolia-index.php @@ -11,6 +11,7 @@ use WebDevStudios\WPSWA\Algolia\AlgoliaSearch\Exceptions\AlgoliaException; use WebDevStudios\WPSWA\Algolia\AlgoliaSearch\SearchClient; use WebDevStudios\WPSWA\Algolia\AlgoliaSearch\SearchIndex; +use WebDevStudios\WPSWA\Algolia\AlgoliaSearch\Exceptions\NotFoundException; /** * Class Algolia_Index @@ -18,6 +19,14 @@ * @since 1.0.0 */ abstract class Algolia_Index { + const AC_INDEX_NOT_EXISTS_EXCEPTION_MSG = 'Index does not exist'; + + /** + * Default index settings + * + * @var array + */ + protected array $default_settings = []; /** * The SearchClient instance. @@ -152,7 +161,7 @@ final public function set_client( SearchClient $client ) { * * @throws LogicException If the SearchClient has not been set. */ - final protected function get_client() { + public function get_client() { if ( null === $this->client ) { throw new LogicException( 'SearchClient has not been set.' ); } @@ -579,8 +588,7 @@ public function push_settings() { $index = $this->get_index(); // This will create the index if it does not exist. - $settings = $this->get_settings(); - $index->setSettings( $settings ); + ( new Algolia_Primary_Index_Settings( $this ) )->push(); // Push synonyms. $synonyms = $this->get_synonyms(); @@ -686,7 +694,29 @@ protected function get_re_index_batch_size() { * * @return array */ - abstract protected function get_settings(); + public function get_default_settings() { + $settings = (array) apply_filters( 'algolia_' . $this->get_id() . '_index_settings', $this->default_settings ); + + /** + * Replacing `attributesToIndex` with `searchableAttributes` as + * it has been replaced by Algolia. + * + * @link https://www.algolia.com/doc/api-reference/api-parameters/searchableAttributes/ + * @since 2.2.0 + */ + if ( + array_key_exists( 'attributesToIndex', $settings ) + && is_array( $settings['attributesToIndex'] ) + ) { + $settings['searchableAttributes'] = array_merge( + $settings['searchableAttributes'], + $settings['attributesToIndex'] + ); + unset( $settings['attributesToIndex'] ); + } + + return $settings; + } /** * Get synonyms. @@ -824,10 +854,7 @@ private function sync_replicas() { ) ); - $client = $this->get_client(); - - // Ensure we re-push the master index settings each time. - $settings = $this->get_settings(); + $settings = new Algolia_Primary_Index_Settings( $this ); // TODO: maybe move a common place. /** * Loop over the replicas. @@ -838,10 +865,7 @@ private function sync_replicas() { * @var Algolia_Index_Replica $replica */ foreach ( $replicas as $replica ) { - $settings['ranking'] = $replica->get_ranking(); - $replica_index_name = $replica->get_replica_index_name( $this ); - $index = $client->initIndex( $replica_index_name ); - $index->setSettings( $settings ); + ( new Algolia_Replica_Index_Settings( $settings, $replica ) ); } } @@ -858,27 +882,23 @@ abstract public function delete_item( $item ); /** * Check if the index exists in Algolia. * - * Returns true if the index exists in Algolia. - * false otherwise. + * Returns true if the index exists in Algolia, false otherwise. * + * @throws \Exception various exceptions can be thrown by Algolia Client. * @author WebDevStudios * @since 1.0.0 * * @return bool - * - * @throws AlgoliaException If the index does not exist in Algolia. */ public function exists() { try { $this->get_index()->getSettings(); - } catch ( AlgoliaException $exception ) { - if ( $exception->getMessage() === 'Index does not exist' ) { + } catch ( NotFoundException $exception ) { + if ( self::AC_INDEX_NOT_EXISTS_EXCEPTION_MSG === $exception->getMessage() ) { return false; } - error_log( $exception->getMessage() ); // phpcs:ignore -- Legacy. - - return false; + throw $exception; } return true; diff --git a/includes/indices/class-algolia-posts-index.php b/includes/indices/class-algolia-posts-index.php index 91971be6..da5b4652 100644 --- a/includes/indices/class-algolia-posts-index.php +++ b/includes/indices/class-algolia-posts-index.php @@ -14,6 +14,30 @@ * @since 1.0.0 */ final class Algolia_Posts_Index extends Algolia_Index { + protected array $default_settings = [ + 'searchableAttributes' => array( + 'unordered(post_title)', + 'unordered(taxonomies)', + 'unordered(content)', + ), + 'customRanking' => array( + 'desc(is_sticky)', + 'desc(post_date)', + 'asc(record_index)', + ), + 'attributeForDistinct' => 'post_id', + 'distinct' => true, + 'attributesForFaceting' => array( + 'taxonomies', + 'taxonomies_hierarchical', + 'post_author.display_name', + ), + 'attributesToSnippet' => array( + 'post_title:30', + 'content:30', + ), + 'snippetEllipsisText' => '…', + ]; /** * The post type. @@ -264,60 +288,16 @@ private function get_post_shared_attributes( WP_Post $post ) { /** * Get settings. + * Overridden to able to have "algolia_posts_index_settings" filter. * * @author WebDevStudios * @since 1.0.0 * * @return array */ - protected function get_settings() { - $settings = array( - 'searchableAttributes' => array( - 'unordered(post_title)', - 'unordered(taxonomies)', - 'unordered(content)', - ), - 'customRanking' => array( - 'desc(is_sticky)', - 'desc(post_date)', - 'asc(record_index)', - ), - 'attributeForDistinct' => 'post_id', - 'distinct' => true, - 'attributesForFaceting' => array( - 'taxonomies', - 'taxonomies_hierarchical', - 'post_author.display_name', - ), - 'attributesToSnippet' => array( - 'post_title:30', - 'content:30', - ), - 'snippetEllipsisText' => '…', - ); - - $settings = (array) apply_filters( 'algolia_posts_index_settings', $settings, $this->post_type ); - $settings = (array) apply_filters( 'algolia_posts_' . $this->post_type . '_index_settings', $settings ); - - /** - * Replacing `attributesToIndex` with `searchableAttributes` as - * it has been replaced by Algolia. - * - * @link https://www.algolia.com/doc/api-reference/api-parameters/searchableAttributes/ - * @since 2.2.0 - */ - if ( - array_key_exists( 'attributesToIndex', $settings ) - && is_array( $settings['attributesToIndex'] ) - ) { - $settings['searchableAttributes'] = array_merge( - $settings['searchableAttributes'], - $settings['attributesToIndex'] - ); - unset( $settings['attributesToIndex'] ); - } - - return $settings; + public function get_default_settings() { + $this->default_settings = (array) apply_filters( 'algolia_posts_index_settings', $this->default_settings, $this->post_type ); + return parent::get_default_settings(); } /** diff --git a/includes/indices/class-algolia-searchable-posts-index.php b/includes/indices/class-algolia-searchable-posts-index.php index 16ed0441..3a2e3888 100644 --- a/includes/indices/class-algolia-searchable-posts-index.php +++ b/includes/indices/class-algolia-searchable-posts-index.php @@ -249,13 +249,15 @@ private function get_post_shared_attributes( WP_Post $post ) { /** * Get settings. * + * Overridden to able to have "excerpt_length" WP filter hook for the attributesToSnippet.content + * * @author WebDevStudios * @since 1.0.0 * * @return array */ - protected function get_settings() { - $settings = array( + public function get_default_settings() { + $this->default_settings = [ 'searchableAttributes' => array( 'unordered(post_title)', 'unordered(taxonomies)', @@ -279,29 +281,9 @@ protected function get_settings() { 'content:' . intval( apply_filters( 'excerpt_length', 55 ) ), // phpcs:ignore -- Legitimate use of Core hook. ), 'snippetEllipsisText' => '…', - ); - - $settings = (array) apply_filters( 'algolia_searchable_posts_index_settings', $settings ); - - /** - * Replacing `attributesToIndex` with `searchableAttributes` as - * it has been replaced by Algolia. - * - * @link https://www.algolia.com/doc/api-reference/api-parameters/searchableAttributes/ - * @since 2.2.0 - */ - if ( - array_key_exists( 'attributesToIndex', $settings ) - && is_array( $settings['attributesToIndex'] ) - ) { - $settings['searchableAttributes'] = array_merge( - $settings['searchableAttributes'], - $settings['attributesToIndex'] - ); - unset( $settings['attributesToIndex'] ); - } + ]; - return $settings; + return parent::get_default_settings(); } /** diff --git a/includes/indices/class-algolia-terms-index.php b/includes/indices/class-algolia-terms-index.php index 4aeadc5b..b68c49c7 100644 --- a/includes/indices/class-algolia-terms-index.php +++ b/includes/indices/class-algolia-terms-index.php @@ -14,6 +14,15 @@ * @since 1.0.0 */ final class Algolia_Terms_Index extends Algolia_Index { + protected array $default_settings = [ + 'searchableAttributes' => array( + 'unordered(name)', + 'unordered(description)', + ), + 'customRanking' => array( + 'desc(posts_count)', + ), + ]; /** * What this index contains. @@ -129,39 +138,10 @@ protected function get_re_index_items_count() { * * @return array */ - protected function get_settings() { - $settings = array( - 'searchableAttributes' => array( - 'unordered(name)', - 'unordered(description)', - ), - 'customRanking' => array( - 'desc(posts_count)', - ), - ); - - $settings = (array) apply_filters( 'algolia_terms_index_settings', $settings, $this->taxonomy ); - $settings = (array) apply_filters( 'algolia_terms_' . $this->taxonomy . '_index_settings', $settings ); - - /** - * Replacing `attributesToIndex` with `searchableAttributes` as - * it has been replaced by Algolia. - * - * @link https://www.algolia.com/doc/api-reference/api-parameters/searchableAttributes/ - * @since 2.2.0 - */ - if ( - array_key_exists( 'attributesToIndex', $settings ) - && is_array( $settings['attributesToIndex'] ) - ) { - $settings['searchableAttributes'] = array_merge( - $settings['searchableAttributes'], - $settings['attributesToIndex'] - ); - unset( $settings['attributesToIndex'] ); - } - - return $settings; + public function get_default_settings() { + // override settings prop to have a custom WP filter hook for terms only + $this->default_settings = apply_filters( 'algolia_terms_index_settings', $this->default_settings, $this->taxonomy ); + return parent::get_default_settings(); } /** diff --git a/includes/indices/class-algolia-users-index.php b/includes/indices/class-algolia-users-index.php index 9dfc5a70..8ff1ed37 100644 --- a/includes/indices/class-algolia-users-index.php +++ b/includes/indices/class-algolia-users-index.php @@ -14,6 +14,14 @@ * @since 1.0.0 */ final class Algolia_Users_Index extends Algolia_Index { + protected array $default_settings = [ + 'searchableAttributes' => array( + 'unordered(display_name)', + ), + 'customRanking' => array( + 'desc(posts_count)', + ), + ]; /** * What this index contains. @@ -113,47 +121,6 @@ protected function get_re_index_items_count() { return (int) $users_count['total_users']; } - /** - * Get settings. - * - * @author WebDevStudios - * @since 1.0.0 - * - * @return array - */ - protected function get_settings() { - $settings = array( - 'searchableAttributes' => array( - 'unordered(display_name)', - ), - 'customRanking' => array( - 'desc(posts_count)', - ), - ); - - $settings = (array) apply_filters( 'algolia_users_index_settings', $settings ); - - /** - * Replacing `attributesToIndex` with `searchableAttributes` as - * it has been replaced by Algolia. - * - * @link https://www.algolia.com/doc/api-reference/api-parameters/searchableAttributes/ - * @since 2.2.0 - */ - if ( - array_key_exists( 'attributesToIndex', $settings ) - && is_array( $settings['attributesToIndex'] ) - ) { - $settings['searchableAttributes'] = array_merge( - $settings['searchableAttributes'], - $settings['attributesToIndex'] - ); - unset( $settings['attributesToIndex'] ); - } - - return $settings; - } - /** * Get synonyms. * diff --git a/includes/indices/settings/class-algolia-index-settings-decorator.php b/includes/indices/settings/class-algolia-index-settings-decorator.php new file mode 100644 index 00000000..9c0bb6f6 --- /dev/null +++ b/includes/indices/settings/class-algolia-index-settings-decorator.php @@ -0,0 +1,39 @@ +index_settings = $index_settings; + } + + public function get_index(): Algolia_Index { + return $this->index_settings->get_index(); + } + + public function get_algolia_index(): SearchIndex { + return $this->index_settings->get_algolia_index(); + } + + public function get_local_settings(): array { + return $this->index_settings->get_local_settings(); + } + + public function get_remote_settings(): array { + return $this->index_settings->get_remote_settings(); + } + + public function set_algolia_index(): void { + $this->index_settings->set_algolia_index(); + } + + public function get_settings_needs_sync(): array { + return $this->index_settings->get_settings_needs_sync(); + } + + public function push(): bool { + return $this->index_settings->push(); + } +} diff --git a/includes/indices/settings/class-algolia-primary-index-settings.php b/includes/indices/settings/class-algolia-primary-index-settings.php new file mode 100644 index 00000000..bf81f242 --- /dev/null +++ b/includes/indices/settings/class-algolia-primary-index-settings.php @@ -0,0 +1,85 @@ +index = $index; + $this->set_algolia_index(); + } + + public function get_index(): Algolia_Index { + return $this->index; + } + + public function get_algolia_index(): SearchIndex { + return $this->algolia_index; + } + + public function set_algolia_index(): void { + $this->algolia_index = $this->get_index()->get_index(); + } + + public function get_local_settings(): array { + return $this->get_index()->get_default_settings(); + } + + /** + * If the Indice is not found, return empty settings. In that case, a new index is created. + * + * @throws Exception Varius exceptions can be thrown by Algolia Client except for NotFoundException. + * @return array + */ + public function get_remote_settings(): array { + try { + return $this->get_algolia_index()->getSettings(); + } catch ( NotFoundException $e ) { + // being more strict, catch only index does not exists case. + if ( Algolia_Index::AC_INDEX_NOT_EXISTS_EXCEPTION_MSG === $e->getMessage() ) { + return []; + } + + throw $e; + } + } + + public function get_settings_needs_sync(): array { + $remote_settings = $this->get_remote_settings(); + + $needs_sync = []; + + foreach ( $this->get_local_settings() as $key => $value ) { + if ( ! array_key_exists( $key, $remote_settings ) || $remote_settings[ $key ] === null ) { + $needs_sync[ $key ] = $value; + } + } + + return $needs_sync; + } + + /** + * Push settings to the Algolia + * + * @param array $overrides The settings array that will be forcefully pushed. + * @return bool + */ + public function push( $overrides = [] ): bool { + $settings = wp_parse_args( $overrides, $this->get_settings_needs_sync() ); + + if ( count( $settings ) === 0 ) { + return false; + } + + try { + $this->get_algolia_index()->setSettings( $settings ); // Creates new indice if doesn't exist. + } catch ( Exception $e ) { + return false; + } + + return true; + } +} diff --git a/includes/indices/settings/class-algolia-replica-index-settings.php b/includes/indices/settings/class-algolia-replica-index-settings.php new file mode 100644 index 00000000..51284a70 --- /dev/null +++ b/includes/indices/settings/class-algolia-replica-index-settings.php @@ -0,0 +1,29 @@ +replica = $replica; + } + + public function get_local_settings(): array { + $settings = $this->index_settings->get_local_settings(); + $settings['ranking'] = $this->replica->get_ranking(); + + return $settings; + } + + protected function switch_algolia_index(): void { + $primary_index = $this->index_settings->get_index(); + $client = $primary_index->get_client(); + $replica_index_name = $this->replica->get_replica_index_name( $primary_index ); + $this->set_algolia_index( $client->initIndex( $replica_index_name ) ); + } + + public function push(): bool { + $this->switch_algolia_index(); + return parent::push(); + } +} diff --git a/includes/indices/settings/interface-algolia-index-settings.php b/includes/indices/settings/interface-algolia-index-settings.php new file mode 100644 index 00000000..18439dad --- /dev/null +++ b/includes/indices/settings/interface-algolia-index-settings.php @@ -0,0 +1,13 @@ +