$template ) { if ( $type !== $template['type'] ) { continue; } // On an Elementor Canvas page only templates with the Canvas flag count. if ( $canvas && '1' !== $template['canvas'] ) { continue; } $score = self::score_template( $template ); if ( $score < 0 ) { continue; } if ( $score > $best_score || ( $score === $best_score && $id > $winner ) ) { $winner = $id; $best_score = $score; } } return self::translate( $winner ); } /** * Evaluates the rules of one template. * * @param array $template Template row from get_templates(). * @return int Score of the best matching include rule, or -1 when the template does not match. */ private static function score_template( $template ) { if ( ! Rules::role_matches( $template['roles'] ) ) { return -1; } foreach ( $template['exclude'] as $rule ) { if ( Rules::matches( $rule ) ) { return -1; } } $score = -1; foreach ( $template['include'] as $rule ) { if ( Rules::matches( $rule ) ) { $score = max( $score, Rules::score( $rule ) ); } } return $score; } /** * Returns the published templates with their rules. * * The list lives in a transient without expiration, so WordPress loads it * with the autoloaded options, or from the object cache when the site has * one. The post type hooks clear it on every change. * * @return array id => [ type, include, exclude, roles, canvas ], newest first */ public static function get_templates() { $key = self::get_language_key(); if ( isset( self::$templates[ $key ] ) ) { return self::$templates[ $key ]; } $stored = get_transient( self::TEMPLATES_TRANSIENT ); if ( is_array( $stored ) && isset( $stored[ $key ] ) && is_array( $stored[ $key ] ) ) { self::$templates[ $key ] = $stored[ $key ]; return self::$templates[ $key ]; } $templates = self::load_templates(); $stored = is_array( $stored ) ? $stored : []; $stored[ $key ] = $templates; set_transient( self::TEMPLATES_TRANSIENT, $stored ); self::$templates[ $key ] = $templates; return $templates; } /** * Loads the published templates and their rules from the database. * * @return array */ private static function load_templates() { $query = new \WP_Query( [ 'post_type' => Post_Type::POST_TYPE, 'post_status' => 'publish', 'posts_per_page' => -1, 'no_found_rows' => true, 'fields' => 'ids', 'orderby' => 'ID', 'order' => 'DESC', 'update_post_term_cache' => false, 'ignore_sticky_posts' => true, 'lang' => '', ] ); $ids = array_map( 'intval', $query->posts ); if ( empty( $ids ) ) { return []; } update_meta_cache( 'post', $ids ); $templates = []; foreach ( $ids as $id ) { $include = get_post_meta( $id, Post_Type::META_INCLUDE, true ); $exclude = get_post_meta( $id, Post_Type::META_EXCLUDE, true ); $roles = get_post_meta( $id, Post_Type::META_ROLES, true ); $templates[ $id ] = [ 'type' => Post_Type::sanitize_type( get_post_meta( $id, Post_Type::META_TYPE, true ) ), 'include' => is_array( $include ) ? array_values( $include ) : [], 'exclude' => is_array( $exclude ) ? array_values( $exclude ) : [], 'roles' => is_array( $roles ) ? array_values( $roles ) : [], 'canvas' => '1' === get_post_meta( $id, Post_Type::META_CANVAS, true ) ? '1' : '0', ]; } return $templates; } /** * Returns a cache key for the current language on multilingual sites. * * @return string */ private static function get_language_key() { if ( function_exists( 'pll_current_language' ) ) { return 'pll-' . (string) pll_current_language( 'slug' ); } if ( defined( 'ICL_LANGUAGE_CODE' ) ) { return 'wpml-' . (string) ICL_LANGUAGE_CODE; } return 'all'; } /** * Swaps the winner for its translation on multilingual sites. * * @param int $id Template ID. * @return int */ private static function translate( $id ) { if ( ! $id ) { return 0; } if ( defined( 'ICL_SITEPRESS_VERSION' ) ) { $id = (int) apply_filters( 'wpml_object_id', $id, Post_Type::POST_TYPE, true ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML owns this hook. } if ( function_exists( 'pll_get_post' ) ) { $translated = pll_get_post( $id ); if ( $translated ) { $id = (int) $translated; } } return $id; } }