version 4.13.0

This commit is contained in:
2021-12-07 11:08:05 +00:00
commit cb26d2c0c4
1285 changed files with 254735 additions and 0 deletions

View File

@ -0,0 +1,26 @@
<?php
class ET_Theme_Builder_Api_Errors {
const UNKNOWN = 'unknown';
const PORTABILITY_INCORRECT_CONTEXT = 'incorrect_context';
const PORTABILITY_REQUIRE_INCOMING_LAYOUT_DUPLICATE_DECISION = 'require_incoming_layout_duplicate_decision';
const PORTABILITY_IMPORT_PRESETS_FAILURE = 'import_presets_failure';
const PORTABILITY_IMPORT_INVALID_FILE = 'invalid_file';
/**
* Get map of all error codes.
*
* @since 4.0
*
* @return string[]
*/
public static function getMap() {
return array(
'unknown' => self::UNKNOWN,
'portabilityIncorrectContext' => self::PORTABILITY_INCORRECT_CONTEXT,
'portabilityRequireIncomingLayoutDuplicateDecision' => self::PORTABILITY_REQUIRE_INCOMING_LAYOUT_DUPLICATE_DECISION,
'portabilityImportPresetsFailure' => self::PORTABILITY_IMPORT_PRESETS_FAILURE,
'portabilityImportInvalidFile' => self::PORTABILITY_IMPORT_INVALID_FILE,
);
}
}

View File

@ -0,0 +1,444 @@
<?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();
}
}

View File

