This repository has been archived on 2022-06-23. You can view files and clone it, but cannot push or open issues or pull requests.
divi/includes/builder/frontend-builder/theme-builder/ThemeBuilderRequest.php
2021-12-07 11:08:05 +00:00

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();
}
}