prepare(): values and ID lists as placeholders, table and // column names as %i. Fragments added to a query are code (Scope::sql()) or are prepared // where they are built (Scope::type_sql(), Lang::sql(), SeoMeta joins). /** * ClusterTypes (0.98.177) * * Clusters come from categories, tags, WooCommerce product categories, custom * clusters — and now from any other public taxonomy of the scanned content * (attraction categories, regions, genres …). cluster_type holds the taxonomy * name; this class names and describes the types for the screens, and finds * the clusters an article belongs to, whatever their taxonomy. */ final class ClusterTypes { /** Taxonomies that are never offered as clusters. */ private const NEVER = [ 'post_format', 'nav_menu', 'link_category', 'product_visibility', 'product_type', 'product_shipping_class', 'wp_theme', 'wp_template_part_area', 'wp_pattern_category' ]; /** Types with their own settings and cards (everything else goes through enabled_terms). */ public const BUILT_IN = [ 'category', 'post_tag', 'product_cat', 'custom' ]; /** * Public taxonomies of these post types that could become clusters, other * than categories, tags and product categories. * * @param string[] $post_types * @return array */ public static function extra_taxonomies( array $post_types ): array { $out = []; foreach ( get_object_taxonomies( array_values( $post_types ), 'objects' ) as $name => $tax ) { if ( in_array( $name, self::BUILT_IN, true ) || in_array( $name, self::NEVER, true ) ) continue; if ( empty( $tax->public ) || empty( $tax->show_ui ) || strlen( $name ) > 32 ) continue; $out[ $name ] = $tax; } uasort( $out, fn( $a, $b ) => ( (int) ! $a->hierarchical <=> (int) ! $b->hierarchical ) ?: strcasecmp( $a->label, $b->label ) ); return $out; } /** * Label, glyph and member noun per cluster type, for the scripts * (window.clusterlaneClusterTypes). Types not listed fall back to the taxonomy name. * * @return array */ public static function for_js(): array { $out = [ 'category' => [ 'label' => __( 'Category', 'clusterlane' ), 'plural' => __( 'Categories', 'clusterlane' ), 'glyph' => 'folder', 'noun' => 'article' ], 'post_tag' => [ 'label' => __( 'Tag', 'clusterlane' ), 'plural' => __( 'Tags', 'clusterlane' ), 'glyph' => 'tag', 'noun' => 'article' ], 'custom' => [ 'label' => __( 'Custom', 'clusterlane' ), 'plural' => __( 'Custom clusters', 'clusterlane' ), 'glyph' => 'pencil', 'noun' => 'article' ], 'product_cat' => [ 'label' => __( 'Product category', 'clusterlane' ), 'plural' => __( 'Product categories', 'clusterlane' ), 'glyph' => 'bag', 'noun' => 'product' ], ]; foreach ( self::in_use() as $tax ) { if ( isset( $out[ $tax ] ) ) continue; if ( HubClusters::is_hub_type( $tax ) ) { // pages as clusters (0.98.180): named after the post type $pt = get_post_type_object( HubClusters::post_type_of( $tax ) ); $out[ $tax ] = [ 'label' => $pt ? html_entity_decode( (string) $pt->labels->singular_name, ENT_QUOTES, 'UTF-8' ) : $tax, 'plural' => $pt ? html_entity_decode( (string) $pt->labels->name, ENT_QUOTES, 'UTF-8' ) : $tax, 'glyph' => 'link', 'noun' => 'article', ]; continue; } $obj = get_taxonomy( $tax ); $out[ $tax ] = [ 'label' => $obj ? html_entity_decode( (string) $obj->labels->singular_name, ENT_QUOTES, 'UTF-8' ) : $tax, 'plural' => $obj ? html_entity_decode( (string) $obj->labels->name, ENT_QUOTES, 'UTF-8' ) : $tax, 'glyph' => ( $obj && $obj->hierarchical ) ? 'folder' : 'tag', 'noun' => 'article', ]; } return $out; } /** Taxonomies that have active clusters now (not custom). @return string[] */ public static function in_use(): array { global $wpdb; static $cache = null; if ( $cache !== null ) return $cache; $table = $wpdb->prefix . 'clusterlane_clusters'; if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) !== $table ) return $cache = []; // phpcs:ignore WordPress.DB.DirectDatabaseQuery $cache = array_map( 'strval', (array) $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT cluster_type FROM %i WHERE is_active = 1 AND cluster_type <> 'custom'", $table ) ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery return $cache; } /** * The cluster keys an article belongs to: its terms in every taxonomy that * has clusters, plus custom clusters. Categories, tags, product categories * and other taxonomies alike (before 0.98.177 only categories and tags). * * @return string[] */ public static function post_keys( int $post_id ): array { global $wpdb; $keys = []; $taxes = array_values( array_filter( array_unique( array_merge( [ 'category', 'post_tag' ], self::in_use() ) ), 'taxonomy_exists' ) ); if ( $taxes ) { $terms = wp_get_object_terms( $post_id, $taxes, [ 'fields' => 'all' ] ); if ( ! is_wp_error( $terms ) ) { foreach ( $terms as $t ) $keys[] = $t->taxonomy . '_' . (int) $t->term_id; } } $custom = $wpdb->get_col( $wpdb->prepare( "SELECT cluster_key FROM {$wpdb->prefix}clusterlane_custom_members WHERE post_id = %d", $post_id ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery return array_values( array_unique( array_merge( $keys, array_map( 'strval', (array) $custom ), HubClusters::keys_for( $post_id ) ) ) ); } }