@ -0,0 +1,327 @@
<?php
/**
* Class ET_Theme_Builder_Woocommerce_Product_Variable_Placeholder
*
* Variable product class extension for displaying WooCommerce placeholder on Theme Builder
*/
class ET_Theme_Builder_Woocommerce_Product_Variable_Placeholder extends WC_Product_Variable {
/**
* Cached upsells id
*
* @since 4.0.10
*
* @var array
*/
protected static $tb_upsells_id;
/**
* Cached product category ids
*
* @since 4.0.10
*
* @var array
*/
protected static $tb_category_ids;
/**
* Cached product tag ids
*
* @since 4.0.10
*
* @var array
*/
protected static $tb_tag_ids;
/**
* Cached attributes
*
* @since 4.0.10
*
* @var array
*/
protected static $tb_attributes;
/**
* Create pre-filled WC Product (variable) object which acts as placeholder generator in TB
*
* @since 4.0.10 Instead of empty product object that is set later, pre-filled default data properties
*
* @param int|WC_Product|object $product Product to init.
*/
public function __construct( $product = 0 ) {
// Pre-filled default data with placeholder value so everytime this product class is
// initialized, it already has sufficient data to be displayed on Theme Builder
$this->data = array(
'name' => esc_html( 'Product name', 'et_builder' ),
'slug' => 'product-name',
'date_created' => current_time( 'timestamp' ),
'date_modified' => null,
'status' => 'publish',
'featured' => false,
'catalog_visibility' => 'visible',
'description' => esc_html( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris bibendum eget dui sed vehicula. Suspendisse potenti. Nam dignissim at elit non lobortis. Cras sagittis dui diam, a finibus nibh euismod vestibulum. Integer sed blandit felis. Maecenas commodo ante in mi ultricies euismod. Morbi condimentum interdum luctus. Mauris iaculis interdum risus in volutpat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Praesent cursus odio eget cursus pharetra. Aliquam lacinia lectus a nibh ullamcorper maximus. Quisque at sapien pulvinar, dictum elit a, bibendum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Mauris non pellentesque urna.', 'et_builder' ),
'short_description' => esc_html( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris bibendum eget dui sed vehicula. Suspendisse potenti. Nam dignissim at elit non lobortis.', 'et_builder' ),
'sku' => 'product-name',
'price' => '75',
'regular_price' => '80',
'sale_price' => '65',
'date_on_sale_from' => null,
'date_on_sale_to' => null,
'total_sales' => '0',
'tax_status' => 'taxable',
'tax_class' => '',
'manage_stock' => true,
'stock_quantity' => 50,
'stock_status' => 'instock',
'backorders' => 'no',
'low_stock_amount' => 2,
'sold_individually' => false,
'weight' => 2,
'length' => '',
'width' => 2,
'height' => 2,
'upsell_ids' => array(),
'cross_sell_ids' => array(),
'parent_id' => 0,
'reviews_allowed' => true,
'purchase_note' => '',
'attributes' => array(),
'default_attributes' => array(),
'menu_order' => 0,
'post_password' => '',
'virtual' => false,
'downloadable' => false,
'category_ids' => array(),
'tag_ids' => array(),
'shipping_class_id' => 0,
'downloads' => array(),
'image_id' => '',
'gallery_image_ids' => array(),
'download_limit' => -1,
'download_expiry' => -1,
'rating_counts' => array(
4 => 2,
),
'average_rating' => '4.00',
'review_count' => 2,
'recent_product_ids' => null,
);
parent::__construct( $product );
}
/**
* Get internal type.
* Define custom internal type so custom data store can be used to bypass database value retrieval
*
* @since 4.0.10
*
* @return string
*/
public function get_type() {
return 'tb-placeholder';
}
/**
* Get placeholder product as available variation. The method is basically identical to
* `WC_Product_Variable->get_available_variation()` except for the checks which are removed
* so placeholder value can be passed
*
* @since 4.3.3
*
* @param int|object $variation not needed since it will be overwritten by placeholder variation
* but it needs to be kept for compatibility with base class' method
*
* @return array
*/
function get_available_variation( $variation = 0 ) {
$variation = new ET_Theme_Builder_Woocommerce_Product_Variation_Placeholder();
$show_variation_price = apply_filters( 'woocommerce_show_variation_price', $variation->get_price() === '' || $this->get_variation_sale_price( 'min' ) !== $this->get_variation_sale_price( 'max' ) || $this->get_variation_regular_price( 'min' ) !== $this->get_variation_regular_price( 'max' ), $this, $variation );
// Set variation id; Prevent $product->get_id() returns falsey which usually triggers wc_product_get()
// in WC add ons; Valid $product->get_id() makes global $product being used most of the time
$variation->set_id( $this->get_id() );
// Set current product id as variation parent id so $product->get_parent_id() returns
// valid value (mostly when being called by WC add-ons). The absence of this value (in TB)
// triggers new `wc_get_product()` which most likely returned unwanted output
$variation->set_prop( 'parent_id', $this->get_id() );
// Returned array properties are identical to `WC_Product_Variable->get_available_variation()`
return apply_filters(
'woocommerce_available_variation',
array(
'attributes' => $variation->get_variation_attributes(),
'availability_html' => wc_get_stock_html( $variation ),
'backorders_allowed' => $variation->backorders_allowed(),
'dimensions' => $variation->get_dimensions( false ),
'dimensions_html' => wc_format_dimensions( $variation->get_dimensions( false ) ),
'display_price' => wc_get_price_to_display( $variation ),
'display_regular_price' => wc_get_price_to_display( $variation, array( 'price' => $variation->get_regular_price() ) ),
'image' => wc_get_product_attachment_props( $variation->get_image_id() ),
'image_id' => $variation->get_image_id(),
'is_downloadable' => $variation->is_downloadable(),
'is_in_stock' => $variation->is_in_stock(),
'is_purchasable' => $variation->is_purchasable(),
'is_sold_individually' => $variation->is_sold_individually() ? 'yes' : 'no',
'is_virtual' => $variation->is_virtual(),
'max_qty' => 0 < $variation->get_max_purchase_quantity() ? $variation->get_max_purchase_quantity() : '',
'min_qty' => $variation->get_min_purchase_quantity(),
'price_html' => $show_variation_price ? '<span class="price">' . $variation->get_price_html() . '</span>' : '',
'sku' => $variation->get_sku(),
'variation_description' => wc_format_content( $variation->get_description() ),
'variation_id' => $variation->get_id(),
'variation_is_active' => $variation->variation_is_active(),
'variation_is_visible' => $variation->variation_is_visible(),
'weight' => $variation->get_weight(),
'weight_html' => wc_format_weight( $variation->get_weight() ),
),
$this,
$variation
);
}
/**
* Add to cart's <select> requires variable product type and get_available_variations() method
* outputting product->children value. Filtering get_available_variations() can't be done so
* extending WC_Product_Variable and set fixed value for get_available_variations() method
*
* @since 4.5.7 Introduced $return arg to fix compatibility issue {@link https://github.com/elegantthemes/Divi/issues/20985}
* @since 4.3.3 `Replaced ET_Theme_Builder_Woocommerce_Product_Variable_Placeholder` with
* `ET_Theme_Builder_Woocommerce_Product_Variation_Placeholder` (which is now
* called at `get_available_variations()` method and similar to
* `WC_Product_Variation`'s method with no check). It has all variation-required
* methods and properties which makes it more reliable when WC add-ons are used
* @since 4.0.1
*
* @return array
*/
public function get_available_variations( $return = 'array' ) {
return array(
$this->get_available_variation(),
);
}
/**
* Display Divi's placeholder image in WC image in TB
*
* @since 4.0.10
*
* @param string not used but need to be declared to prevent incompatible declaration error
* @param array not used but need to be declared to prevent incompatible declaration error
* @param bool not used but need to be declared to prevent incompatible declaration error
*
* @return string
*/
public function get_image( $size = 'woocommerce_thumbnail', $attr = array(), $placeholder = true ) {
return et_builder_wc_placeholder_img();
}
/**
* Set product upsells id for TB's woocommerceComponent. This can't be called during class
* initialization and need to be called BEFORE `woocommerce_product_class` filter callback
* to avoid infinite loop
*
* @since 4.0.10
*
* @param array $args
*/
public static function set_tb_upsells_ids( $args = array() ) {
$defaults = array(
'limit' => 4,
);
$args = wp_parse_args( $args, $defaults );
// Get recent products for upsells product; Any product will do since its purpose is
// for visual preview only
$recent_products_query = new WC_Product_Query( $args );
$recent_product_ids = array();
foreach ( $recent_products_query->get_products() as $recent_product ) {
$recent_product_ids[] = $recent_product->get_id();
}
// Set up upsells id product
self::$tb_upsells_id = $recent_product_ids;
}
/**
* Get upsells id
*
* @since 4.0.10
*
* @param string not used but need to be declared to prevent incompatible declaration error
*
* @return array
*/
public function get_upsell_ids( $context = 'view' ) {
// Bypass database value retrieval and simply pulled cached value from property
return is_array( self::$tb_upsells_id ) ? self::$tb_upsells_id : array();
}
/**
* Get attributes
*
* @since 4.0.10
*
* @param string not used but need to be declared to prevent incompatible declaration error
*
* @return array
*/
public function get_attributes( $context = 'view' ) {
if ( ! is_null( self::$tb_attributes ) ) {
return self::$tb_attributes;
}
// Initialize color attribute
$colors = new WC_Product_Attribute();
$colors->set_id( 1 );
$colors->set_name( 'color' );
$colors->set_options( array( 'Black', 'White', 'Gray' ) );
$colors->set_position( 1 );
$colors->set_visible( 1 );
$colors->set_variation( 1 );
// Initialize size attribute
$sizes = new WC_Product_Attribute();
$sizes->set_id( 2 );
$sizes->set_name( 'size' );
$sizes->set_options( array( 'S', 'M', 'L', 'XL' ) );
$sizes->set_position( 1 );
$sizes->set_visible( 1 );
$sizes->set_variation( 1 );
self::$tb_attributes = array(
'pa_color' => $colors,
'pa_size' => $sizes,
);
return self::$tb_attributes;
}
/**
* Get variation price
*
* @since 4.0.10
*
* @param bool not used but need to be declared to prevent incompatible declaration error
*
* @return array
*/
public function get_variation_prices( $for_display = false ) {
return array(
'price' => array( $this->data['price'] ),
'regular_price' => array( $this->data['regular_price'] ),
'sale_price' => array( $this->data['sale_price'] ),
);
}
}
/**
* Render default product variable add to cart UI for tb-placeholder product type
*
* @since 4.0.10
*/
add_action( 'woocommerce_tb-placeholder_add_to_cart', 'woocommerce_variable_add_to_cart', 30 );

View File

@ -0,0 +1,53 @@
<?php
/**
* Register data store for ET_Theme_Builder_Woocommerce_Product_Variable_Placeholder_Data_Store_CPT
* which aims to bypass database value retrieval and simply returns default value as placeholder
*
* @since 4.0.10
*/
class ET_Theme_Builder_Woocommerce_Product_Variable_Placeholder_Data_Store_CPT extends WC_Product_Variable_Data_Store_CPT implements WC_Object_Data_Store_Interface, WC_Product_Variable_Data_Store_Interface {
/**
* Basically the original read() method with one exception: retruns default value (which is
* placeholder value) and remove all database value retrieval mechanism so any add-ons
* on TB refers to TB placeholder product data
*
* @since 4.0.10
*
* @param WC_Product $product Product object.
*/
public function read( &$product ) {
$product->set_defaults();
}
/**
* Register product type data store
*
* @since 4.0.10
* @since 4.3.3 register the store for tb-placeholder-variation product
*
* @param array $stores
*
* @return array
*/
public static function register_store( $stores ) {
$stores['product-tb-placeholder'] = 'ET_Theme_Builder_Woocommerce_Product_Variable_Placeholder_Data_Store_CPT';
// Placeholder variation store requirement is identical to placeholder variable, which is
// loading defaults as value and skipping database value retrieval; thus best keep thing
// simple and reuse it for tb-placeholder-variation
$stores['product-tb-placeholder-variation'] = 'ET_Theme_Builder_Woocommerce_Product_Variable_Placeholder_Data_Store_CPT';
return $stores;
}
}
/**
* Register product tb-placeholder's store
*
* @since 4.0.10
*/
add_filter(
'woocommerce_data_stores',
array( 'ET_Theme_Builder_Woocommerce_Product_Variable_Placeholder_Data_Store_CPT', 'register_store' )
);

View File

@ -0,0 +1,21 @@
<?php
/**
* Class ET_Theme_Builder_Woocommerce_Product_Variation_Placeholder
*
* Display variation (child of variable) placeholder product on Theme Builder. This needs to be
* explicitly defined in case WC add-ons relies on any of variation's method.
*/
class ET_Theme_Builder_Woocommerce_Product_Variation_Placeholder extends WC_Product_Variation {
/**
* Get internal type.
* Define custom internal type so custom data store can be used to bypass database value retrieval
*
* @since 4.3.3
*
* @return string
*/
public function get_type() {
return 'tb-placeholder-variation';
}
}

View File

@ -0,0 +1,208 @@
<?php
/**
* Load portability used in the Theme Builder admin page.
*
* @since 4.0
*
* @return void
*/
function et_theme_builder_load_portability() {
if ( ! et_pb_is_allowed( 'theme_builder' ) ) {
return;
}
et_core_load_component( 'portability' );
et_core_portability_register(
'et_theme_builder',
array(
'name' => esc_html__( 'Divi Theme Builder', 'et_builder' ),
'type' => 'theme_builder',
'view' => 'et_theme_builder' === et_()->array_get( $_GET, 'page' ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- No need to use nonce.
)
);
}
add_action( 'admin_init', 'et_theme_builder_load_portability' );
/**
* Register the Theme Builder admin page.
*
* @since 4.0
*
* @param string $parent
*
* @return void
*/
function et_theme_builder_add_admin_page( $parent ) {
if ( ! et_pb_is_allowed( 'theme_builder' ) ) {
return;
}
// We register the page with the 'edit_others_posts' capability since it's the lowest
// requirement to use VB and we already checked for the theme_builder ET cap.
add_submenu_page(
$parent,
esc_html__( 'Theme Builder', 'et_builder' ),
esc_html__( 'Theme Builder', 'et_builder' ),
'edit_others_posts',
'et_theme_builder',
'et_theme_builder_admin_page'
);
}
/**
* Enqueue Theme Builder assets.
*
* @since 4.0
*
* @return void
*/
function et_theme_builder_enqueue_scripts() {
if ( ! et_builder_is_tb_admin_screen() ) {
return;
}
$role_capabilities = et_pb_get_role_settings();
$user_role = et_pb_get_current_user_role();
et_builder_enqueue_open_sans();
et_fb_enqueue_bundle( 'et-theme-builder', 'theme-builder.css', array( 'et-core-admin' ) );
et_builder_enqueue_assets_head();
et_builder_enqueue_assets_main();
global $wp_version;
$ver = ET_BUILDER_VERSION;
$root = ET_BUILDER_URI;
if ( version_compare( substr( $wp_version, 0, 3 ), '4.5', '<' ) ) {
$dep = array( 'jquery-ui-compat' );
wp_register_script( 'jquery-ui-compat', "{$root}/scripts/ext/jquery-ui-1.10.4.custom.min.js", array( 'jquery' ), $ver, true );
} else {
$dep = array( 'jquery-ui-datepicker' );
}
wp_register_script( 'jquery-ui-datepicker-addon', "{$root}/scripts/ext/jquery-ui-timepicker-addon.js", $dep, $ver, true );
wp_register_script( 'react-tiny-mce', "{$root}/frontend-builder/assets/vendors/tinymce.min.js" );
$asset_ver = ET_BUILDER_VERSION;
$frame_helpers_id = 'et-frame-helpers';
$frame_helpers_path = ET_BUILDER_DIR . '/frontend-builder/build/frame-helpers.js';
$frame_helpers_url = ET_BUILDER_URI . '/frontend-builder/build/frame-helpers.js';
if ( ! file_exists( $frame_helpers_path ) ) {
// Load "hot" from webpack-dev-server.
$site_url = wp_parse_url( get_site_url() );
$frame_helpers_url = "{$site_url['scheme']}://{$site_url['host']}:31495/frame-helpers.js";
}
wp_register_script( $frame_helpers_id, $frame_helpers_url, array(), $asset_ver );
$asset_id = 'et-theme-builder';
$asset_path = ET_BUILDER_DIR . '/frontend-builder/build/theme-builder.js';
$asset_uri = ET_BUILDER_URI . '/frontend-builder/build/theme-builder.js';
$dependencies = array(
'jquery',
'jquery-ui-sortable',
'jquery-ui-datepicker-addon',
'react',
'react-dom',
'react-tiny-mce',
'et-core-admin',
'wp-hooks',
'et-frame-helpers',
);
if ( ! wp_script_is( 'wp-hooks', 'registered' ) ) {
// Use bundled wp-hooks script when WP < 5.0
wp_enqueue_script( 'wp-hooks', ET_BUILDER_URI . '/frontend-builder/assets/backports/hooks.js', array(), $asset_ver, false );
}
et_fb_enqueue_react();
if ( ! file_exists( $asset_path ) ) {
// Load "hot" from webpack-dev-server.
$site_url = wp_parse_url( get_site_url() );
$asset_uri = "{$site_url['scheme']}://{$site_url['host']}:31495/theme-builder.js";
}
wp_enqueue_script( $asset_id, $asset_uri, $dependencies, $asset_ver, true );
// Strip 'validate' key from settings as it is used server-side only.
$default_settings = et_theme_builder_get_template_settings_options();
foreach ( $default_settings as $group_key => $group ) {
foreach ( $group['settings'] as $setting_key => $setting ) {
unset( $default_settings[ $group_key ]['settings'][ $setting_key ]['validate'] );
}
}
$preloaded_settings = et_theme_builder_get_template_settings_options_for_preloading();
foreach ( $preloaded_settings as $setting_key => $setting ) {
unset( $preloaded_settings[ $setting_key ]['validate'] );
}
$preferences = et_fb_app_preferences();
$animation = et_()->array_get( $preferences, 'builder_animation.value', 'true' );
$animation = true === $animation || 'true' === $animation;
$i18n = require ET_BUILDER_DIR . 'frontend-builder/i18n.php';
wp_localize_script(
'et-theme-builder',
'et_theme_builder_bundle',
array(
'config' => array(
'distPath' => ET_BUILDER_URI . '/frontend-builder/build/',
'api' => admin_url( 'admin-ajax.php' ),
'apiErrors' => ET_Theme_Builder_Api_Errors::getMap(),
'diviLibraryUrl' => ET_BUILDER_DIVI_LIBRARY_URL,
'diviLibraryCustomTabs' => apply_filters( 'et_builder_library_modal_custom_tabs', array(), 'theme-builder' ),
'nonces' => array(
'et_builder_library_get_layouts_data' => wp_create_nonce( 'et_builder_library_get_layouts_data' ),
'et_theme_builder_api_duplicate_layout' => wp_create_nonce( 'et_theme_builder_api_duplicate_layout' ),
'et_theme_builder_api_create_layout' => wp_create_nonce( 'et_theme_builder_api_create_layout' ),
'et_theme_builder_api_get_layout_url' => wp_create_nonce( 'et_theme_builder_api_get_layout_url' ),
'et_theme_builder_api_save' => wp_create_nonce( 'et_theme_builder_api_save' ),
'et_theme_builder_api_drop_autosave' => wp_create_nonce( 'et_theme_builder_api_drop_autosave' ),
'et_theme_builder_api_get_template_settings' => wp_create_nonce( 'et_theme_builder_api_get_template_settings' ),
'et_theme_builder_api_reset' => wp_create_nonce( 'et_theme_builder_api_reset' ),
'et_theme_builder_api_export_theme_builder' => wp_create_nonce( 'et_theme_builder_api_export_theme_builder' ),
'et_theme_builder_api_import_theme_builder' => wp_create_nonce( 'et_theme_builder_api_import_theme_builder' ),
'et_builder_library_update_account' => wp_create_nonce( 'et_builder_library_update_account' ),
),
'rtl' => is_rtl(),
'animation' => $animation,
'templateSettings' => array(
'default' => $default_settings,
'preloaded' => $preloaded_settings,
),
'etAccount' => et_core_get_et_account(),
'capabilities' => isset( $role_capabilities[ $user_role ] ) ? $role_capabilities[ $user_role ] : array(),
'templates' => array(
'hasDraft' => 0 !== et_theme_builder_get_theme_builder_post_id( false, false ),
'live' => et_theme_builder_get_theme_builder_templates( true ),
'draft' => et_theme_builder_get_theme_builder_templates( false ),
),
),
'i18n' => array(
'generic' => $i18n['generic'],
'portability' => $i18n['portability'],
'library' => $i18n['library'],
'themeBuilder' => $i18n['themeBuilder'],
),
)
);
}
add_action( 'admin_enqueue_scripts', 'et_theme_builder_enqueue_scripts' );
/**
* Render the Theme Builder admin page.
*
* @since 4.0
*
* @return void
*/
function et_theme_builder_admin_page() {
echo '<div id="et-theme-builder"></div>';
}

View File

@ -0,0 +1,851 @@
<?php
/**
* Create a new layout.
*
* @since 4.0
*
* @return void
*/
function et_theme_builder_api_create_layout() {
et_builder_security_check( 'theme_builder', 'edit_others_posts', 'et_theme_builder_api_create_layout', 'nonce' );
$layout_type = isset( $_POST['layout_type'] ) ? sanitize_text_field( $_POST['layout_type'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- No need to use nonce.
$post_type = et_theme_builder_get_valid_layout_post_type( $layout_type );
if ( '' === $post_type ) {
wp_send_json_error(
array(
'message' => 'Invalid layout type: ' . $layout_type,
)
);
}
$post_id = et_theme_builder_insert_layout(
array(
'post_type' => $post_type,
)
);
if ( is_wp_error( $post_id ) ) {
wp_send_json_error(
array(
'message' => 'Failed to create layout.',
)
);
}
wp_send_json_success(
array(
'id' => $post_id,
)
);
}
add_action( 'wp_ajax_et_theme_builder_api_create_layout', 'et_theme_builder_api_create_layout' );
/**
* Duplicate a layout.
*
* @since 4.0
*
* @return void
*/
function et_theme_builder_api_duplicate_layout() {
et_builder_security_check( 'theme_builder', 'edit_others_posts', 'et_theme_builder_api_duplicate_layout', 'nonce' );
$layout_type = isset( $_POST['layout_type'] ) ? sanitize_text_field( $_POST['layout_type'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- No need to use nonce.
$post_type = et_theme_builder_get_valid_layout_post_type( $layout_type );
$layout_id = isset( $_POST['layout_id'] ) ? (int) $_POST['layout_id'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- No need to use nonce.
$layout = get_post( $layout_id );
if ( ! $layout ) {
wp_send_json_error(
array(
'message' => 'Failed to duplicate layout.',
)
);
}
$post_id = et_theme_builder_insert_layout(
array(
'post_type' => '' !== $post_type ? $post_type : $layout->post_type,
'post_status' => $layout->post_status,
'post_title' => $layout->post_title,
'post_content' => $layout->post_content,
)
);
if ( is_wp_error( $post_id ) ) {
wp_send_json_error(
array(
'message' => 'Failed to duplicate layout.',
)
);
}
$meta = et_core_get_post_builder_meta( $layout_id );
foreach ( $meta as $entry ) {
update_post_meta( $post_id, $entry['key'], $entry['value'] );
}
wp_send_json_success(
array(
'id' => $post_id,
)
);
}
add_action( 'wp_ajax_et_theme_builder_api_duplicate_layout', 'et_theme_builder_api_duplicate_layout' );
/**
* Get layout url.
*
* @since 4.0
*
* @return void
*/
function et_theme_builder_api_get_layout_url() {
et_builder_security_check( 'theme_builder', 'edit_others_posts', 'et_theme_builder_api_get_layout_url', 'nonce' );
$layout_id = isset( $_POST['layout_id'] ) ? (int) $_POST['layout_id'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- No need to use nonce.
$layout = get_post( $layout_id );
if ( ! $layout ) {
wp_send_json_error(
array(
'message' => 'Failed to load layout.',
)
);
}
$edit_url = add_query_arg( 'et_tb', '1', et_fb_get_builder_url( get_permalink( $layout_id ) ) );
// If Admin is SSL but FE is not, we need to fix VB url or it won't work
// because trying to load insecure resource.
$edit_url = set_url_scheme( $edit_url, is_ssl() ? 'https' : 'http' );
wp_send_json_success(
array(
'editUrl' => $edit_url,
)
);
}
add_action( 'wp_ajax_et_theme_builder_api_get_layout_url', 'et_theme_builder_api_get_layout_url' );
/**
* Save the theme builder post.
*
* The templates upload will be chunked into several POST requests with size 30 templates per request.
* Hence we need to store the uploaded templates data into temporary file in cache directory before
* making changes into database.
*
* @since 4.0
*
* @return void
*/
function et_theme_builder_api_save() {
et_builder_security_check( 'theme_builder', 'edit_others_posts', 'et_theme_builder_api_save', 'nonce' );
$_ = et_();
$live = '1' === $_->array_get( $_POST, 'live', '0' );
$first_request = '1' === $_->array_get( $_POST, 'first_request', '1' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is done in `et_builder_security_check`.
$last_request = '1' === $_->array_get( $_POST, 'last_request', '1' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is done in `et_builder_security_check`.
$templates = wp_unslash( $_->array_get( $_POST, 'templates', array() ) );
$theme_builder_id = et_theme_builder_get_theme_builder_post_id( $live, true );
$has_default = false;
$updated_ids = array();
// Always reset the cached templates on first request.
if ( $first_request ) {
ET_Core_Cache_File::set( 'et_theme_builder_templates', array() );
}
$cached_templates = ET_Core_Cache_File::get( 'et_theme_builder_templates' );
// Populate the templates.
foreach ( $templates as $index => $template ) {
$cached_templates[ $_->array_get( $template, 'id', 'unsaved_' . $index ) ] = $template;
}
// Store the populated templates data into the cache file.
ET_Core_Cache_File::set( 'et_theme_builder_templates', $cached_templates );
if ( $last_request ) {
$affected_templates = array();
// Update or insert templates.
foreach ( $cached_templates as $template ) {
$raw_post_id = $_->array_get( $template, 'id', 0 );
$post_id = is_numeric( $raw_post_id ) ? (int) $raw_post_id : 0;
$new_post_id = et_theme_builder_store_template( $theme_builder_id, $template, ! $has_default );
if ( ! $new_post_id ) {
continue;
}
$is_default = get_post_meta( $new_post_id, '_et_default', true ) === '1';
if ( $is_default ) {
$has_default = true;
}
// Add template ID into $affected_templates for later use
// to Add mapping template ID to theme builder ID
// and delete existing template mapping.
$affected_templates[ $new_post_id ] = array(
'raw' => $raw_post_id,
'normalized' => $post_id,
);
}
$existing_templates = get_post_meta( $theme_builder_id, '_et_template', false );
if ( $existing_templates ) {
// Store existing template mapping as backup to avoid data lost
// when user interrupting the saving process before completed.
update_option( 'et_tb_templates_backup_' . $theme_builder_id, $existing_templates );
}
// Delete existing template mapping.
delete_post_meta( $theme_builder_id, '_et_template' );
// Insert new template mapping.
foreach ( $affected_templates as $template_id => $template_pair ) {
add_post_meta( $theme_builder_id, '_et_template', $template_id );
if ( $template_pair['normalized'] !== $template_id ) {
$updated_ids[ $template_pair['raw'] ] = $template_id;
}
}
// Delete existing template mapping backup.
delete_option( 'et_tb_templates_backup_' . $theme_builder_id );
if ( $live ) {
et_theme_builder_trash_draft_and_unused_posts();
}
et_theme_builder_clear_wp_cache( 'all' );
// Always reset the cached templates on last request after data stored into database.
ET_Core_Cache_File::set( 'et_theme_builder_templates', array() );
// Remove static resources on save. It's necessary because how we are generating the dynamic assets for the TB.
ET_Core_PageResource::remove_static_resources( 'all', 'all', false, 'dynamic' );
}
wp_send_json_success(
array(
'updatedTemplateIds' => (object) $updated_ids,
)
);
}
add_action( 'wp_ajax_et_theme_builder_api_save', 'et_theme_builder_api_save' );
/**
* Drop the theme builder post autosave.
*
* @since 4.0
*
* @return void
*/
function et_theme_builder_api_drop_autosave() {
et_builder_security_check( 'theme_builder', 'edit_others_posts', 'et_theme_builder_api_drop_autosave', 'nonce' );
et_theme_builder_trash_draft_and_unused_posts();
wp_send_json_success();
}
add_action( 'wp_ajax_et_theme_builder_api_drop_autosave', 'et_theme_builder_api_drop_autosave' );
function et_theme_builder_api_get_template_settings() {
et_builder_security_check( 'theme_builder', 'edit_others_posts', 'et_theme_builder_api_get_template_settings', 'nonce', '_GET' );
$parent = isset( $_GET['parent'] ) ? sanitize_text_field( $_GET['parent'] ) : '';
$search = isset( $_GET['search'] ) ? sanitize_text_field( $_GET['search'] ) : '';
$page = isset( $_GET['page'] ) ? (int) $_GET['page'] : 1;
$page = $page >= 1 ? $page : 1;
$per_page = 30;
$settings = et_theme_builder_get_flat_template_settings_options();
if ( ! isset( $settings[ $parent ] ) || empty( $settings[ $parent ]['options'] ) ) {
wp_send_json_error(
array(
'message' => __( 'Invalid parent setting specified.', 'et_builder' ),
)
);
}
$setting = $settings[ $parent ];
$results = et_theme_builder_get_template_setting_child_options( $setting, array(), $search, $page, $per_page );
wp_send_json_success(
array(
'results' => array_values( $results ),
)
);
}
add_action( 'wp_ajax_et_theme_builder_api_get_template_settings', 'et_theme_builder_api_get_template_settings' );
function et_theme_builder_api_reset() {
et_builder_security_check( 'theme_builder', 'edit_others_posts', 'et_theme_builder_api_reset', 'nonce' );
$live_id = et_theme_builder_get_theme_builder_post_id( true, false );
if ( $live_id > 0 ) {
wp_trash_post( $live_id );
// Reset cache when theme builder is reset.
ET_Core_PageResource::remove_static_resources( 'all', 'all', true );
}
et_theme_builder_trash_draft_and_unused_posts();
wp_send_json_success();
}
add_action( 'wp_ajax_et_theme_builder_api_reset', 'et_theme_builder_api_reset' );
function et_theme_builder_api_export_theme_builder() {
if ( ! et_pb_is_allowed( 'theme_builder' ) ) {
wp_send_json_error();
}
et_builder_security_check(
'et_theme_builder_portability',
et_core_portability_cap( 'et_theme_builder' ),
'et_theme_builder_api_export_theme_builder',
'nonce'
);
$_ = et_();
$raw_templates = wp_unslash( $_->array_get( $_POST, 'templates', array() ) );
$global_layouts = array(
'header' => (int) $_->array_get( $_POST, 'global_layouts.header', 0 ),
'body' => (int) $_->array_get( $_POST, 'global_layouts.body', 0 ),
'footer' => (int) $_->array_get( $_POST, 'global_layouts.footer', 0 ),
);
$has_default = false;
$steps = array();
foreach ( $raw_templates as $template ) {
$is_default = ! $has_default && '1' === $_->array_get( $template, 'default', '0' );
if ( $is_default ) {
$has_default = true;
}
$sanitized = et_theme_builder_sanitize_template(
array_merge(
$template,
array(
'default' => $is_default ? '1' : '0',
)
)
);
$steps[] = array(
'type' => 'template',
'data' => $sanitized,
);
$layout_keys = array( 'header', 'body', 'footer' );
foreach ( $layout_keys as $key ) {
$layout_id = (int) $_->array_get( $sanitized, array( 'layouts', $key, 'id' ), 0 );
if ( 0 === $layout_id ) {
continue;
}
$steps[] = array(
'type' => 'layout',
'data' => array(
'post_id' => $layout_id,
'is_global' => $layout_id === $global_layouts[ $key ],
),
);
}
}
$presets_manager = ET_Builder_Global_Presets_Settings::instance();
$presets = $presets_manager->get_global_presets();
if ( ! empty( $presets ) ) {
$steps[] = array(
'type' => 'presets',
'data' => $presets,
);
}
$id = md5( get_current_user_id() . '_' . uniqid( 'et_theme_builder_export_', true ) );
$transient = 'et_theme_builder_export_' . get_current_user_id() . '_' . $id;
set_transient(
$transient,
array(
'ready' => false,
'steps' => $steps,
'temp_file' => '',
'temp_file_id' => '',
),
60 * 60 * 24
);
wp_send_json_success(
array(
'id' => $id,
'steps' => count( $steps ),
)
);
}
add_action( 'wp_ajax_et_theme_builder_api_export_theme_builder', 'et_theme_builder_api_export_theme_builder' );
function et_theme_builder_api_export_theme_builder_step() {
if ( ! et_pb_is_allowed( 'theme_builder' ) ) {
wp_send_json_error();
}
et_builder_security_check(
'et_theme_builder_portability',
et_core_portability_cap( 'et_theme_builder' ),
'et_theme_builder_api_export_theme_builder',
'nonce'
);
$_ = et_();
$id = sanitize_text_field( $_->array_get( $_POST, 'id', '' ) );
$step = (int) $_->array_get( $_POST, 'step', 0 );
$chunk = (int) $_->array_get( $_POST, 'chunk', 0 );
$transient = 'et_theme_builder_export_' . get_current_user_id() . '_' . $id;
$export = get_transient( $transient );
if ( false === $export || ! isset( $export['steps'][ $step ] ) ) {
wp_send_json_error();
}
$portability = et_core_portability_load( 'et_theme_builder' );
$export_step = isset( $export['steps'][ $step ] ) ? $export['steps'][ $step ] : array();
$result = $portability->export_theme_builder( $id, $export_step, count( $export['steps'] ), $step, $chunk );
if ( false === $result ) {
wp_send_json_error();
}
if ( $result['ready'] ) {
set_transient(
$transient,
array_merge(
$export,
array(
'ready' => $result['ready'],
'temp_file' => $result['temp_file'],
'temp_file_id' => $result['temp_file_id'],
)
),
60 * 60 * 24
);
}
wp_send_json_success(
array(
'chunks' => $result['chunks'],
)
);
}
add_action( 'wp_ajax_et_theme_builder_api_export_theme_builder_step', 'et_theme_builder_api_export_theme_builder_step' );
function et_theme_builder_api_export_theme_builder_download() {
if ( ! et_pb_is_allowed( 'theme_builder' ) ) {
wp_send_json_error();
}
et_builder_security_check(
'et_theme_builder_portability',
et_core_portability_cap( 'et_theme_builder' ),
'et_theme_builder_api_export_theme_builder',
'nonce',
'_GET'
);
$_ = et_();
$id = sanitize_text_field( $_->array_get( $_GET, 'id', '' ) );
$filename = sanitize_text_field( $_->array_get( $_GET, 'filename', '' ) );
$filename = '' !== $filename ? $filename : 'Divi Theme Builder Templates';
$filename = sanitize_file_name( $filename );
$transient = 'et_theme_builder_export_' . get_current_user_id() . '_' . $id;
$export = get_transient( $transient );
if ( false === $export || ! $export['ready'] ) {
wp_send_json_error();
}
$portability = et_core_portability_load( 'et_theme_builder' );
$portability->download_file( $filename, $export['temp_file_id'], $export['temp_file'] );
}
add_action( 'wp_ajax_et_theme_builder_api_export_theme_builder_download', 'et_theme_builder_api_export_theme_builder_download' );
/**
* Save a layout in a temporary file to prepare it for import.
*
* @since 4.1.0
*
* @param ET_Core_Portability $portability Portability object.
* @param string $template_id Template ID.
* @param integer $layout_id Layout ID.
* @param array $layout Layout.
* @param string $temp_id Temporary ID.
* @param string $temp_group Temporary Group.
*/
function et_theme_builder_api_import_theme_builder_save_layout( $portability, $template_id, $layout_id, $layout, $temp_id, $temp_group ) {
if ( ! empty( $layout['images'] ) ) {
// Split up images into individual temporary files
// to avoid hitting the memory limit.
foreach ( $layout['images'] as $url => $data ) {
$image_temp_id = $temp_id . '-image-' . md5( $url );
$portability->temp_file( $image_temp_id, $temp_group, false, wp_json_encode( $data ) );
$layout['images'][ $url ] = array(
'id' => $image_temp_id,
'group' => $temp_group,
);
}
}
$portability->temp_file(
$temp_id,
$temp_group,
false,
wp_json_encode(
array(
'type' => 'layout',
'data' => $layout,
'id' => $layout_id,
'template_id' => $template_id,
)
)
);
}
/**
* Load a previously saved layout from a temporary file.
*
* @since 4.1.0
*
* @param ET_Core_Portability $portability Portability Object.
* @param string $temp_id Temporary ID.
* @param string $temp_group Temporary Group.
*
* @return array
*/
function et_theme_builder_api_import_theme_builder_load_layout( $portability, $temp_id, $temp_group ) {
$import = $portability->get_temp_file_contents( $temp_id, $temp_group );
$import = ! empty( $import ) ? json_decode( $import, true ) : array();
$images = et_()->array_get( $import, array( 'data', 'images' ), array() );
// Hydrate images back from their individual temporary files.
foreach ( $images as $url => $file ) {
$import['data']['images'][ $url ] = json_decode( $portability->get_temp_file_contents( $file['id'], $file['group'] ), true );
}
return $import;
}
function et_theme_builder_api_import_theme_builder() {
$i18n = array_merge(
require ET_BUILDER_DIR . 'frontend-builder/i18n/generic.php',
require ET_BUILDER_DIR . 'frontend-builder/i18n/portability.php',
require ET_BUILDER_DIR . 'frontend-builder/i18n/theme-builder.php'
);
if ( ! et_pb_is_allowed( 'theme_builder' ) ) {
wp_send_json_error(
array(
'code' => ET_Theme_Builder_Api_Errors::UNKNOWN,
'error' => $i18n['An unknown error has occurred. Please try again later.'],
)
);
}
et_builder_security_check(
'et_theme_builder_portability',
et_core_portability_cap( 'et_theme_builder' ),
'et_theme_builder_api_import_theme_builder',
'nonce'
);
if ( ! isset( $_FILES['file']['name'] ) || ! et_()->ends_with( sanitize_file_name( $_FILES['file']['name'] ), '.json' ) ) {
wp_send_json_error(
array(
'code' => ET_Theme_Builder_Api_Errors::PORTABILITY_IMPORT_INVALID_FILE,
'error' => $i18n['$invalid_file'],
)
);
}
$_ = et_();
$upload = wp_handle_upload(
$_FILES['file'],
array(
'test_size' => false,
'test_type' => false,
'test_form' => false,
)
);
if ( ! $_->array_get( $upload, 'file', null ) ) {
wp_send_json_error(
array(
'code' => ET_Theme_Builder_Api_Errors::UNKNOWN,
'error' => $i18n['An unknown error has occurred. Please try again later.'],
)
);
}
$export = json_decode( et_()->WPFS()->get_contents( $upload['file'] ), true );
if ( null === $export ) {
wp_send_json_error(
array(
'code' => ET_Theme_Builder_Api_Errors::UNKNOWN,
'error' => $i18n['An unknown error has occurred. Please try again later.'],
)
);
}
$portability = et_core_portability_load( 'et_theme_builder' );
if ( ! $portability->is_valid_theme_builder_export( $export ) ) {
wp_send_json_error(
array(
'code' => ET_Theme_Builder_Api_Errors::PORTABILITY_INCORRECT_CONTEXT,
'error' => $i18n['This file should not be imported in this context.'],
)
);
}
$override_default_website_template = '1' === $_->array_get( $_POST, 'override_default_website_template', '0' );
$import_presets = '1' === $_->array_get( $_POST, 'import_presets', '0' );
$has_default_template = $_->array_get( $export, 'has_default_template', false );
$has_global_layouts = $_->array_get( $export, 'has_global_layouts', false );
$presets = $_->array_get( $export, 'presets', array() );
$presets_rewrite_map = array();
$incoming_layout_duplicate = false;
// Maybe ask the user to make a decision on how to deal with global layouts.
if ( ( ! $override_default_website_template || ! $has_default_template ) && $has_global_layouts ) {
$incoming_layout_duplicate_decision = $_->array_get( $_POST, 'incoming_layout_duplicate_decision', '' );
if ( 'duplicate' === $incoming_layout_duplicate_decision ) {
$incoming_layout_duplicate = true;
} elseif ( 'relink' === $incoming_layout_duplicate_decision ) {
$incoming_layout_duplicate = false;
} else {
wp_send_json_error(
array(
'code' => ET_Theme_Builder_Api_Errors::PORTABILITY_REQUIRE_INCOMING_LAYOUT_DUPLICATE_DECISION,
'error' => $i18n['This import contains references to global layouts.'],
)
);
}
}
// Make imported preset overrides to avoid collisions with local presets.
if ( $import_presets && is_array( $presets ) && ! empty( $presets ) ) {
$presets_rewrite_map = $portability->prepare_to_import_layout_presets( $presets );
}
// Prepare import steps.
$layout_id_map = array();
$layout_keys = array( 'header', 'body', 'footer' );
$id = md5( get_current_user_id() . '_' . uniqid( 'et_theme_builder_import_', true ) );
$transient = 'et_theme_builder_import_' . get_current_user_id() . '_' . $id;
$steps_files = array();
foreach ( $export['templates'] as $index => $template ) {
foreach ( $layout_keys as $key ) {
$layout_id = (int) $_->array_get( $template, array( 'layouts', $key, 'id' ), 0 );
if ( 0 === $layout_id ) {
continue;
}
$layout = $_->array_get( $export, array( 'layouts', $layout_id ), null );
if ( empty( $layout ) ) {
continue;
}
// Use a temporary string id to avoid numerical keys being reset by various array functions.
$template_id = 'template_' . $index;
$is_global = (bool) $_->array_get( $layout, 'theme_builder.is_global', false );
$create_new = ( $template['default'] && $override_default_website_template ) || ! $is_global || $incoming_layout_duplicate;
if ( $create_new ) {
$temp_id = 'tbi-step-' . count( $steps_files );
et_theme_builder_api_import_theme_builder_save_layout( $portability, $template_id, $layout_id, $layout, $temp_id, $transient );
$steps_files[] = array(
'id' => $temp_id,
'group' => $transient,
);
} else {
if ( ! isset( $layout_id_map[ $layout_id ] ) ) {
$layout_id_map[ $layout_id ] = array();
}
$layout_id_map[ $layout_id ][ $template_id ] = 'use_global';
}
}
}
set_transient(
$transient,
array(
'ready' => false,
'steps' => $steps_files,
'templates' => $export['templates'],
'override_default_website_template' => $override_default_website_template,
'incoming_layout_duplicate' => $incoming_layout_duplicate,
'layout_id_map' => $layout_id_map,
'presets' => $presets,
'import_presets' => $import_presets,
'presets_rewrite_map' => $presets_rewrite_map,
),
60 * 60 * 24
);
wp_send_json_success(
array(
'id' => $id,
'steps' => count( $steps_files ),
)
);
}
add_action( 'wp_ajax_et_theme_builder_api_import_theme_builder', 'et_theme_builder_api_import_theme_builder' );
function et_theme_builder_api_import_theme_builder_step() {
if ( ! et_pb_is_allowed( 'theme_builder' ) ) {
wp_send_json_error();
}
et_builder_security_check(
'et_theme_builder_portability',
et_core_portability_cap( 'et_theme_builder' ),
'et_theme_builder_api_import_theme_builder',
'nonce'
);
$_ = et_();
$id = sanitize_text_field( $_->array_get( $_POST, 'id', '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is done in `et_builder_security_check`.
$step = (int) $_->array_get( $_POST, 'step', 0 ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is done in `et_builder_security_check`.
$chunk = (int) $_->array_get( $_POST, 'chunk', 0 ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is done in `et_builder_security_check`.
$transient = 'et_theme_builder_import_' . get_current_user_id() . '_' . $id;
$export = get_transient( $transient );
if ( false === $export ) {
wp_send_json_error();
}
$layout_keys = array( 'header', 'body', 'footer' );
$portability = et_core_portability_load( 'et_theme_builder' );
$steps = $export['steps'];
$ready = empty( $steps );
$layout_id_map = $export['layout_id_map'];
$presets = $export['presets'];
$presets_rewrite_map = $export['presets_rewrite_map'];
$import_presets = $export['import_presets'];
$templates = array();
$template_settings = array();
$chunks = 1;
if ( ! $ready ) {
$import_step = et_theme_builder_api_import_theme_builder_load_layout( $portability, $steps[ $step ]['id'], $steps[ $step ]['group'] );
$import_step = array_merge( $import_step, array( 'presets' => $presets ) );
$import_step = array_merge( $import_step, array( 'presets_rewrite_map' => $presets_rewrite_map ) );
$import_step['import_presets'] = $import_presets;
$result = $portability->import_theme_builder( $id, $import_step, count( $steps ), $step, $chunk );
if ( false === $result ) {
wp_send_json_error();
}
$ready = $result['ready'];
$chunks = $result['chunks'];
foreach ( $result['layout_id_map'] as $old_id => $new_ids ) {
$layout_id_map[ $old_id ] = array_merge(
$_->array_get( $layout_id_map, $old_id, array() ),
$new_ids
);
}
}
if ( $ready ) {
if ( $import_presets && is_array( $presets ) && ! empty( $presets ) ) {
if ( ! $portability->import_global_presets( $presets ) ) {
$presets_error = apply_filters( 'et_core_portability_import_error_message', '' );
if ( $presets_error ) {
wp_send_json_error(
array(
'code' => ET_Theme_Builder_Api_Errors::PORTABILITY_IMPORT_PRESETS_FAILURE,
'error' => $presets_error,
)
);
}
}
}
$portability->delete_temp_files( $transient );
$conditions = array();
foreach ( $export['templates'] as $index => $template ) {
$sanitized = et_theme_builder_sanitize_template( $template );
foreach ( $layout_keys as $key ) {
$old_layout_id = (int) $_->array_get( $sanitized, array( 'layouts', $key, 'id' ), 0 );
$layout_id = et_()->array_get( $layout_id_map, array( $old_layout_id, 'template_' . $index ), '' );
$layout_id = ! empty( $layout_id ) ? $layout_id : 0;
$_->array_set( $sanitized, array( 'layouts', $key, 'id' ), $layout_id );
}
$conditions = array_merge( $conditions, $sanitized['use_on'], $sanitized['exclude_from'] );
$templates[] = $sanitized;
}
// Load all conditions from templates.
$conditions = array_unique( $conditions );
$template_settings = array_replace(
et_theme_builder_get_flat_template_settings_options(),
et_theme_builder_load_template_setting_options( $conditions )
);
$valid_settings = array_keys( $template_settings );
// Strip all invalid conditions from templates.
foreach ( $templates as $index => $template ) {
$templates[ $index ]['use_on'] = array_values( array_intersect( $template['use_on'], $valid_settings ) );
$templates[ $index ]['exclude_from'] = array_values( array_intersect( $template['exclude_from'], $valid_settings ) );
}
} else {
set_transient(
$transient,
array_merge(
$export,
array(
'layout_id_map' => $layout_id_map,
)
),
60 * 60 * 24
);
}
wp_send_json_success(
array(
'chunks' => $chunks,
'templates' => $templates,
'templateSettings' => $template_settings,
)
);
}
add_action( 'wp_ajax_et_theme_builder_api_import_theme_builder_step', 'et_theme_builder_api_import_theme_builder_step' );

View File

@ -0,0 +1,216 @@
<?php
/**
* Resolve placeholder content for built-in dynamic content fields for Theme Builder layouts.
*
* @since 4.0
*
* @param string $content Content.
* @param string $name Name.
* @param array $settings Settings.
* @param integer $post_id Post ID.
* @param string $context Context.
* @param array $overrides Overrides.
*
* @return string
*/
function et_theme_builder_filter_resolve_default_dynamic_content( $content, $name, $settings, $post_id, $context, $overrides ) {
$post_type = get_post_type( $post_id );
if ( ! et_theme_builder_is_layout_post_type( $post_type ) ) {
return $content;
}
$placeholders = array(
'post_title' => __( 'Your Dynamic Post Title Will Display Here', 'et_builder' ),
'post_excerpt' => __( 'Your dynamic post excerpt will display here. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus auctor urna eleifend diam eleifend sollicitudin a fringilla turpis. Curabitur lectus enim.', 'et_builder' ),
'post_date' => time(),
'post_comment_count' => 12,
'post_categories' => array(
__( 'Category 1', 'et_builder' ),
__( 'Category 2', 'et_builder' ),
__( 'Category 3', 'et_builder' ),
),
'post_tags' => array(
__( 'Tag 1', 'et_builder' ),
__( 'Tag 2', 'et_builder' ),
__( 'Tag 3', 'et_builder' ),
),
'post_author' => array(
'display_name' => __( 'John Doe', 'et_builder' ),
'first_last_name' => __( 'John Doe', 'et_builder' ),
'last_first_name' => __( 'Doe, John', 'et_builder' ),
'first_name' => __( 'John', 'et_builder' ),
'last_name' => __( 'Doe', 'et_builder' ),
'nickname' => __( 'John', 'et_builder' ),
'username' => __( 'johndoe', 'et_builder' ),
),
'post_author_bio' => __( 'Your dynamic author bio will display here. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus auctor urna eleifend diam eleifend sollicitudin a fringilla turpis. Curabitur lectus enim.', 'et_builder' ),
'post_featured_image' => ET_BUILDER_PLACEHOLDER_LANDSCAPE_IMAGE_DATA,
'term_description' => __( 'Your dynamic category description will display here. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus auctor urna eleifend diam eleifend sollicitudin a fringilla turpis. Curabitur lectus enim.', 'et_builder' ),
'site_logo' => 'https://www.elegantthemes.com/img/divi.png',
);
$_ = et_();
$def = 'et_builder_get_dynamic_attribute_field_default';
$wrapped = false;
switch ( $name ) {
case 'post_title':
$content = esc_html( $placeholders[ $name ] );
break;
case 'post_excerpt':
$words = (int) $_->array_get( $settings, 'words', $def( $post_id, $name, 'words' ) );
$read_more = $_->array_get( $settings, 'read_more_label', $def( $post_id, $name, 'read_more_label' ) );
$content = esc_html( $placeholders[ $name ] );
if ( $words > 0 ) {
$content = wp_trim_words( $content, $words );
}
if ( ! empty( $read_more ) ) {
$content .= sprintf(
' <a href="%1$s">%2$s</a>',
'#',
esc_html( $read_more )
);
}
break;
case 'post_date':
$format = $_->array_get( $settings, 'date_format', $def( $post_id, $name, 'date_format' ) );
$custom_format = $_->array_get( $settings, 'custom_date_format', $def( $post_id, $name, 'custom_date_format' ) );
if ( 'default' === $format ) {
$format = get_option( 'date_format' );
}
if ( 'custom' === $format ) {
$format = $custom_format;
}
$content = esc_html( date( $format, $placeholders[ $name ] ) );
break;
case 'post_comment_count':
$link = $_->array_get( $settings, 'link_to_comments_page', $def( $post_id, $name, 'link_to_comments_page' ) );
$link = 'on' === $link;
$content = esc_html( $placeholders[ $name ] );
if ( $link ) {
$content = sprintf(
'<a href="%1$s">%2$s</a>',
'#',
et_core_esc_previously( et_builder_wrap_dynamic_content( $post_id, $name, $content, $settings ) )
);
$wrapped = true;
}
break;
case 'post_categories': // Intentional fallthrough.
case 'post_tags':
$link = $_->array_get( $settings, 'link_to_term_page', $def( $post_id, $name, 'link_to_category_page' ) );
$link = 'on' === $link;
$url = '#';
$separator = $_->array_get( $settings, 'separator', $def( $post_id, $name, 'separator' ) );
$separator = ! empty( $separator ) ? $separator : $def( $post_id, $name, 'separator' );
$content = $placeholders[ $name ];
foreach ( $content as $index => $item ) {
$content[ $index ] = esc_html( $item );
if ( $link ) {
$content[ $index ] = sprintf(
'<a href="%1$s" target="%2$s">%3$s</a>',
esc_url( $url ),
esc_attr( '_blank' ),
et_core_esc_previously( $content[ $index ] )
);
}
}
$content = implode( esc_html( $separator ), $content );
break;
case 'post_link':
$text = $_->array_get( $settings, 'text', $def( $post_id, $name, 'text' ) );
$custom_text = $_->array_get( $settings, 'custom_text', $def( $post_id, $name, 'custom_text' ) );
$label = 'custom' === $text ? $custom_text : $placeholders['post_title'];
$content = sprintf(
'<a href="%1$s">%2$s</a>',
'#',
esc_html( $label )
);
break;
case 'post_author':
$name_format = $_->array_get( $settings, 'name_format', $def( $post_id, $name, 'name_format' ) );
$link = $_->array_get( $settings, 'link', $def( $post_id, $name, 'link' ) );
$link = 'on' === $link;
$label = isset( $placeholders[ $name ][ $name_format ] ) ? $placeholders[ $name ][ $name_format ] : '';
$url = '#';
$content = esc_html( $label );
if ( $link && ! empty( $url ) ) {
$content = sprintf(
'<a href="%1$s" target="%2$s">%3$s</a>',
esc_url( $url ),
esc_attr( '_blank' ),
et_core_esc_previously( $content )
);
}
break;
case 'post_author_bio':
$content = esc_html( $placeholders[ $name ] );
break;
case 'term_description':
$content = esc_html( $placeholders[ $name ] );
break;
case 'post_link_url':
$content = '#';
break;
case 'post_author_url':
$content = '#';
break;
case 'post_featured_image':
$content = et_core_intentionally_unescaped( $placeholders[ $name ], 'fixed_string' );
break;
case 'site_logo':
if ( empty( $content ) ) {
$content = esc_url( $placeholders[ $name ] );
} else {
$wrapped = true;
}
break;
default:
// Avoid unhandled cases being wrapped twice by the default resolve and this one.
$wrapped = true;
break;
}
if ( $_->starts_with( $name, 'custom_meta_' ) ) {
$meta_key = substr( $name, strlen( 'custom_meta_' ) );
$meta_value = get_post_meta( $post_id, $meta_key, true );
if ( empty( $meta_value ) ) {
$content = et_builder_get_dynamic_content_custom_field_label( $meta_key );
} else {
$wrapped = true;
}
}
if ( ! $wrapped ) {
$content = et_builder_wrap_dynamic_content( $post_id, $name, $content, $settings );
$wrapped = true;
}
return $content;
}
add_filter( 'et_builder_resolve_dynamic_content', 'et_theme_builder_filter_resolve_default_dynamic_content', 11, 6 );

View File

@ -0,0 +1,15 @@
<?php
$layouts = et_theme_builder_get_template_layouts();
?>
<?php get_header(); ?>
<?php
et_theme_builder_frontend_render_body(
$layouts[ ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE ]['id'],
$layouts[ ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE ]['enabled'],
$layouts[ ET_THEME_BUILDER_TEMPLATE_POST_TYPE ]
);
?>
<?php
get_footer();

View File

@ -0,0 +1,25 @@
<?php
$layouts = et_theme_builder_get_template_layouts();
?>
<?php
et_theme_builder_frontend_render_footer(
$layouts[ ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE ]['id'],
$layouts[ ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE ]['enabled'],
$layouts[ ET_THEME_BUILDER_TEMPLATE_POST_TYPE ]
);
?>
<?php if ( et_core_is_fb_enabled() && et_theme_builder_is_layout_post_type( get_post_type() ) ) : ?>
<?php // Hide the footer when we are editing a TB layout. ?>
<div class="et-tb-fb-footer" style="display: none;">
<?php wp_footer(); ?>
</div>
<?php else : ?>
<?php wp_footer(); ?>
<?php endif; ?>
<?php if ( ! et_is_builder_plugin_active() && 'on' === et_get_option( 'divi_back_to_top', 'false' ) ) : ?>
<span class="et_pb_scroll_top et-pb-icon"></span>
<?php endif; ?>
</body>
</html>

View File

@ -0,0 +1,23 @@
<?php
$layouts = et_theme_builder_get_template_layouts();
?>
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<?php echo $tb_theme_head; ?>
<?php do_action( 'et_theme_builder_template_head' ); ?>
<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php
wp_body_open();
et_theme_builder_frontend_render_header(
$layouts[ ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE ]['id'],
$layouts[ ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE ]['enabled'],
$layouts[ ET_THEME_BUILDER_TEMPLATE_POST_TYPE ]
);
?>

View File

@ -0,0 +1,667 @@
<?php
/**
* Remove the admin bar from the VB when used from the Theme Builder.
*
* @since 4.0
*
* @return void
*/
function et_theme_builder_frontend_disable_admin_bar() {
if ( et_builder_tb_enabled() ) {
add_filter( 'show_admin_bar', '__return_false' );
}
}
add_filter( 'wp', 'et_theme_builder_frontend_disable_admin_bar' );
/**
* Add body classes depending on which areas are overridden by TB.
*
* @since 4.0
*
* @param array $classes
*
* @return string[]
*/
function et_theme_builder_frontend_add_body_classes( $classes ) {
if ( et_builder_bfb_enabled() ) {
// Do not add any classes in BFB.
return $classes;
}
$layouts = et_theme_builder_get_template_layouts();
if ( ! empty( $layouts ) ) {
$classes[] = 'et-tb-has-template';
if ( $layouts[ ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE ]['override'] ) {
$classes[] = 'et-tb-has-header';
if ( ! $layouts[ ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE ]['enabled'] ) {
$classes[] = 'et-tb-header-disabled';
}
}
if ( $layouts[ ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE ]['override'] ) {
$classes[] = 'et-tb-has-body';
if ( ! $layouts[ ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE ]['enabled'] ) {
$classes[] = 'et-tb-body-disabled';
}
}
if ( $layouts[ ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE ]['override'] ) {
$classes[] = 'et-tb-has-footer';
if ( ! $layouts[ ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE ]['enabled'] ) {
$classes[] = 'et-tb-footer-disabled';
}
}
}
return $classes;
}
add_filter( 'body_class', 'et_theme_builder_frontend_add_body_classes', 9 );
/**
* Conditionally override the template being loaded by WordPress based on what the user
* has created in their Theme Builder.
* The header and footer are always dealt with as a pair - if the header is replaced the footer is replaced a well.
*
* @since 4.0
*
* @param string $template
*
* @return string
*/
function et_theme_builder_frontend_override_template( $template ) {
$layouts = et_theme_builder_get_template_layouts();
$page_template = locate_template( 'page.php' );
$override_header = et_theme_builder_overrides_layout( ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE );
$override_body = et_theme_builder_overrides_layout( ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE );
$override_footer = et_theme_builder_overrides_layout( ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE );
$is_visual_builder = isset( $_GET['et_fb'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Value is not used
$is_theme_builder = et_builder_tb_enabled();
if ( ( $override_header || $override_body || $override_footer ) && et_core_is_fb_enabled() ) {
// When cached assets/definitions do not exist, a VB/BFB page will generate them inline.
// This would normally happen later but not in the case of TB because, due to early
// `the_content()` calls, `maybe_rebuild_option_template()` would be invoked before
// saving definitions/assets resulting in them including resolved option templates instead
// of placeholders.
$post_type = get_post_type();
et_fb_get_dynamic_asset( 'helpers', $post_type );
et_fb_get_dynamic_asset( 'definitions', $post_type );
}
if ( $override_header || $override_footer ) {
// wp-version >= 5.2
remove_action( 'wp_body_open', 'wp_admin_bar_render', 0 );
add_action( 'get_header', 'et_theme_builder_frontend_override_header' );
add_action( 'get_footer', 'et_theme_builder_frontend_override_footer' );
}
et_theme_builder_frontend_enqueue_styles( $layouts );
// For other themes than Divi, use 'frontend-body-template.php'.
if ( $override_body && ! function_exists( 'et_divi_fonts_url' ) ) {
return ET_THEME_BUILDER_DIR . 'frontend-body-template.php';
}
if ( $override_body && ( $is_theme_builder || ! $is_visual_builder || ! $layouts['et_template'] || ! et_pb_is_allowed( 'theme_builder' ) ) ) {
return ET_THEME_BUILDER_DIR . 'frontend-body-template.php';
}
if ( $override_body && ! is_home() ) {
return $page_template;
}
return $template;
}
// Priority of 98 so it can be overridden by BFB.
add_filter( 'template_include', 'et_theme_builder_frontend_override_template', 98 );
/**
* Enqueue any necessary TB layout styles.
*
* @since 4.0
*
* @param array $layouts
*
* @return void
*/
function et_theme_builder_frontend_enqueue_styles( $layouts ) {
if ( empty( $layouts ) ) {
return;
}
if ( ! is_singular() || et_core_is_fb_enabled() ) {
// Create styles managers so they can enqueue styles early enough.
// What styles are created and how they are enqueued:
// - In FE, singular post view:
// -> TB + Post Styles are combined into et-*-tb-{HEADER_ID}-tb-{BODY_ID}-tb-{FOOTER_ID}-{POST_ID}-*.css
//
// - In FE, non-singular post view:
// -> TB styles are separate with the usual filename: et-*-{LAYOUT_ID}-*.css
//
// - In FE, singular post view in VB so post-specific DC works:
// -> TB styles are separate with the current post ID prepended: et-*-tb-for-{POST_ID}-{LAYOUT_ID}-*.css.
if ( $layouts[ ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE ]['override'] ) {
ET_Builder_Element::setup_advanced_styles_manager( $layouts[ ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE ]['id'] );
}
if ( $layouts[ ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE ]['override'] ) {
ET_Builder_Element::setup_advanced_styles_manager( $layouts[ ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE ]['id'] );
}
if ( $layouts[ ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE ]['override'] ) {
ET_Builder_Element::setup_advanced_styles_manager( $layouts[ ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE ]['id'] );
}
}
}
/**
* Render a custom partial overriding the original one.
*
* @since 4.0
*
* @param string $partial
* @param string $action
* @param string $name
*
* @return void
*/
function et_theme_builder_frontend_override_partial( $partial, $name, $action = '' ) {
global $wp_filter;
$tb_theme_head = '';
/**
* Slightly adjusted version of WordPress core code in order to mimic behavior.
*
* @link https://core.trac.wordpress.org/browser/tags/5.0.3/src/wp-includes/general-template.php#L33
*/
$templates = array();
$name = (string) $name;
if ( '' !== $name ) {
$templates[] = "{$partial}-{$name}.php";
}
$templates[] = "{$partial}.php";
// Buffer and discard the original partial forcing a require_once so it doesn't load again later.
$buffered = ob_start();
if ( $buffered ) {
$actions = array();
if ( ! empty( $action ) ) {
// Skip any partial-specific actions so they don't run twice.
$actions = et_()->array_get( $wp_filter, $action, array() );
unset( $wp_filter[ $action ] );
}
locate_template( $templates, true, true );
$html = ob_get_clean();
if ( 'wp_head' === $action ) {
$tb_theme_head = et_theme_builder_extract_head( $html );
}
if ( ! empty( $action ) ) {
// Restore skipped actions.
$wp_filter[ $action ] = $actions;
}
}
require_once ET_THEME_BUILDER_DIR . "frontend-{$partial}-template.php";
}
/**
* Extract <head> tag contents.
*
* @since 4.0.8
*
* @param string $html
*
* @return string
*/
function et_theme_builder_extract_head( $html ) {
// We could use DOMDocument here to guarantee proper parsing but we need
// the most performant solution since we cannot reliably cache the result.
$head = array();
preg_match( '/^[\s\S]*?<head[\s\S]*?>([\s\S]*?)<\/head>[\s\S]*$/i', $html, $head );
return ! empty( $head[1] ) ? trim( $head[1] ) : '';
}
/**
* Override the default header template.
*
* @since 4.0
*
* @param string $name
*
* @return void
*/
function et_theme_builder_frontend_override_header( $name ) {
et_theme_builder_frontend_override_partial( 'header', $name, 'wp_head' );
}
/**
* Override the default footer template.
*
* @since 4.0
*
* @param string $name
*
* @return void
*/
function et_theme_builder_frontend_override_footer( $name ) {
et_theme_builder_frontend_override_partial( 'footer', $name, 'wp_footer' );
}
/**
* Filter builder content wrapping as Theme Builder Layouts are wrapped collectively instead of individually.
*
* @since 4.0
*
* @param bool $wrap
*
* @return bool
*/
function et_theme_builder_frontend_filter_add_outer_content_wrap( $wrap ) {
$override_header = et_theme_builder_overrides_layout( ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE );
$override_footer = et_theme_builder_overrides_layout( ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE );
// Theme Builder layouts must not be individually wrapped as they are wrapped
// collectively, with the exception of the BFB or body layout.
if ( ( $override_header || $override_footer ) && ! et_builder_bfb_enabled() ) {
$wrap = false;
}
return $wrap;
}
add_filter( 'et_builder_add_outer_content_wrap', 'et_theme_builder_frontend_filter_add_outer_content_wrap' );
/**
* Render a template builder layout.
*
* Wrapper cases:
* 1. Header/Footer are replaced.
* => Common is open and closed. Header/Footer do not get opened/closed because
* Common is opened before them.
*
* 2. Body is replaced.
* => Common is NOT opened/closed. Body is open/closed.
*
* 3. Header/Body/Footer are replaced.
* => Common is open and closed. Header/Body/Footer do not get opened/closed because
* Common is opened before them.
*
* @since 4.0
*
* @param string $layout_type Layout Type.
* @param integer $layout_id Layout ID.
*
* @return void
*/
function et_theme_builder_frontend_render_layout( $layout_type, $layout_id ) {
if ( $layout_id <= 0 ) {
return;
}
$layout = get_post( $layout_id );
if ( null === $layout || $layout->post_type !== $layout_type ) {
return;
}
et_theme_builder_frontend_render_common_wrappers( $layout_type, true );
/**
* Fires after Theme Builder layout opening wrappers have been output but before any
* other processing has been done (e.g. replacing the current post).
*
* @since 4.0.10
*
* @param string $layout_type
* @param integer $layout_id
*/
do_action( 'et_theme_builder_after_layout_opening_wrappers', $layout_type, $layout_id );
ET_Builder_Element::begin_theme_builder_layout( $layout_id );
ET_Post_Stack::replace( $layout );
$is_visual_builder = isset( $_GET['et_fb'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Value is not used
$theme_builder_layouts = array( 'et_header_layout', 'et_footer_layout' );
// Do not pass header and footer content here if visual builder is loaded,
// they will be loaded inside the builder itself.
if ( et_pb_is_allowed( 'theme_builder' ) && $is_visual_builder && in_array( $layout_type, $theme_builder_layouts, true ) ) {
$post_content = '';
} else {
$post_content = get_the_content();
}
echo et_core_intentionally_unescaped( et_builder_render_layout( $post_content ), 'html' );
// Get dynamic content.
$has_dynamic_content = et_builder_get_dynamic_contents( get_the_content() );
// Handle style output.
if ( is_singular() && ! et_core_is_fb_enabled() ) {
$result = ET_Builder_Element::setup_advanced_styles_manager( ET_Post_Stack::get_main_post_id() );
} elseif ( is_tax() && ! empty( $has_dynamic_content ) ) {
// Set post id to 0 if its a taxonomy page.
// This is because of the dynamic content not working properly,
// With the theme builder cache.
$result = ET_Builder_Element::setup_advanced_styles_manager( 0 );
} else {
$result = ET_Builder_Element::setup_advanced_styles_manager( $layout->ID );
}
$advanced_styles_manager = $result['manager'];
if ( isset( $result['deferred'] ) ) {
$deferred_styles_manager = $result['deferred'];
}
// Pass styles to page resource which will handle their output.
/**
* Filters whether Critical CSS feature is enabled or not.
*
* @since 4.10.0
*
* @param bool $enabled Critical CSS enabled value.
*/
$is_critical_enabled = apply_filters( 'et_builder_critical_css_enabled', false );
if ( ET_Builder_Element::$forced_inline_styles || ! $advanced_styles_manager->has_file() || $advanced_styles_manager->forced_inline ) {
$custom = et_pb_get_page_custom_css( $layout->ID );
$critical = $is_critical_enabled ? ET_Builder_Element::get_style( false, $layout->ID, true ) . ET_Builder_Element::get_style( true, $layout->ID, true ) : [];
$styles = ET_Builder_Element::get_style( false, $layout->ID ) . ET_Builder_Element::get_style( true, $layout->ID );
if ( empty( $critical ) ) {
// No critical styles defined, just enqueue everything as usual.
$styles = $custom . $styles;
if ( ! empty( $styles ) ) {
if ( isset( $deferred_styles_manager ) ) {
$deferred_styles_manager->set_data( $styles, 40 );
} else {
$advanced_styles_manager->set_data( $styles, 40 );
}
}
} else {
// Add page css to the critical section.
$critical = $custom . $critical;
$advanced_styles_manager->set_data( $critical, 40 );
if ( ! empty( $styles ) ) {
// Defer everything else.
$deferred_styles_manager->set_data( $styles, 40 );
}
}
}
ET_Post_Stack::restore();
ET_Builder_Element::end_theme_builder_layout();
/**
* Fires before Theme Builder layout closing wrappers have been output and after any
* other processing has been done (e.g. replacing the current post).
*
* @since 4.0.10
*
* @param string $layout_type
* @param integer $layout_id
*/
do_action( 'et_theme_builder_before_layout_closing_wrappers', $layout_type, $layout_id );
et_theme_builder_frontend_render_common_wrappers( $layout_type, false );
}
/**
* Render a header layout.
*
* @since 4.0
*
* @param integer $layout_id The layout id or 0.
* @param boolean $layout_enabled
* @param integer $template_id The template id or 0.
*
* @return void
*/
function et_theme_builder_frontend_render_header( $layout_id, $layout_enabled, $template_id ) {
/**
* Fires before theme builder page wrappers are output.
* Example use case is to add opening wrapping html tags for the entire page.
*
* @since 4.0
*
* @param integer $layout_id The layout id or 0.
* @param bool $layout_enabled
* @param integer $template_id The template id or 0.
*/
do_action( 'et_theme_builder_template_before_page_wrappers', $layout_id, $layout_enabled, $template_id );
et_theme_builder_frontend_render_common_wrappers( 'common', true );
/**
* Fires before theme builder template header is output.
* Example use case is to add opening wrapping html tags for the header and/or the entire page.
*
* @since 4.0
*
* @param integer $layout_id The layout id or 0.
* @param bool $layout_enabled
* @param integer $template_id The template id or 0.
*/
do_action( 'et_theme_builder_template_before_header', $layout_id, $layout_enabled, $template_id );
if ( $layout_enabled ) {
et_theme_builder_frontend_render_layout( ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE, $layout_id );
}
/**
* Fires after theme builder template header is output.
* Example use case is to add closing wrapping html tags for the header.
*
* @since 4.0
*
* @param integer $layout_id The layout id or 0.
* @param bool $layout_enabled
* @param integer $template_id The template id or 0.
*/
do_action( 'et_theme_builder_template_after_header', $layout_id, $layout_enabled, $template_id );
}
/**
* Render a body layout.
*
* @since 4.0
*
* @param integer $layout_id The layout id or 0.
* @param boolean $layout_enabled
* @param integer $template_id The template id or 0.
*
* @return void
*/
function et_theme_builder_frontend_render_body( $layout_id, $layout_enabled, $template_id ) {
/**
* Fires before theme builder template body is output.
* Example use case is to add opening wrapping html tags for the body.
*
* @since 4.0
*
* @param integer $layout_id The layout id or 0.
* @param bool $layout_enabled
* @param integer $template_id The template id or 0.
*
* @return void
*/
do_action( 'et_theme_builder_template_before_body', $layout_id, $layout_enabled, $template_id );
if ( $layout_enabled ) {
et_theme_builder_frontend_render_layout( ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE, $layout_id );
}
/**
* Fires after theme builder template body is output.
* Example use case is to add closing wrapping html tags for the body.
*
* @since 4.0
*
* @param integer $layout_id The layout id or 0.
* @param bool $layout_enabled
* @param integer $template_id The template id or 0.
*
* @return void
*/
do_action( 'et_theme_builder_template_after_body', $layout_id, $layout_enabled, $template_id );
}
/**
* Render a footer layout.
*
* @since 4.0
*
* @param integer $layout_id The layout id or 0.
* @param boolean $layout_enabled
* @param integer $template_id The template id or 0.
*
* @return void
*/
function et_theme_builder_frontend_render_footer( $layout_id, $layout_enabled, $template_id ) {
/**
* Fires before theme builder template footer is output.
* Example use case is to add opening wrapping html tags for the footer.
*
* @since 4.0
*
* @param integer $layout_id The layout id or 0.
* @param bool $layout_enabled
* @param integer $template_id The template id or 0.
*
* @return void
*/
do_action( 'et_theme_builder_template_before_footer', $layout_id, $layout_enabled, $template_id );
if ( $layout_enabled ) {
et_theme_builder_frontend_render_layout( ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE, $layout_id );
}
/**
* Fires after theme builder template footer is output.
* Example use case is to add closing wrapping html tags for the footer and/or the entire page.
*
* @since 4.0
*
* @param integer $layout_id The layout id or 0.
* @param bool $layout_enabled
* @param integer $template_id The template id or 0.
*
* @return void
*/
do_action( 'et_theme_builder_template_after_footer', $layout_id, $layout_enabled, $template_id );
et_theme_builder_frontend_render_common_wrappers( 'common', false );
/**
* Fires after theme builder page wrappers are output.
* Example use case is to add closing wrapping html tags for the entire page.
*
* @since 4.0
*
* @param integer $layout_id The layout id or 0.
* @param bool $layout_enabled
* @param integer $template_id The template id or 0.
*/
do_action( 'et_theme_builder_template_after_page_wrappers', $layout_id, $layout_enabled, $template_id );
}
/**
* Open or close common builder wrappers (e.g. #et-boc) in order to avoid having triple wrappers - one for every layout.
*
* Useful
*
* @param $area
* @param $open
*/
function et_theme_builder_frontend_render_common_wrappers( $area, $open ) {
static $wrapper = '';
if ( $open ) {
// Open wrappers only if there are no other open wrappers already.
if ( '' === $wrapper ) {
$wrapper = $area;
echo et_builder_get_builder_content_opening_wrapper();
}
return;
}
if ( '' === $wrapper || $area !== $wrapper ) {
// Do not close wrappers if the opener does not match the current area.
return;
}
echo et_builder_get_builder_content_closing_wrapper();
}
/**
* Get the html representing the post content for the current post.
*
* @since 4.0
*
* @return string
*/
function et_theme_builder_frontend_render_post_content() {
static $__prevent_recursion = false;
global $wp_query;
if ( ET_Builder_Element::get_theme_builder_layout_type() !== ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE ) {
// Prevent usage on non-body layouts.
return '';
}
if ( ! is_singular() ) {
// Do not output anything on non-singular pages.
return '';
}
$main_query_post = ET_Post_Stack::get_main_post();
if ( ! $main_query_post ) {
// Bail if there is no current post.
return '';
}
if ( true === $__prevent_recursion ) {
// Failsafe just in case.
return '';
}
$__prevent_recursion = true;
$html = '';
$buffered = ob_start();
if ( $buffered ) {
ET_Post_Stack::replace( $main_query_post );
ET_Builder_Element::begin_theme_builder_layout( get_the_ID() );
do_action_ref_array( 'loop_start', array( &$wp_query ) );
the_content();
do_action_ref_array( 'loop_end', array( &$wp_query ) );
ET_Builder_Element::end_theme_builder_layout();
ET_Post_Stack::restore();
$html = ob_get_clean();
}
$__prevent_recursion = false;
return $html;
}

View File

@ -0,0 +1,325 @@
<?php
/**
* Filters an object id for use in template settings validation functions.
*
* @since 4.2
*
* @param integer $id Object ID.
* @param string $type Type.
* @param string $subtype Subtype.
*
* @return integer
*/
function et_theme_builder_template_setting_filter_validation_object_id( $id, $type, $subtype ) {
/**
* Filters template settings object id for validation use.
*
* @since 4.2
*
* @param integer $id
* @param string $type
* @param string $subtype
*/
return apply_filters( 'et_theme_builder_template_setting_filter_validation_id', $id, $type, $subtype );
}
/**
* Validate homepage.
*
* @since 4.0
*
* @param string $type Type.
* @param string $subtype Subtype.
* @param integer $id ID.
* @param string[] $setting Setting.
*
* @return bool
*/
function et_theme_builder_template_setting_validate_homepage( $type, $subtype, $id, $setting ) {
return ET_Theme_Builder_Request::TYPE_FRONT_PAGE === $type;
}
/**
* Validate singular:post_type:<post_type>:all.
*
* @since 4.0
*
* @param string $type Type.
* @param string $subtype Subtype.
* @param integer $id ID.
* @param string[] $setting Setting.
*
* @return bool
*/
function et_theme_builder_template_setting_validate_singular_post_type_all( $type, $subtype, $id, $setting ) {
if ( ET_Theme_Builder_Request::TYPE_FRONT_PAGE === $type && 'page' === $setting[2] && $id === (int) get_option( 'page_on_front' ) ) {
// Cover the homepage as well.
return true;
}
return ET_Theme_Builder_Request::TYPE_SINGULAR === $type && $subtype === $setting[2];
}
/**
* Validate archive:post_type:<post_type>.
*
* @since 4.0
*
* @param string $type Type.
* @param string $subtype Subtype.
* @param integer $id ID.
* @param string[] $setting Setting.
*
* @return bool
*/
function et_theme_builder_template_setting_validate_archive_post_type( $type, $subtype, $id, $setting ) {
return ET_Theme_Builder_Request::TYPE_POST_TYPE_ARCHIVE === $type && $subtype === $setting[2];
}
/**
* Validate singular:post_type:<post_type>:id:<id>.
*
* @since 4.0
*
* @param string $type Type.
* @param string $subtype Subtype.
* @param integer $id ID.
* @param string[] $setting Setting.
*
* @return bool
*/
function et_theme_builder_template_setting_validate_singular_post_type_id( $type, $subtype, $id, $setting ) {
$object_id = et_theme_builder_template_setting_filter_validation_object_id( (int) $setting[4], 'post', $setting[2] );
return (
// Cover the special case where the post selected is assigned as the website homepage.
( ET_Theme_Builder_Request::TYPE_FRONT_PAGE === $type && $id === $object_id )
||
( ET_Theme_Builder_Request::TYPE_SINGULAR === $type && $id === $object_id )
);
}
/**
* Validate singular:post_type:<post_type>:children:id:<id>.
*
* @since 4.0
*
* @param string $type Type.
* @param string $subtype Subtype.
* @param integer $id ID.
* @param string[] $setting Setting.
*
* @return bool
*/
function et_theme_builder_template_setting_validate_singular_post_type_children_id( $type, $subtype, $id, $setting ) {
if ( ET_Theme_Builder_Request::TYPE_SINGULAR !== $type ) {
return false;
}
$object_id = et_theme_builder_template_setting_filter_validation_object_id( (int) $setting[5], 'post', $setting[2] );
return in_array( $object_id, get_post_ancestors( $id ), true );
}
/**
* Validate singular:taxonomy:<taxonomy>:term:id:<id>.
*
* @since 4.0
*
* @param string $type Type.
* @param string $subtype Subtype.
* @param integer $id ID.
* @param string[] $setting Setting.
*
* @return bool
*/
function et_theme_builder_template_setting_validate_singular_taxonomy_term_id( $type, $subtype, $id, $setting ) {
if ( ET_Theme_Builder_Request::TYPE_SINGULAR !== $type ) {
return false;
}
$taxonomy = $setting[2];
$object_id = et_theme_builder_template_setting_filter_validation_object_id( (int) $setting[5], 'taxonomy', $taxonomy );
return has_term( $object_id, $taxonomy, $id );
}
/**
* Validate archive:all.
*
* @since 4.0
*
* @param string $type Type.
* @param string $subtype Subtype.
* @param integer $id ID.
* @param string[] $setting Setting.
*
* @return bool
*/
function et_theme_builder_template_setting_validate_archive_all( $type, $subtype, $id, $setting ) {
$archives = array(
ET_Theme_Builder_Request::TYPE_POST_TYPE_ARCHIVE,
ET_Theme_Builder_Request::TYPE_TERM,
ET_Theme_Builder_Request::TYPE_AUTHOR,
ET_Theme_Builder_Request::TYPE_DATE,
);
return in_array( $type, $archives, true );
}
/**
* Validate archive:taxonomy:<taxonomy>:all.
*
* @since 4.0
*
* @param string $type Type.
* @param string $subtype Subtype.
* @param integer $id ID.
* @param string[] $setting Setting.
*
* @return bool
*/
function et_theme_builder_template_setting_validate_archive_taxonomy_all( $type, $subtype, $id, $setting ) {
return ET_Theme_Builder_Request::TYPE_TERM === $type && $subtype === $setting[2];
}
/**
* Validate archive:taxonomy:<taxonomy>:term:id:<id>.
*
* @since 4.0
*
* @param string $type Type.
* @param string $subtype Subtype.
* @param integer $id ID.
* @param string[] $setting Setting.
*
* @return bool
*/
function et_theme_builder_template_setting_validate_archive_taxonomy_term_id( $type, $subtype, $id, $setting ) {
$taxonomy = $setting[2];
$object_id = et_theme_builder_template_setting_filter_validation_object_id( (int) $setting[5], 'post', $taxonomy );
if ( ET_Theme_Builder_Request::TYPE_TERM === $type && $subtype === $taxonomy ) {
// Exact match.
if ( $id === $object_id ) {
return true;
}
// Specified setting term id is an ancestor of the request term id ($id).
if ( term_is_ancestor_of( $object_id, $id, $taxonomy ) ) {
return true;
}
}
return false;
}
/**
* Validate archive:user:all.
*
* @since 4.0
*
* @param string $type Type.
* @param string $subtype Subtype.
* @param integer $id ID.
* @param string[] $setting Setting.
*
* @return bool
*/
function et_theme_builder_template_setting_validate_archive_user_all( $type, $subtype, $id, $setting ) {
return ET_Theme_Builder_Request::TYPE_AUTHOR === $type;
}
/**
* Validate archive:user:id:<id>.
*
* @since 4.0
*
* @param string $type Type.
* @param string $subtype Subtype.
* @param integer $id ID.
* @param string[] $setting Setting.
*
* @return bool
*/
function et_theme_builder_template_setting_validate_archive_user_id( $type, $subtype, $id, $setting ) {
return ET_Theme_Builder_Request::TYPE_AUTHOR === $type && $id === (int) $setting[3];
}
/**
* Validate archive:user:role:<role>.
*
* @since 4.0.10
*
* @param string $type Type.
* @param string $subtype Subtype.
* @param integer $id ID.
* @param string[] $setting Setting.
*
* @return bool
*/
function et_theme_builder_template_setting_validate_archive_user_role( $type, $subtype, $id, $setting ) {
$user = get_userdata( $id );
if ( ! $user ) {
return false;
}
if ( 'administrator' === $setting[3] && is_super_admin( $user->ID ) ) {
// Superadmins may:
// - have a low-level role assigned in the current site
// - not be added to the site at all
// in either case they are treated as administrators so we have to handle this edge case.
return true;
}
return ET_Theme_Builder_Request::TYPE_AUTHOR === $type && in_array( $setting[3], $user->roles, true );
}
/**
* Validate archive:date:all.
*
* @since 4.0
*
* @param string $type Type.
* @param string $subtype Subtype.
* @param integer $id ID.
* @param string[] $setting Setting.
*
* @return bool
*/
function et_theme_builder_template_setting_validate_archive_date_all( $type, $subtype, $id, $setting ) {
return ET_Theme_Builder_Request::TYPE_DATE === $type;
}
/**
* Validate search.
*
* @since 4.0
*
* @param string $type Type.
* @param string $subtype Subtype.
* @param integer $id ID.
* @param string[] $setting Setting.
*
* @return bool
*/
function et_theme_builder_template_setting_validate_search( $type, $subtype, $id, $setting ) {
return ET_Theme_Builder_Request::TYPE_SEARCH === $type;
}
/**
* Validate 404.
*
* @since 4.0
*
* @param string $type Type.
* @param string $subtype Subtype.
* @param integer $id ID.
* @param string[] $setting Setting.
*
* @return bool
*/
function et_theme_builder_template_setting_validate_404( $type, $subtype, $id, $setting ) {
return ET_Theme_Builder_Request::TYPE_404 === $type;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,256 @@
<?php
/**
* Get placeholders for WooCommerce module in Theme Builder
*
* @since 4.0.1
* @since 4.0.10 Product placeholders is initialized as TB placeholder product's default props
*
* @return array
*/
function et_theme_builder_wc_placeholders() {
return array(
'title' => esc_html__( 'Product name', 'et_builder' ),
'slug' => 'product-name',
'short_description' => esc_html__( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris bibendum eget dui sed vehicula. Suspendisse potenti. Nam dignissim at elit non lobortis.', 'et_builder' ),
'description' => esc_html__( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris bibendum eget dui sed vehicula. Suspendisse potenti. Nam dignissim at elit non lobortis. Cras sagittis dui diam, a finibus nibh euismod vestibulum. Integer sed blandit felis. Maecenas commodo ante in mi ultricies euismod. Morbi condimentum interdum luctus. Mauris iaculis interdum risus in volutpat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Praesent cursus odio eget cursus pharetra. Aliquam lacinia lectus a nibh ullamcorper maximus. Quisque at sapien pulvinar, dictum elit a, bibendum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Mauris non pellentesque urna.', 'et_builder' ),
'status' => 'publish',
'comment_status' => 'open',
);
}
/**
* Force set product's class to ET_Theme_Builder_Woocommerce_Product_Variable_Placeholder in TB's woocommerceComponent
* rendering. This product classname is specifically filled and will returned TB placeholder data
* without retrieving actual value from database
*
* @since 4.0.10
*
* @return string
*/
function et_theme_builder_wc_product_class() {
return 'ET_Theme_Builder_Woocommerce_Product_Variable_Placeholder';
}
/**
* Get review placeholder for WooCommerce module in Theme Builder. This can't be included at
* `et_theme_builder_wc_placeholders()` due to dependability on global $post value and
* `et_theme_builder_wc_placeholders()`'s returned value being cached on static variable
*
* @since 4.0.1
*
* @return object
*/
function et_theme_builder_wc_review_placeholder() {
global $post;
$review = new stdClass();
$review->comment_ID = 1;
$review->comment_author = 'John Doe';
$review->comment_author_email = 'john@doe.com';
$review->comment_date = '2019-10-15 16:13:13';
$review->comment_date_gmt = '2019-10-15 16:13:13';
$review->comment_content = 'Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cum sociis natoque penatibus et magnis dis parturient montes; nascetur ridiculus mus.';
$review->comment_post_ID = $post->ID;
$review->user_id = null;
return new WP_Comment( $review );
}
/**
* Set global objects needed to manipulate `ETBuilderBackend.currentPage.woocommerceComponents` on
* theme builder into displaying WooCommerce module placeholder (even though TB's CPT is not
* WooCommerce's product CPT)
*
* @since 4.0.1
*
* @param array $conditional_tags evaluate conditional tags when current request is AJAX request
*/
function et_theme_builder_wc_set_global_objects( $conditional_tags = array() ) {
$is_tb = et_()->array_get( $conditional_tags, 'is_tb', false );
// Check if current request is theme builder (direct page / AJAX request)
if ( ! et_builder_tb_enabled() && ! $is_tb ) {
return;
}
// Global variable that affects WC module rendering
global $product, $post, $tb_original_product, $tb_original_post, $tb_wc_post, $tb_wc_product;
// Making sure correct comment template is loaded on WC tabs' review tab
add_filter( 'comments_template', array( 'ET_Builder_Module_Woocommerce_Tabs', 'comments_template_loader' ), 20 );
// Force display related posts; technically sets all products as related
add_filter( 'woocommerce_product_related_posts_force_display', '__return_true' );
// Make sure review's form is opened
add_filter( 'comments_open', '__return_true' );
// Save original $post for reset later
$tb_original_post = $post;
// Save original $product for reset later
$tb_original_product = $product;
// If modified global existed, use it for efficiency
if ( ! is_null( $tb_wc_post ) && ! is_null( $tb_wc_product ) ) {
$post = $tb_wc_post;
$product = $tb_wc_product;
return;
}
// Get placeholders
$placeholders = et_theme_builder_wc_placeholders();
if ( $is_tb ) {
$placeholder_src = wc_placeholder_img_src( 'full' );
$placeholder_id = attachment_url_to_postid( $placeholder_src );
if ( absint( $placeholder_id ) > 0 ) {
$placeholders['gallery_image_ids'] = array( $placeholder_id );
}
} else {
$placeholders['gallery_image_ids'] = array();
}
// $post might be null if current request is computed callback (ie. WC gallery)
if ( is_null( $post ) ) {
$post = new stdClass();
}
// Overwrite $post global
$post->post_title = $placeholders['title'];
$post->post_slug = $placeholders['slug'];
$post->post_excerpt = $placeholders['short_description'];
$post->post_content = $placeholders['description'];
$post->post_status = $placeholders['status'];
$post->comment_status = $placeholders['comment_status'];
// Overwrite global $product
$product = new ET_Theme_Builder_Woocommerce_Product_Variable_Placeholder();
// Set current post ID as product's ID. `ET_Theme_Builder_Woocommerce_Product_Variable_Placeholder`
// handles all placeholder related value but product ID need to be manually set to match current
// post's ID. This is especially needed when add-ons is used and accessing get_id() method.
$product->set_id( $post->ID );
// Save modified global for later use
$tb_wc_post = $post;
$tb_wc_product = $product;
}
/**
* Reset global objects needed to manipulate `ETBuilderBackend.currentPage.woocommerceComponents`
*
* @since 4.0.1
*/
function et_theme_builder_wc_reset_global_objects() {
if ( ! et_builder_tb_enabled() ) {
return;
}
global $product, $post, $tb_original_product, $tb_original_post;
remove_filter( 'comments_template', array( 'ET_Builder_Module_Woocommerce_Tabs', 'comments_template_loader' ), 20 );
remove_filter( 'woocommerce_product_related_posts_force_display', '__return_true' );
remove_filter( 'comments_open', '__return_true' );
$post = $tb_original_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Need override the post with the theme builder post.
$product = $tb_original_product; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Need override the product with the theme builder product.
}
/**
* Modify reviews output on WooCommerce's review and tabs' review module in TB
*
* @since 4.0.1
*
* @param array $comments
*
* @return array
*/
function et_theme_builder_wc_set_review_objects( $comments ) {
// Return early if it isn't theme builder
if ( ! et_builder_tb_enabled() ) {
return $comments;
}
$placeholder = et_theme_builder_wc_review_placeholder();
// Add two placeholder reviews
$comments = array(
$placeholder,
$placeholder,
);
// When comment metadata is modified via `get_comment_metadata` filter, the $comment param
// passed into template functions is int instead of WP_Comment object which triggers
// `get_comment()` which triggers error because there's no real review/comment saved in database
// to fix it, modify cache to short-circuit and prevent full `get_comment()` execution
wp_cache_set( $placeholder->comment_ID, $placeholder, 'comment' );
return $comments;
}
// Modify review output on WooCommerce Tabs module
add_filter( 'comments_array', 'et_theme_builder_wc_set_review_objects' );
// Modify review output on WooCommerce Review module
add_filter( 'the_comments', 'et_theme_builder_wc_set_review_objects' );
/**
* Modify review rating output on WooCommerce review and tabs review module in TB
*
* @since 4.0.1
*
* @param mixed $value
* @param int $object_id
* @param string $meta_key
* @param bool $single
*
* @return mixed
*/
function et_theme_builder_wc_set_review_metadata( $value, $object_id, $meta_key, $single ) {
$is_tb = et_builder_tb_enabled();
// Modify rating metadata
if ( $is_tb && 'rating' === $meta_key ) {
global $product;
return $product->get_average_rating();
}
// Modify verified metadata
if ( $is_tb && 'verified' === $meta_key ) {
return false;
}
return $value;
}
add_filter( 'get_comment_metadata', 'et_theme_builder_wc_set_review_metadata', 10, 4 );
/**
* Filter `get_the_terms()` output for Theme Builder layout usage. `get_the_term()` is used for
* product tags and categories in WC meta module and relies on current post's ID to output product's
* tags and categories. In TB settings, post ID is irrelevant as the current layout can be used in
* various pages. Thus, simply get the first tags and cats then output it for visual preview purpose
*
* @since 4.0.10
*
* @param WP_Term[]|WP_Error $terms Array of attached terms, or WP_Error on failure.
* @param int $post_id Post ID.
* @param string $taxonomy Name of the taxonomy.
*
* @return
*/
function et_theme_builder_wc_terms( $terms, $post_id, $taxonomy ) {
// Only modify product_cat and product_tag taxonomies; This function is only called in TB's
// woocommerceComponent output for current product setting
if ( in_array( $taxonomy, array( 'product_cat', 'product_tag' ) ) && empty( $terms ) ) {
$tags = get_categories( array( 'taxonomy' => $taxonomy ) );
if ( isset( $tags[0] ) ) {
$terms = array( $tags[0] );
}
}
return $terms;
}

View File

@ -0,0 +1,127 @@
<?php
/**
* Disable language filtering of terms in TB.
*
* @since 4.2
*
* @param string $parent_id
* @param string $child_type
* @param string $child_value
*/
function et_theme_builder_wpml_disable_term_filters( $parent_id, $child_type, $child_value ) {
global $sitepress;
if ( ! $sitepress || 'taxonomy' !== $child_type ) {
return;
}
remove_filter( 'terms_clauses', array( $sitepress, 'terms_clauses' ), 10 );
remove_filter( 'get_terms_args', array( $sitepress, 'get_terms_args_filter' ), 10 );
remove_filter( 'get_term', array( $sitepress, 'get_term_adjust_id' ), 1 );
}
add_action( 'et_theme_builder_before_get_template_setting_child_options', 'et_theme_builder_wpml_disable_term_filters', 10, 3 );
/**
* Enable language filtering of terms in TB.
*
* @since 4.2
*
* @param string $parent_id
* @param string $child_type
* @param string $child_value
*/
function et_theme_builder_wpml_enable_term_filters( $parent_id, $child_type, $child_value ) {
global $sitepress;
if ( ! $sitepress || 'taxonomy' !== $child_type ) {
return;
}
add_filter( 'terms_clauses', array( $sitepress, 'terms_clauses' ), 10, 3 );
add_filter( 'get_terms_args', array( $sitepress, 'get_terms_args_filter' ), 10, 2 );
add_filter( 'get_term', array( $sitepress, 'get_term_adjust_id' ), 1, 1 );
}
add_action( 'et_theme_builder_after_get_template_setting_child_options', 'et_theme_builder_wpml_enable_term_filters', 10, 3 );
/**
* Normalize an object ID to it's base language ID if it is a translation.
*
* @since 4.2
*
* @param integer $id WPML object ID.
* @param string $type Type.
* @param string $subtype Subtype.
*
* @return integer
*/
function et_theme_builder_wpml_normalize_object_id( $id, $type, $subtype ) {
return apply_filters( 'wpml_object_id', $id, $subtype, true );
}
add_filter( 'et_theme_builder_template_setting_filter_validation_id', 'et_theme_builder_wpml_normalize_object_id', 10, 3 );
/**
* Prioritize IDs for the current active language over translated IDs
* when comparing template settings priority.
*
* @since 4.2
*
* @param string $prioritized_setting Prioritized setting.
* @param string $a First translated id.
* @param string $b Second translated id.
* @param ET_Theme_Builder_Request $request
*
* @return string
*/
function et_theme_builder_wpml_prioritize_translated_id( $prioritized_setting, $a, $b, $request ) {
$a_id = '';
$a_id_translated = '';
$b_id = '';
$b_id_translated = '';
$a_matches = array();
$b_matches = array();
// Match singular:post_type:<post_type>:id:<id>
$singular = '/^singular:post_type:([^:]+):id:(\d+)$/i';
// Match singular:post_type:<post_type>:children:id:<id>
$singular_children = '/^singular:post_type:([^:]+):children:id:(\d+)$/i';
// Match singular:taxonomy:<taxonomy>:term:id:<id>
$singular_term = '/^singular:taxonomy:([^:]+):term:id:(\d+)$/i';
// Match archive:taxonomy:<taxonomy>:term:id:<id>
$archive_term = '/^archive:taxonomy:([^:]+):term:id:(\d+)$/i';
if ( preg_match( $singular, $a, $a_matches ) && preg_match( $singular, $b, $b_matches ) ) {
$a_id = (int) $a_matches[2];
$a_id_translated = et_theme_builder_wpml_normalize_object_id( $a_id, 'post', $a_matches[1] );
$b_id = (int) $b_matches[2];
$b_id_translated = et_theme_builder_wpml_normalize_object_id( $b_id, 'post', $b_matches[1] );
} elseif ( preg_match( $singular_children, $a, $a_matches ) && preg_match( $singular_children, $b, $b_matches ) ) {
$a_id = (int) $a_matches[2];
$a_id_translated = et_theme_builder_wpml_normalize_object_id( $a_id, 'post', $a_matches[1] );
$b_id = (int) $b_matches[2];
$b_id_translated = et_theme_builder_wpml_normalize_object_id( $b_id, 'post', $b_matches[1] );
} elseif ( preg_match( $singular_term, $a, $a_matches ) && preg_match( $singular_term, $b, $b_matches ) ) {
$a_id = (int) $a_matches[2];
$a_id_translated = et_theme_builder_wpml_normalize_object_id( $a_id, 'taxonomy', $a_matches[1] );
$b_id = (int) $b_matches[2];
$b_id_translated = et_theme_builder_wpml_normalize_object_id( $b_id, 'taxonomy', $b_matches[1] );
} elseif ( preg_match( $archive_term, $a, $a_matches ) && preg_match( $archive_term, $b, $b_matches ) ) {
$a_id = (int) $a_matches[2];
$a_id_translated = et_theme_builder_wpml_normalize_object_id( $a_id, 'taxonomy', $a_matches[1] );
$b_id = (int) $b_matches[2];
$b_id_translated = et_theme_builder_wpml_normalize_object_id( $b_id, 'taxonomy', $b_matches[1] );
}
if ( $a_id && $a_id_translated && $a_id_translated === $a_id ) {
// $a is an exact match for the current request and not a translated match so we prioritize it.
return $a;
}
if ( $b_id && $b_id_translated && $b_id_translated === $b_id ) {
// $b is an exact match for the current request and not a translated match so we prioritize it.
return $b;
}
// Neither $a nor $b are exact matches so don't prioritize either.
return $prioritized_setting;
}
add_filter( 'et_theme_builder_prioritized_template_setting', 'et_theme_builder_wpml_prioritize_translated_id', 10, 6 );