445 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			445 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
 | 
						|
class ET_Theme_Builder_Request {
 | 
						|
	/**
 | 
						|
	 * Type constants.
 | 
						|
	 */
 | 
						|
	const TYPE_FRONT_PAGE        = 'front_page';
 | 
						|
	const TYPE_404               = '404';
 | 
						|
	const TYPE_SEARCH            = 'search';
 | 
						|
	const TYPE_SINGULAR          = 'singular';
 | 
						|
	const TYPE_POST_TYPE_ARCHIVE = 'archive';
 | 
						|
	const TYPE_TERM              = 'term';
 | 
						|
	const TYPE_AUTHOR            = 'author';
 | 
						|
	const TYPE_DATE              = 'date';
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Requested object type.
 | 
						|
	 *
 | 
						|
	 * @var string
 | 
						|
	 */
 | 
						|
	protected $type = '';
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Requested object subtype.
 | 
						|
	 *
 | 
						|
	 * @var string
 | 
						|
	 */
 | 
						|
	protected $subtype = '';
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Requested object id.
 | 
						|
	 *
 | 
						|
	 * @var integer
 | 
						|
	 */
 | 
						|
	protected $id = 0;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Create a request object based on the current request.
 | 
						|
	 *
 | 
						|
	 * @since 4.0
 | 
						|
	 *
 | 
						|
	 * @return ET_Theme_Builder_Request|null
 | 
						|
	 */
 | 
						|
	public static function from_current() {
 | 
						|
		$is_extra_layout_home = 'layout' === get_option( 'show_on_front' ) && is_home();
 | 
						|
 | 
						|
		if ( $is_extra_layout_home || is_front_page() ) {
 | 
						|
			return new self( self::TYPE_FRONT_PAGE, '', get_queried_object_id() );
 | 
						|
		}
 | 
						|
 | 
						|
		if ( is_404() ) {
 | 
						|
			return new self( self::TYPE_404, '', 0 );
 | 
						|
		}
 | 
						|
 | 
						|
		if ( is_search() ) {
 | 
						|
			return new self( self::TYPE_SEARCH, '', 0 );
 | 
						|
		}
 | 
						|
 | 
						|
		$id             = get_queried_object_id();
 | 
						|
		$object         = get_queried_object();
 | 
						|
		$page_for_posts = (int) get_option( 'page_for_posts' );
 | 
						|
		$is_blog_page   = 0 !== $page_for_posts && is_page( $page_for_posts );
 | 
						|
 | 
						|
		if ( is_singular() ) {
 | 
						|
			return new self( self::TYPE_SINGULAR, get_post_type( $id ), $id );
 | 
						|
		}
 | 
						|
 | 
						|
		if ( $is_blog_page || is_home() ) {
 | 
						|
			return new self( self::TYPE_POST_TYPE_ARCHIVE, 'post', $id );
 | 
						|
		}
 | 
						|
 | 
						|
		if ( is_category() || is_tag() || is_tax() ) {
 | 
						|
			return new self( self::TYPE_TERM, $object->taxonomy, $id );
 | 
						|
		}
 | 
						|
 | 
						|
		if ( is_post_type_archive() ) {
 | 
						|
			return new self( self::TYPE_POST_TYPE_ARCHIVE, $object->name, $id );
 | 
						|
		}
 | 
						|
 | 
						|
		if ( is_author() ) {
 | 
						|
			return new self( self::TYPE_AUTHOR, '', $id );
 | 
						|
		}
 | 
						|
 | 
						|
		if ( is_date() ) {
 | 
						|
			return new self( self::TYPE_DATE, '', 0 );
 | 
						|
		}
 | 
						|
 | 
						|
		return null;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Create a request object based on a post id.
 | 
						|
	 *
 | 
						|
	 * @since 4.0
 | 
						|
	 *
 | 
						|
	 * @param integer $post_id
 | 
						|
	 *
 | 
						|
	 * @return ET_Theme_Builder_Request
 | 
						|
	 */
 | 
						|
	public static function from_post( $post_id ) {
 | 
						|
		if ( (int) get_option( 'page_on_front' ) === $post_id ) {
 | 
						|
			return new self( self::TYPE_FRONT_PAGE, '', $post_id );
 | 
						|
		}
 | 
						|
 | 
						|
		if ( (int) get_option( 'page_for_posts' ) === $post_id ) {
 | 
						|
			return new self( self::TYPE_POST_TYPE_ARCHIVE, 'post', $post_id );
 | 
						|
		}
 | 
						|
 | 
						|
		return new self( self::TYPE_SINGULAR, get_post_type( $post_id ), $post_id );
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Constructor.
 | 
						|
	 *
 | 
						|
	 * @since 4.0
 | 
						|
	 *
 | 
						|
	 * @param string  $type    Type.
 | 
						|
	 * @param string  $subtype Subtype.
 | 
						|
	 * @param integer $id      ID.
 | 
						|
	 */
 | 
						|
	public function __construct( $type, $subtype, $id ) {
 | 
						|
		$this->type    = $type;
 | 
						|
		$this->subtype = $subtype;
 | 
						|
		$this->id      = $id;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get the requested object type.
 | 
						|
	 *
 | 
						|
	 * @since 4.0
 | 
						|
	 *
 | 
						|
	 * @return string
 | 
						|
	 */
 | 
						|
	public function get_type() {
 | 
						|
		return $this->type;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get the requested object subtype.
 | 
						|
	 *
 | 
						|
	 * @since 4.0
 | 
						|
	 *
 | 
						|
	 * @return string
 | 
						|
	 */
 | 
						|
	public function get_subtype() {
 | 
						|
		return $this->subtype;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get the requested object id.
 | 
						|
	 *
 | 
						|
	 * @since 4.0
 | 
						|
	 *
 | 
						|
	 * @return string
 | 
						|
	 */
 | 
						|
	public function get_id() {
 | 
						|
		return $this->id;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get the top ancestor of a setting based on its id. Takes the setting itself
 | 
						|
	 * if it has no ancestors.
 | 
						|
	 * Returns an empty array if the setting is not found.
 | 
						|
	 *
 | 
						|
	 * @since 4.0
 | 
						|
	 *
 | 
						|
	 * @param array  $flat_settings Flat settings.
 | 
						|
	 * @param string $setting_id    Setting ID.
 | 
						|
	 *
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	protected function _get_template_setting_ancestor( $flat_settings, $setting_id ) {
 | 
						|
		$id = $setting_id;
 | 
						|
 | 
						|
		if ( ! isset( $flat_settings[ $id ] ) ) {
 | 
						|
			// If the setting is not found, check if a valid parent exists.
 | 
						|
			$parent_id = explode( ET_THEME_BUILDER_SETTING_SEPARATOR, $id );
 | 
						|
			array_pop( $parent_id );
 | 
						|
			$parent_id[] = '';
 | 
						|
			$parent_id   = implode( ET_THEME_BUILDER_SETTING_SEPARATOR, $parent_id );
 | 
						|
			$id          = $parent_id;
 | 
						|
		}
 | 
						|
 | 
						|
		if ( ! isset( $flat_settings[ $id ] ) ) {
 | 
						|
			// The setting is still not found - bail.
 | 
						|
			return array();
 | 
						|
		}
 | 
						|
 | 
						|
		return $flat_settings[ $id ];
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get $a or $b depending on which template setting has a higher priority.
 | 
						|
	 * Handles cases such as category settings with equal priority but in a ancestor-child relationship.
 | 
						|
	 * Returns an empty string if neither setting is found.
 | 
						|
	 *
 | 
						|
	 * @since 4.0
 | 
						|
	 *
 | 
						|
	 * @param array  $flat_settings Flat settings.
 | 
						|
	 * @param string $a             First template setting.
 | 
						|
	 * @param string $b             Second template setting.
 | 
						|
	 *
 | 
						|
	 * @return string
 | 
						|
	 */
 | 
						|
	protected function _get_higher_priority_template_setting( $flat_settings, $a, $b ) {
 | 
						|
		$map        = array_flip( array_keys( $flat_settings ) );
 | 
						|
		$a_ancestor = $this->_get_template_setting_ancestor( $flat_settings, $a );
 | 
						|
		$b_ancestor = $this->_get_template_setting_ancestor( $flat_settings, $b );
 | 
						|
		$a_found    = ! empty( $a_ancestor );
 | 
						|
		$b_found    = ! empty( $b_ancestor );
 | 
						|
 | 
						|
		if ( ! $a_found || ! $b_found ) {
 | 
						|
			if ( $a_found ) {
 | 
						|
				return $a;
 | 
						|
			}
 | 
						|
 | 
						|
			if ( $b_found ) {
 | 
						|
				return $b;
 | 
						|
			}
 | 
						|
 | 
						|
			return '';
 | 
						|
		}
 | 
						|
 | 
						|
		if ( $a_ancestor['priority'] !== $b_ancestor['priority'] ) {
 | 
						|
			// Priorities are not equal - use a simple comparison.
 | 
						|
			return $a_ancestor['priority'] >= $b_ancestor['priority'] ? $a : $b;
 | 
						|
		}
 | 
						|
 | 
						|
		if ( $a_ancestor['id'] !== $b_ancestor['id'] ) {
 | 
						|
			// Equal priorities, but the ancestors are not the same - use the order in $flat_settings
 | 
						|
			// so we have a deterministic result even if $a and $b are swapped.
 | 
						|
			return $map[ $a_ancestor['id'] ] <= $map[ $b_ancestor['id'] ] ? $a : $b;
 | 
						|
		}
 | 
						|
 | 
						|
		// Equal priorities, same ancestor.
 | 
						|
		$ancestor  = $a_ancestor;
 | 
						|
		$a_pieces  = explode( ET_THEME_BUILDER_SETTING_SEPARATOR, $a );
 | 
						|
		$b_pieces  = explode( ET_THEME_BUILDER_SETTING_SEPARATOR, $b );
 | 
						|
		$separator = preg_quote( ET_THEME_BUILDER_SETTING_SEPARATOR, '/' );
 | 
						|
 | 
						|
		// Hierarchical post types are a special case by spec since we have to take hierarchy into account.
 | 
						|
		// Test if the ancestor matches "singular:post_type:<post_type>:children:id:".
 | 
						|
		$id_pieces  = array( 'singular', 'post_type', '[^' . $separator . ']+', 'children', 'id', '' );
 | 
						|
		$term_regex = '/^' . implode( $separator, $id_pieces ) . '$/';
 | 
						|
 | 
						|
		if ( preg_match( $term_regex, $ancestor['id'] ) && is_post_type_hierarchical( $a_pieces[2] ) ) {
 | 
						|
			$a_post_id = (int) $a_pieces[5];
 | 
						|
			$b_post_id = (int) $b_pieces[5];
 | 
						|
 | 
						|
			$a_post_ancestors = get_post_ancestors( $a_post_id );
 | 
						|
			$b_post_ancestors = get_post_ancestors( $b_post_id );
 | 
						|
 | 
						|
			if ( in_array( $a_post_id, $b_post_ancestors, true ) ) {
 | 
						|
				// $b is a child of $a so it should take priority.
 | 
						|
				return $b;
 | 
						|
			}
 | 
						|
 | 
						|
			if ( in_array( $b_post_id, $a_post_ancestors, true ) ) {
 | 
						|
				// $a is a child of $b so it should take priority.
 | 
						|
				return $a;
 | 
						|
			}
 | 
						|
 | 
						|
			// neither $a nor $b is an ancestor to the other - continue the comparisons.
 | 
						|
		}
 | 
						|
 | 
						|
		// Term archive listings are a special case by spec since we have to take hierarchy into account.
 | 
						|
		// Test if the ancestor matches "archive:taxonomy:<taxonomy>:term:id:".
 | 
						|
		$id_pieces  = array( 'archive', 'taxonomy', '[^' . $separator . ']+', 'term', 'id', '' );
 | 
						|
		$term_regex = '/^' . implode( $separator, $id_pieces ) . '$/';
 | 
						|
 | 
						|
		if ( preg_match( $term_regex, $ancestor['id'] ) && is_taxonomy_hierarchical( $a_pieces[2] ) ) {
 | 
						|
			$a_term_id = $a_pieces[5];
 | 
						|
			$b_term_id = $b_pieces[5];
 | 
						|
 | 
						|
			if ( term_is_ancestor_of( $a_term_id, $b_term_id, $a_pieces[2] ) ) {
 | 
						|
				// $b is a child of $a so it should take priority.
 | 
						|
				return $b;
 | 
						|
			}
 | 
						|
 | 
						|
			if ( term_is_ancestor_of( $b_term_id, $a_term_id, $a_pieces[2] ) ) {
 | 
						|
				// $a is a child of $b so it should take priority.
 | 
						|
				return $a;
 | 
						|
			}
 | 
						|
 | 
						|
			// neither $a nor $b is an ancestor to the other - continue the comparisons.
 | 
						|
		}
 | 
						|
 | 
						|
		// Find the first difference in the settings and compare it.
 | 
						|
		// The difference should be representing an id or a slug.
 | 
						|
		foreach ( $a_pieces as $index => $a_piece ) {
 | 
						|
			$b_piece = $b_pieces[ $index ];
 | 
						|
 | 
						|
			if ( $b_piece === $a_piece ) {
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			if ( is_numeric( $a_piece ) ) {
 | 
						|
				$prioritized = (float) $a_piece <= (float) $b_piece ? $a : $b;
 | 
						|
			} else {
 | 
						|
				$prioritized = strcmp( $a, $b ) <= 0 ? $a : $b;
 | 
						|
			}
 | 
						|
 | 
						|
			/**
 | 
						|
			 * Filters the higher prioritized setting in a given pair that
 | 
						|
			 * has equal built-in priority.
 | 
						|
			 *
 | 
						|
			 * @since 4.2
 | 
						|
			 *
 | 
						|
			 * @param string $prioritized_setting
 | 
						|
			 * @param string $setting_a
 | 
						|
			 * @param string $setting_b
 | 
						|
			 * @param ET_Theme_Builder_Request $request
 | 
						|
			 */
 | 
						|
			return apply_filters( 'et_theme_builder_prioritized_template_setting', $prioritized, $a, $b, $this );
 | 
						|
		}
 | 
						|
 | 
						|
		// We should only reach this point if $a and $b are equal so it doesn't
 | 
						|
		// matter which we return.
 | 
						|
		return $a;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Check if this request fulfills a template setting.
 | 
						|
	 *
 | 
						|
	 * @since 4.0
 | 
						|
	 *
 | 
						|
	 * @param array  $flat_settings Flat settings.
 | 
						|
	 * @param string $setting_id    Setting ID.
 | 
						|
	 *
 | 
						|
	 * @return boolean
 | 
						|
	 */
 | 
						|
	protected function _fulfills_template_setting( $flat_settings, $setting_id ) {
 | 
						|
		$ancestor  = $this->_get_template_setting_ancestor( $flat_settings, $setting_id );
 | 
						|
		$fulfilled = false;
 | 
						|
 | 
						|
		if ( ! empty( $ancestor ) && isset( $ancestor['validate'] ) && is_callable( $ancestor['validate'] ) ) {
 | 
						|
			// @phpcs:ignore Generic.PHP.ForbiddenFunctions.Found
 | 
						|
			$fulfilled = call_user_func(
 | 
						|
				$ancestor['validate'],
 | 
						|
				$this->get_type(),
 | 
						|
				$this->get_subtype(),
 | 
						|
				$this->get_id(),
 | 
						|
				explode( ET_THEME_BUILDER_SETTING_SEPARATOR, $setting_id )
 | 
						|
			);
 | 
						|
		}
 | 
						|
 | 
						|
		return $fulfilled;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Reduce callback for self::get_template() to get the highest priority template from all applicable ones.
 | 
						|
	 *
 | 
						|
	 * @since 4.0
 | 
						|
	 *
 | 
						|
	 * @param array $carry
 | 
						|
	 * @param array $applicable_template
 | 
						|
	 *
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	public function reduce_get_template( $carry, $applicable_template ) {
 | 
						|
		global $__et_theme_builder_request_flat_settings;
 | 
						|
 | 
						|
		if ( empty( $carry ) ) {
 | 
						|
			return $applicable_template;
 | 
						|
		}
 | 
						|
 | 
						|
		$higher = $this->_get_higher_priority_template_setting(
 | 
						|
			$__et_theme_builder_request_flat_settings,
 | 
						|
			$carry['top_setting_id'],
 | 
						|
			$applicable_template['top_setting_id']
 | 
						|
		);
 | 
						|
 | 
						|
		return $carry['top_setting_id'] !== $higher ? $applicable_template : $carry;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get the highest-priority template that should be applied for this request, if any.
 | 
						|
	 *
 | 
						|
	 * @since 4.0
 | 
						|
	 *
 | 
						|
	 * @param array $templates
 | 
						|
	 * @param array $flat_settings
 | 
						|
	 *
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	public function get_template( $templates, $flat_settings ) {
 | 
						|
		// Use a global variable to pass data to the reduce callback as we support PHP 5.2.
 | 
						|
		global $__et_theme_builder_request_flat_settings;
 | 
						|
 | 
						|
		$applicable_templates = array();
 | 
						|
 | 
						|
		foreach ( $templates as $template ) {
 | 
						|
			if ( ! $template['enabled'] ) {
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			foreach ( $template['exclude_from'] as $setting_id ) {
 | 
						|
				if ( $this->_fulfills_template_setting( $flat_settings, $setting_id ) ) {
 | 
						|
					// The setting is explicitly excluded - bail from testing the template any further.
 | 
						|
					continue 2;
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			$highest_priority = '';
 | 
						|
 | 
						|
			foreach ( $template['use_on'] as $setting_id ) {
 | 
						|
				if ( $this->_fulfills_template_setting( $flat_settings, $setting_id ) ) {
 | 
						|
					$highest_priority = $this->_get_higher_priority_template_setting( $flat_settings, $highest_priority, $setting_id );
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if ( '' !== $highest_priority ) {
 | 
						|
				$applicable_templates[] = array(
 | 
						|
					'template'       => $template,
 | 
						|
					'top_setting_id' => $highest_priority,
 | 
						|
				);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		$__et_theme_builder_request_flat_settings = $flat_settings;
 | 
						|
		$applicable_template                      = array_reduce( $applicable_templates, array( $this, 'reduce_get_template' ), array() );
 | 
						|
		$__et_theme_builder_request_flat_settings = array();
 | 
						|
 | 
						|
		if ( ! empty( $applicable_template ) ) {
 | 
						|
			// Found the highest priority applicable template - return it.
 | 
						|
			return $applicable_template['template'];
 | 
						|
		}
 | 
						|
 | 
						|
		$default_templates = et_()->array_pick( $templates, array( 'default' => true ) );
 | 
						|
 | 
						|
		if ( ! empty( $default_templates ) ) {
 | 
						|
			$default_template = $default_templates[0];
 | 
						|
 | 
						|
			if ( $default_template['enabled'] ) {
 | 
						|
				// Return the first default template. We don't expect there to be multiple ones but
 | 
						|
				// it is technically possible with direct database edits, for example.
 | 
						|
				return $default_template;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// No templates found at all - probably never used the Theme Builder.
 | 
						|
		return array();
 | 
						|
	}
 | 
						|
}
 |