array( 100 ),
'2-cols' => array( 50, 50 ),
'3-cols' => array( 33.33, 33.33, 33.33 ),
'4-cols' => array( 25, 25, 25, 25 ),
'5-cols' => array( 20, 20, 20, 20, 20 ),
'6-cols' => array( 16.65, 16.65, 16.65, 16.65, 16.65, 16.65 ),
'left-sidebar' => array( 33.33, 66.66 ),
'right-sidebar' => array( 66.66, 33.33 ),
'left-right-sidebar' => array( 25, 50, 25 ),
);
/**
* An array that contains data for each registered settings form.
*
* @since 1.0
* @var array $settings_forms
*/
static public $settings_forms = array();
/**
* An array used to cache default values for settings forms.
*
* @since 1.0
* @var array $settings_form_defaults
*/
static public $settings_form_defaults = array();
/**
* An array that instances for each registered module.
*
* @since 1.0
* @var array $modules
*/
static public $modules = array();
/**
* An array of module aliases with their own names,
* categories and default settings.
*
* @since 1.10
* @var array $module_aliases
*/
static public $module_aliases = array();
/**
* Whether the builder is active or not.
*
* @since 1.10
* @access private
* @var bool $active
*/
static private $active = null;
/**
* Cached global settings.
*
* @access private
* @var array $global_settings
*/
static private $global_settings;
/**
* The last node id that was generated by the builder.
* This is saved to ensure the next node id is unique.
*
* @since 1.0
* @access private
* @var string $last_generated_node_id
*/
static private $last_generated_node_id = null;
/**
* Cached post data from either the $_POST array
* or from the fl_builder_data post variable.
*
* @since 1.0
* @access private
* @var array $post_data
*/
static private $post_data = null;
/**
* An array of post IDs the builder will be forced to use instead
* of a post ID set in the internal $post_data array or the global
* $post->ID when calling the FLBuilderModel::get_post_id method.
*
* The first post ID in the array is always returned for the
* FLBuilderModel::get_post_id method. To get a previously set
* post ID, first call the FLBuilderModel::reset_post_id method.
*
* @since 1.10
* @access private
* @var array $post_id
*/
static private $post_id = array();
/**
* An array of cached published layout data by post_id.
*
* @since 1.0
* @access private
* @var array $published_layout_data
*/
static private $published_layout_data = array();
/**
* An array of cached draft layout data by post_id.
*
* @since 1.0
* @access private
* @var array $draft_layout_data
*/
static private $draft_layout_data = array();
/**
* An array of paths to template data files.
*
* @since 1.8
* @access private
* @var array $templates
*/
static private $templates = array();
/**
* An array of cached template data that has been
* loaded from .dat files.
*
* @since 1.10
* @access private
* @var array $template_data
*/
static private $template_data = null;
/**
* An array of cached post IDs for node templates.
*
* @since 1.7.6
* @access private
* @var array $node_template_post_ids
*/
static private $node_template_post_ids = array();
/**
* An array of cached types for user and node templates.
*
* @since 1.7.9
* @access private
* @var array $node_template_types
*/
static private $node_template_types = array();
static private $get_user_templates_cache = array();
/**
* Initialize hooks.
*
* @since 1.8
* @return void
*/
static public function init() {
/* Admin AJAX */
add_action( 'wp_ajax_fl_builder_disable', __CLASS__ . '::disable' );
add_action( 'wp_ajax_fl_builder_duplicate_wpml_layout', __CLASS__ . '::duplicate_wpml_layout' );
/* Actions */
add_action( 'init', __CLASS__ . '::load_settings', 1 );
add_action( 'init', __CLASS__ . '::load_modules', 2 );
add_action( 'before_delete_post', __CLASS__ . '::delete_post' );
add_action( 'save_post', __CLASS__ . '::save_revision', 10, 3 );
add_action( 'save_post', __CLASS__ . '::set_node_template_default_type', 10, 3 );
add_action( 'wp_restore_post_revision', __CLASS__ . '::restore_revision', 10, 2 );
add_action( 'fl_builder_after_save_layout', __CLASS__ . '::save_layout_revision' );
add_action( 'fl_builder_after_save_user_template', __CLASS__ . '::save_layout_revision' );
/* Filters */
add_filter( 'fl_builder_get_global_settings', __CLASS__ . '::filter_global_settings', 10, 1 );
add_filter( 'heartbeat_received', __CLASS__ . '::lock_post', 10, 2 );
add_filter( 'fl_builder_register_settings_form', __CLASS__ . '::filter_row_settings_for_resize', 10, 2 );
add_filter( 'wp_revisions_to_keep', __CLASS__ . '::limit_revisions', 10, 2 );
/* Core Templates */
self::register_core_templates();
}
/**
* Returns a builder edit URL for a post.
*
* @since 1.0
* @param int $post_id The post id to get an edit url for.
* @param bool $include_ui Return the full UI URL or just the layout.
* @return string
*/
static public function get_edit_url( $post_id = false, $include_ui = true ) {
if ( false === $post_id ) {
global $post;
} else {
$post = get_post( $post_id );
}
preg_match( '/(https?)/', get_bloginfo( 'url' ), $matches );
$scheme = ( isset( $matches[1] ) ) ? $matches[1] : false;
$url = set_url_scheme( get_permalink( $post->ID ), $scheme );
$url = add_query_arg( 'fl_builder', '', $url );
if ( FLBuilderUIIFrame::is_enabled() ) {
if ( $include_ui ) {
$url = add_query_arg( 'fl_builder_ui', '', $url );
} else {
/**
* Preserve any query args and pass to the iframe
*/
$args = array();
parse_str( $_SERVER['QUERY_STRING'], $args );
$args['fl_builder_ui_iframe'] = '';
unset( $args['fl_builder_ui'] );
$url = add_query_arg( $args, $url );
}
}
/**
* Filter the bb edit url.
* @see fl_get_edit_url
* @param $url url
* @param $post post object
*/
return apply_filters( 'fl_get_edit_url', $url, $post );
}
/**
* Returns the URL to upgrade the builder to the premium version.
* Can be overridden by theme developers to use their affiliate
* link using the fl_builder_upgrade_url filter.
*
* @since 1.0
* @param array $params An array of key/value params to add to the query string.
* @return string
*/
static public function get_upgrade_url( $params = array() ) {
/**
* Use this filter to modify the upgrade URL in Beaver Builder Lite.
* This can be used to add an affiliate ID.
* @see fl_builder_upgrade_url
* @link https://docs.wpbeaverbuilder.com/beaver-builder/developer/tutorials-guides/common-beaver-builder-filter-examples
*/
return apply_filters( 'fl_builder_upgrade_url', self::get_store_url( '', $params ) );
}
/**
* Returns a URL that points to the Beaver Builder store.
*
* @since 1.8.6
* @param string $path A URL path to append to the store URL.
* @param array $params An array of key/value params to add to the query string.
* @return string
*/
static public function get_store_url( $path = '', $params = array() ) {
$url = add_query_arg( $params, FL_BUILDER_STORE_URL . $path );
return apply_filters( 'fl_builder_store_url', $url, $path );
}
/**
* Returns the relative URL for the plugin folder.
*
* @since 2.3
* @return string
*/
static public function get_relative_plugin_url() {
$url = str_ireplace( home_url(), '', FLBuilder::plugin_url() );
$parsed_path = parse_url( FLBuilder::plugin_url(), PHP_URL_PATH );
if ( strstr( $url, '://' ) && $parsed_path ) {
$url = $parsed_path;
}
return $url;
}
/**
* Returns an array of post data from either $_POST['fl_builder_data']
* or $_POST if that is not set.
*
* @since 1.0
* @return array
*/
static public function get_post_data() {
if ( ! self::$post_data ) {
self::$post_data = array();
if ( isset( $_POST['fl_builder_data'] ) ) {
// Decode settings if our ModSecurity fix is enabled.
if ( isset( $_POST['fl_builder_data']['settings'] ) ) {
$_POST['fl_builder_data']['settings'] = FLBuilderUtils::modsec_fix_decode( $_POST['fl_builder_data']['settings'] );
}
if ( isset( $_POST['fl_builder_data']['node_settings'] ) ) {
$_POST['fl_builder_data']['node_settings'] = FLBuilderUtils::modsec_fix_decode( $_POST['fl_builder_data']['node_settings'] );
}
if ( isset( $_POST['fl_builder_data']['node_preview'] ) ) {
$_POST['fl_builder_data']['node_preview'] = FLBuilderUtils::modsec_fix_decode( $_POST['fl_builder_data']['node_preview'] );
}
$data = FLBuilderUtils::json_decode_deep( wp_unslash( $_POST['fl_builder_data'] ) );
foreach ( $data as $key => $val ) {
self::$post_data[ $key ] = $val;
}
} elseif ( isset( $_POST ) ) {
foreach ( $_POST as $key => $val ) {
self::$post_data[ $key ] = $val;
}
}
}
return self::$post_data;
}
/**
* Update a value in the $post_data array.
*
* @since 1.0
* @param string $key The post data key.
* @param mixed $value The value to update.
* @return void
*/
static public function update_post_data( $key, $value ) {
$post_data = self::get_post_data();
$post_data[ $key ] = $value;
self::$post_data = $post_data;
}
/**
* Return an array of post types that the builder
* is enabled to work with.
*
* @since 1.0
* @return array
*/
static public function get_post_types() {
$value = self::get_admin_settings_option( '_fl_builder_post_types', true );
if ( ! $value ) {
$value = array( 'page', 'fl-builder-template' );
} else {
$value[] = 'fl-builder-template';
}
/**
* Use this filter to modify the post types that the builder works with.
* @see fl_builder_post_types
* @link https://docs.wpbeaverbuilder.com/beaver-builder/developer/tutorials-guides/common-beaver-builder-filter-examples
*/
return apply_filters( 'fl_builder_post_types', $value );
}
/**
* Return an array of post ids that should have their
* builder assets loaded globally.
*
* @since 1.0
* @return array
*/
static public function get_global_posts() {
/**
* Use this filter to specify a post or posts whose CSS and JavaScript assets should be loaded globally.
* @link https://docs.wpbeaverbuilder.com/beaver-builder/developer/tutorials-guides/common-beaver-builder-filter-examples
* @see fl_builder_global_posts
* @since 1.0
*/
return apply_filters( 'fl_builder_global_posts', array() );
}
/**
* Adds the given post ID to the beginning of the internal $post_id
* array so the builder will be forced to use that instead of
* a post ID set in the internal $post_data array or the global $post->ID.
*
* @since 1.10
* @param int $post_id
* @return void
*/
static public function set_post_id( $post_id ) {
array_unshift( self::$post_id, $post_id );
}
/**
* Removes the first item from the internal $post_id array so the
* last set post ID is used. If the internal $post_id array is
* empty, a post ID set in the internal $post_data array or the global
* $post->ID will be used when calling FLBuilderModel::get_post_id.
*
* @since 1.10
* @return void
*/
static public function reset_post_id() {
array_shift( self::$post_id );
}
/**
* Returns the post id for the current post that
* is being displayed or worked on.
*
* @since 1.0
* @param bool $force_globals Force the use of WP globals instead of checking our internal post ID.
* @return int|bool The post id or false.
*/
static public function get_post_id( $force_globals = false ) {
// Check our internal post IDs first if we're not forced to use WP globals.
if ( ! $force_globals ) {
$post_data = self::get_post_data();
if ( ! empty( self::$post_id ) ) {
// Get a post ID from the internal $post_id array if not empty.
return (int) self::$post_id[0];
} elseif ( isset( $post_data['post_id'] ) ) {
// Get a post ID from an AJAX request.
return (int) $post_data['post_id'];
}
}
// Check WP globals.
global $wp_the_query;
global $post;
if ( in_the_loop() && is_main_query() && isset( $wp_the_query->post ) && $wp_the_query->post instanceof WP_Post ) {
// Get a post ID from the main query.
return (int) $wp_the_query->post->ID;
} elseif ( $post instanceof WP_Post ) {
// Get a post ID in a query outside of the main loop.
return (int) $post->ID;
}
// No post ID found.
return false;
}
/**
* Returns the post object for the current post that
* is being worked on.
*
* @since 1.6.3
* @return object
*/
static public function get_post() {
return get_post( self::get_post_id() );
}
/**
* Checks to see if the site has SSL enabled or not.
*
* @since 1.0
* @return bool
*/
static public function is_ssl() {
if ( is_ssl() ) {
return true;
} elseif ( 0 === stripos( get_option( 'siteurl' ), 'https://' ) ) {
return true;
} elseif ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' == $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
return true;
}
return false;
}
/**
* Checks to see if the builder can be enabled for
* the current post in the main query.
*
* @since 1.0
* @return bool
*/
static public function is_post_editable() {
global $wp_the_query;
$editable = false;
if ( is_singular() && isset( $wp_the_query->post ) ) {
$post = $wp_the_query->post;
$post_types = self::get_post_types();
$user_can = current_user_can( 'edit_post', $post->ID );
$user_access = FLBuilderUserAccess::current_user_can( 'builder_access' );
if ( in_array( $post->post_type, $post_types ) && $user_can && $user_access ) {
$editable = true;
}
}
/**
* Checks to see if the builder can be enabled for
* the current post in the main query.
* @see fl_builder_is_post_editable
*/
return (bool) apply_filters( 'fl_builder_is_post_editable', $editable );
}
/**
* Filter global settings.
*
* @return object
*/
static public function filter_global_settings( $new_settings ) {
// row margin.
if ( isset( $new_settings->row_margins ) ) {
$new_settings->row_margins_top = $new_settings->row_margins;
$new_settings->row_margins_right = $new_settings->row_margins;
$new_settings->row_margins_bottom = $new_settings->row_margins;
$new_settings->row_margins_left = $new_settings->row_margins;
unset( $new_settings->row_margins );
}
if ( isset( $new_settings->row_margins_medium ) ) {
$new_settings->row_margins_top_medium = $new_settings->row_margins_medium;
$new_settings->row_margins_right_medium = $new_settings->row_margins_medium;
$new_settings->row_margins_bottom_medium = $new_settings->row_margins_medium;
$new_settings->row_margins_left_medium = $new_settings->row_margins_medium;
unset( $new_settings->row_margins_medium );
}
if ( isset( $new_settings->row_margins_responsive ) ) {
$new_settings->row_margins_top_responsive = $new_settings->row_margins_responsive;
$new_settings->row_margins_right_responsive = $new_settings->row_margins_responsive;
$new_settings->row_margins_bottom_responsive = $new_settings->row_margins_responsive;
$new_settings->row_margins_left_responsive = $new_settings->row_margins_responsive;
unset( $new_settings->row_margins_responsive );
}
// row padding.
if ( isset( $new_settings->row_padding ) ) {
$new_settings->row_padding_top = $new_settings->row_padding;
$new_settings->row_padding_right = $new_settings->row_padding;
$new_settings->row_padding_bottom = $new_settings->row_padding;
$new_settings->row_padding_left = $new_settings->row_padding;
unset( $new_settings->row_padding );
}
if ( isset( $new_settings->row_padding_medium ) ) {
$new_settings->row_padding_top_medium = $new_settings->row_padding_medium;
$new_settings->row_padding_right_medium = $new_settings->row_padding_medium;
$new_settings->row_padding_bottom_medium = $new_settings->row_padding_medium;
$new_settings->row_padding_left_medium = $new_settings->row_padding_medium;
unset( $new_settings->row_padding_medium );
}
if ( isset( $new_settings->row_padding_responsive ) ) {
$new_settings->row_padding_top_responsive = $new_settings->row_padding_responsive;
$new_settings->row_padding_right_responsive = $new_settings->row_padding_responsive;
$new_settings->row_padding_bottom_responsive = $new_settings->row_padding_responsive;
$new_settings->row_padding_left_responsive = $new_settings->row_padding_responsive;
unset( $new_settings->row_padding_responsive );
}
// column margin.
if ( isset( $new_settings->column_margins ) ) {
$new_settings->column_margins_top = $new_settings->column_margins;
$new_settings->column_margins_right = $new_settings->column_margins;
$new_settings->column_margins_bottom = $new_settings->column_margins;
$new_settings->column_margins_left = $new_settings->column_margins;
unset( $new_settings->column_margins );
}
if ( isset( $new_settings->column_margins_medium ) ) {
$new_settings->column_margins_top_medium = $new_settings->column_margins_medium;
$new_settings->column_margins_right_medium = $new_settings->column_margins_medium;
$new_settings->column_margins_bottom_medium = $new_settings->column_margins_medium;
$new_settings->column_margins_left_medium = $new_settings->column_margins_medium;
unset( $new_settings->column_margins_medium );
}
if ( isset( $new_settings->column_margins_responsive ) ) {
$new_settings->column_margins_top_responsive = $new_settings->column_margins_responsive;
$new_settings->column_margins_right_responsive = $new_settings->column_margins_responsive;
$new_settings->column_margins_bottom_responsive = $new_settings->column_margins_responsive;
$new_settings->column_margins_left_responsive = $new_settings->column_margins_responsive;
unset( $new_settings->column_margins_responsive );
}
// column padding.
if ( isset( $new_settings->column_padding ) ) {
$new_settings->column_padding_top = $new_settings->column_padding;
$new_settings->column_padding_right = $new_settings->column_padding;
$new_settings->column_padding_bottom = $new_settings->column_padding;
$new_settings->column_padding_left = $new_settings->column_padding;
unset( $new_settings->column_padding );
}
if ( isset( $new_settings->column_padding_medium ) ) {
$new_settings->column_padding_top_medium = $new_settings->column_padding_medium;
$new_settings->column_padding_right_medium = $new_settings->column_padding_medium;
$new_settings->column_padding_bottom_medium = $new_settings->column_padding_medium;
$new_settings->column_padding_left_medium = $new_settings->column_padding_medium;
unset( $new_settings->column_padding_medium );
}
if ( isset( $new_settings->column_padding_responsive ) ) {
$new_settings->column_padding_top_responsive = $new_settings->column_padding_responsive;
$new_settings->column_padding_right_responsive = $new_settings->column_padding_responsive;
$new_settings->column_padding_bottom_responsive = $new_settings->column_padding_responsive;
$new_settings->column_padding_left_responsive = $new_settings->column_padding_responsive;
unset( $new_settings->column_padding_responsive );
}
// module margin.
if ( isset( $new_settings->module_margins ) ) {
$new_settings->module_margins_top = $new_settings->module_margins;
$new_settings->module_margins_right = $new_settings->module_margins;
$new_settings->module_margins_bottom = $new_settings->module_margins;
$new_settings->module_margins_left = $new_settings->module_margins;
unset( $new_settings->module_margins );
}
if ( isset( $new_settings->module_margins_medium ) ) {
$new_settings->module_margins_top_medium = $new_settings->module_margins_medium;
$new_settings->module_margins_right_medium = $new_settings->module_margins_medium;
$new_settings->module_margins_bottom_medium = $new_settings->module_margins_medium;
$new_settings->module_margins_left_medium = $new_settings->module_margins_medium;
unset( $new_settings->module_margins_medium );
}
if ( isset( $new_settings->module_margins_responsive ) ) {
$new_settings->module_margins_top_responsive = $new_settings->module_margins_responsive;
$new_settings->module_margins_right_responsive = $new_settings->module_margins_responsive;
$new_settings->module_margins_bottom_responsive = $new_settings->module_margins_responsive;
$new_settings->module_margins_left_responsive = $new_settings->module_margins_responsive;
unset( $new_settings->module_margins_responsive );
}
return $new_settings;
}
/**
* Called by the heartbeat API. Lock the current post
* so only the current user can edit it.
*
* @since 1.0
* @return void
*/
static public function lock_post( $response, $data ) {
if ( isset( $data['fl_builder_post_lock'] ) ) {
require_once ABSPATH . 'wp-admin/includes/post.php';
wp_set_post_lock( $data['fl_builder_post_lock']['post_id'] );
}
return $response;
}
/**
* Checks to see if the builder layout is enabled
* for the current post.
*
* @since 1.0
* @param int $post_id A post ID to check otherwise, self::get_post_id will be used.
* @return bool
*/
static public function is_builder_enabled( $post_id = null ) {
global $wp_the_query;
// If in iframe preview return true as the post might not be a draft yet.
if ( self::is_builder_draft_preview() ) {
return true;
}
$query_id = ( isset( $wp_the_query->post->ID ) ) ? $wp_the_query->post->ID : false;
$post_id = $post_id ? $post_id : self::get_post_id();
if ( ! is_admin() && post_password_required( $post_id ) ) {
return false;
} elseif ( self::is_builder_active() && $query_id === $post_id ) {
return true;
} else {
$post_types = self::get_post_types();
$post = get_post( $post_id );
if ( $post && in_array( $post->post_type, $post_types ) ) {
return get_post_meta( $post->ID, '_fl_builder_enabled', true );
}
}
return false;
}
/**
* Checks to see if the builder UI is active for
* the current post in the main query.
*
* @since 1.0
* @return bool
*/
static public function is_builder_active() {
global $wp_the_query;
global $post;
$query_id = ( isset( $wp_the_query->post->ID ) ) ? $wp_the_query->post->ID : false;
$post_id = ( isset( $post->ID ) ) ? $post->ID : false;
if ( null !== self::$active ) {
return apply_filters( 'fl_builder_model_is_builder_active', self::$active );
} elseif ( ! is_admin() && is_singular() && $query_id != $post_id ) {
self::$active = false;
} elseif ( is_customize_preview() ) {
self::$active = false;
} elseif ( self::is_post_editable() && ! is_admin() && ! post_password_required() ) {
$post_data = self::get_post_data();
self::$active = isset( $_GET['fl_builder'] ) || isset( $post_data['fl_builder'] );
}
return apply_filters( 'fl_builder_model_is_builder_active', self::$active );
}
/**
* Returns if this is a draft layout preview or not.
*
* @since 2.1
* @return bool
*/
static public function is_builder_draft_preview() {
return is_user_logged_in() && isset( $_GET['fl_builder_preview'] );
}
/**
* Checks to see if this is the first time
* a user has launched the builder.
*
* @since 1.4.9
* @return bool
*/
static public function is_new_user() {
if ( self::is_builder_active() ) {
if ( FLBuilderUIIFrame::is_enabled() && ! FLBuilderUIIFrame::is_iframe_request() ) {
return false;
}
$current_user = wp_get_current_user();
$launched = get_user_meta( $current_user->ID, '_fl_builder_launched', true );
if ( empty( $launched ) ) {
update_user_meta( $current_user->ID, '_fl_builder_launched', 1 );
return true;
}
}
return false;
}
/**
* Gets the status to use for working with nodes in
* the database. Returns draft if the builder is active,
* otherwise it returns published.
*
* @since 1.0
* @return string
*/
static public function get_node_status() {
$status = self::is_builder_active() ? 'draft' : 'published';
/**
* @see fl_builder_node_status
*/
return apply_filters( 'fl_builder_node_status', $status );
}
/**
* Enable the builder layout for the current post.
*
* @since 1.0
* @return void
*/
static public function enable() {
update_post_meta( self::get_post_id(), '_fl_builder_enabled', true );
}
/**
* Disable the builder layout for the current post.
*
* @since 1.0
* @return void
*/
static public function disable() {
if ( isset( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'fl-enable-editor' ) ) {
update_post_meta( self::get_post_id(), '_fl_builder_enabled', false );
}
exit;
}
/**
* Enable the builder editor for the main post in the query.
*
* @since 1.0
* @return void
*/
static public function enable_editing() {
global $wp_the_query;
if ( self::is_post_editable() && is_object( $wp_the_query->post ) ) {
$post = $wp_the_query->post;
$published = self::get_layout_data( 'published' );
$draft = self::get_layout_data( 'draft' );
/**
* Original post content from database
* @see fl_builder_migrated_post_content
*/
$content = apply_filters( 'fl_builder_migrated_post_content', $post->post_content );
// Migrate existing post content to the builder?
if ( empty( $published ) && empty( $draft ) && ! empty( $content ) ) {
$row = self::add_row();
$cols = self::get_nodes( 'column' );
$col = array_shift( $cols );
$settings = self::get_module_defaults( 'rich-text' );
$settings->text = $content;
self::add_module( 'rich-text', $settings, $col->node );
} elseif ( empty( $draft ) ) {
self::update_layout_data( $published, 'draft', $post->ID );
self::update_layout_settings( self::get_layout_settings( 'published' ), 'draft', $post->ID );
}
// Delete old draft asset cache.
self::delete_asset_cache();
// Lock the post.
require_once ABSPATH . 'wp-admin/includes/post.php';
wp_set_post_lock( $post->ID );
/**
* Allow devs to hook into when editing is enabled.
* @see fl_builder_editing_enabled
*/
do_action( 'fl_builder_editing_enabled' );
}
}
/**
* Returns an array of paths for the upload directory
* of the current site.
*
* @since 1.0
* @return array
*/
static public function get_upload_dir() {
$wp_info = wp_upload_dir( null, false );
$dir_name = basename( FL_BUILDER_DIR );
// We use bb-plugin for the lite version as well.
if ( 'beaver-builder-lite-version' == $dir_name ) {
$dir_name = 'bb-plugin';
}
// SSL workaround.
if ( self::is_ssl() ) {
$wp_info['baseurl'] = str_ireplace( 'http://', 'https://', $wp_info['baseurl'] );
}
// Build the paths.
$dir_info = array(
'path' => $wp_info['basedir'] . '/' . $dir_name . '/',
'url' => $wp_info['baseurl'] . '/' . $dir_name . '/',
);
// Create the upload dir if it doesn't exist.
if ( ! fl_builder_filesystem()->file_exists( $dir_info['path'] ) ) {
// Create the directory.
fl_builder_filesystem()->mkdir( $dir_info['path'] );
// Add an index file for security.
fl_builder_filesystem()->file_put_contents( $dir_info['path'] . 'index.html', '' );
}
/**
* Use this filter to modify the upload directory path and URL that the builder uses to store things like the cache and custom icons.
* @see fl_builder_get_upload_dir
* @link https://docs.wpbeaverbuilder.com/beaver-builder/developer/tutorials-guides/common-beaver-builder-filter-examples
*/
return apply_filters( 'fl_builder_get_upload_dir', $dir_info );
}
/**
* Returns an array of paths for the cache directory
* of the current site.
*
* @since 1.0
* @param string $name The name of the cache directory to get paths for.
* @return array
*/
static public function get_cache_dir( $name = 'cache' ) {
$upload_info = self::get_upload_dir();
$allowed = array( 'cache', 'icons' );
// Make sure the dir name is allowed.
if ( ! in_array( $name, $allowed ) ) {
return false;
}
// Build the paths.
$dir_info = array(
'path' => $upload_info['path'] . $name . '/',
'url' => $upload_info['url'] . $name . '/',
);
// Create the cache dir if it doesn't exist.
if ( ! fl_builder_filesystem()->file_exists( $dir_info['path'] ) ) {
// Create the directory.
fl_builder_filesystem()->mkdir( $dir_info['path'] );
// Add an index file for security.
fl_builder_filesystem()->file_put_contents( $dir_info['path'] . 'index.html', '' );
}
/**
* Use this filter to modify the cache directory path and URL that the builder uses to store cached images, JavaScript, and CSS files.
* @link https://docs.wpbeaverbuilder.com/beaver-builder/developer/tutorials-guides/common-beaver-builder-filter-examples
* @see fl_builder_get_cache_dir
*/
return apply_filters( 'fl_builder_get_cache_dir', $dir_info );
}
/**
* Returns the version number to be applied to the query string
* of a CSS or JS asset. If the builder is active a random hash
* is returned to prevent caching, otherwise a hash of the post
* update time is returned.
*
* @since 1.0
* @return string
*/
static public function get_asset_version( $path = false ) {
$post_id = self::get_post_id();
$active = self::is_builder_active();
$preview = self::is_builder_draft_preview();
if ( $active || $preview ) {
return md5( FLBuilderModel::uniqid() );
} else {
return $path ? md5( file_get_contents( $path ) ) : md5( get_post_modified_time( 'U', false, $post_id ) );
}
}
/**
* Returns an array of paths for the CSS and JS assets
* of the current post.
*
* @since 1.0
* @return array
*/
static public function get_asset_info() {
$post_data = self::get_post_data();
$post_id = self::get_post_id();
$cache_dir = self::get_cache_dir();
$active = self::is_builder_active();
$preview = self::is_builder_draft_preview();
/**
*
* @see fl_builder_get_asset_info_post_id
* @since 2.2.5
*/
$post_id = apply_filters( 'fl_builder_get_asset_info_post_id', $post_id, $post_data, $active, $preview );
if ( isset( $post_data['node_preview'] ) ) {
$suffix = '-layout-preview';
} elseif ( $active || $preview ) {
$suffix = '-layout-draft';
} else {
$suffix = '-layout';
}
$info = array(
'css' => $cache_dir['path'] . $post_id . $suffix . '.css',
'css_url' => $cache_dir['url'] . $post_id . $suffix . '.css',
'css_partial' => $cache_dir['path'] . $post_id . $suffix . '-partial.css',
'css_partial_url' => $cache_dir['url'] . $post_id . $suffix . '-partial.css',
'js' => $cache_dir['path'] . $post_id . $suffix . '.js',
'js_url' => $cache_dir['url'] . $post_id . $suffix . '.js',
'js_partial' => $cache_dir['path'] . $post_id . $suffix . '-partial.js',
'js_partial_url' => $cache_dir['url'] . $post_id . $suffix . '-partial.js',
);
return $info;
}
/**
* Returns the method used to enqueue layout css and js assets.
* Possible values are 'file' and 'inline'. By default, the file
* method is used. Return true for the fl_builder_render_assets_inline
* filter to enable inline enqueuing.
*
* @since 2.1.5
* @return string
*/
static public function get_asset_enqueue_method() {
/**
* Should assets be rendered inline
* @since 2.1.5
* @see fl_builder_render_assets_inline
*/
return apply_filters( 'fl_builder_render_assets_inline', false ) ? 'inline' : 'file';
}
/**
* Deletes either the preview, draft or live CSS and/or JS asset cache
* for the current post based on the data returned from get_asset_info.
* Both the CSS and JS asset cache will be delete if a type is not specified.
*
* @since 1.0
* @param string $type The type of cache to delete. Either css or js.
* @return void
*/
static public function delete_asset_cache( $type = false ) {
if ( 'inline' === FLBuilderModel::get_asset_enqueue_method() ) {
return false;
}
$info = self::get_asset_info();
$types = $type ? array( $type ) : array( 'css', 'css_partial', 'js', 'js_partial' );
foreach ( $types as $type ) {
if ( isset( $info[ $type ] ) && fl_builder_filesystem()->file_exists( $info[ $type ] ) ) {
fl_builder_filesystem()->unlink( $info[ $type ] );
}
}
}
/**
* Deletes preview, draft and live CSS/JS asset cache for the current
* post. If a post ID is supplied, the asset cache will be deleted for
* that post instead.
*
* @since 1.0
* @param int $post_id
* @return void
*/
static public function delete_all_asset_cache( $post_id = false ) {
$post_id = $post_id ? $post_id : self::get_post_id();
$cache_dir = self::get_cache_dir();
if ( $post_id && 'file' === FLBuilderModel::get_asset_enqueue_method() ) {
$paths = array(
$cache_dir['path'] . $post_id . '-layout.css',
$cache_dir['path'] . $post_id . '-layout-draft.css',
$cache_dir['path'] . $post_id . '-layout-preview.css',
$cache_dir['path'] . $post_id . '-layout-partial.css',
$cache_dir['path'] . $post_id . '-layout-draft-partial.css',
$cache_dir['path'] . $post_id . '-layout-preview-partial.css',
$cache_dir['path'] . $post_id . '-layout.js',
$cache_dir['path'] . $post_id . '-layout-draft.js',
$cache_dir['path'] . $post_id . '-layout-preview.js',
$cache_dir['path'] . $post_id . '-layout-partial.js',
$cache_dir['path'] . $post_id . '-layout-draft-partial.js',
$cache_dir['path'] . $post_id . '-layout-preview-partial.js',
);
foreach ( $paths as $path ) {
if ( fl_builder_filesystem()->file_exists( $path ) ) {
fl_builder_filesystem()->unlink( $path );
}
}
}
}
/**
* Deletes the asset cache for all posts that contain the node
* template with the supplied post ID.
*
* @since 1.6.3
* @param int $post_id
* @return void
*/
static public function delete_node_template_asset_cache( $post_id = false ) {
$posts = self::get_posts_with_global_node_template( $post_id );
if ( ! empty( $posts ) ) {
foreach ( $posts as $post ) {
self::delete_all_asset_cache( $post->ID );
}
}
}
/**
* Deletes preview, draft and live CSS/JS asset cache for all posts.
*
* @since 1.6.3
* @return void
*/
static public function delete_asset_cache_for_all_posts( $parts = '*' ) {
$cache_dir = self::get_cache_dir();
$css = glob( $cache_dir['path'] . $parts . '.css' );
$js = glob( $cache_dir['path'] . $parts . '.js' );
if ( is_array( $css ) ) {
array_map( array( fl_builder_filesystem(), 'unlink' ), $css );
}
if ( is_array( $js ) ) {
array_map( array( fl_builder_filesystem(), 'unlink' ), $js );
}
}
/**
* Generates a unique id for a builder node such as a
* row, column or module.
*
* @since 1.0
* @return string
*/
static public function generate_node_id() {
$node_id = FLBuilderModel::uniqid();
if ( $node_id == self::$last_generated_node_id ) {
return self::generate_node_id();
}
self::$last_generated_node_id = $node_id;
return $node_id;
}
/**
* Generates new node ids for an array of nodes.
*
* @since 1.0
* @param array $data An array of node data.
* @return array
*/
static public function generate_new_node_ids( $data ) {
$map = array();
$nodes = array();
// Map the new node ids to the old.
foreach ( $data as $node_id => $node ) {
$map[ $node_id ] = self::generate_node_id();
}
// Replace the old node ids.
foreach ( $data as $node_id => $node ) {
$nodes[ $map[ $node_id ] ] = $node;
$nodes[ $map[ $node_id ] ]->node = $map[ $node_id ];
if ( ! empty( $node->parent ) && isset( $map[ $node->parent ] ) ) {
$nodes[ $map[ $node_id ] ]->parent = $map[ $node->parent ];
}
}
return $nodes;
}
/**
* Returns a single node.
*
* @since 1.0
* @param string|object $node_id Either a node id or node object.
* @param string $status The node status. Either draft or published.
* @return object
*/
static public function get_node( $node_id = null, $status = null ) {
if ( is_object( $node_id ) ) {
$node = $node_id;
} else {
$data = self::get_layout_data( $status );
$node = isset( $data[ $node_id ] ) ? $data[ $node_id ] : null;
}
if ( $node && ! empty( $node->settings ) ) {
$node->settings = self::get_node_settings( $node );
}
return $node;
}
/**
* Returns an array of nodes.
*
* @since 1.0
* @param string $type The type of nodes to return.
* @param string|object $parent_id Either the parent node id or parent node object.
* @param string $status The node status. Either draft or published.
* @return array
*/
static public function get_nodes( $type = null, $parent_id = null, $status = null ) {
$parent = is_object( $parent_id ) ? $parent_id : self::get_node( $parent_id );
$nodes = array();
// Get the layout data.
if ( ! $parent ) {
$data = self::get_layout_data( $status );
} else {
$data = self::get_child_nodes( $parent, $status );
}
// Return all nodes?
if ( ! $type ) {
$nodes = $data;
} else {
foreach ( $data as $node_id => $node ) {
if ( $node->type == $type ) {
$nodes[ $node_id ] = $node;
}
}
}
// Sort the nodes by position.
uasort( $nodes, array( 'FLBuilderModel', 'order_nodes' ) );
// Merge default settings.
foreach ( $nodes as $node_id => $node ) {
if ( ! empty( $node->settings ) ) {
$nodes[ $node_id ]->settings = self::get_node_settings( $nodes[ $node_id ] );
}
}
// Return the nodes.
return $nodes;
}
/**
* Returns the direct parent object for a single node.
*
* @since 1.9
* @param string|object $node_id Either a node id or node object.
* @param string $status The node status. Either draft or published.
* @return object
*/
static public function get_node_parent( $node_id = null, $status = null ) {
$parent = null;
if ( is_object( $node_id ) ) {
$node = $node_id;
} else {
$node = self::get_node( $node_id, $status );
}
if ( $node ) {
$template_post_id = self::is_node_global( $node );
$post_id = $template_post_id ? $template_post_id : self::get_post_id();
$data = self::get_layout_data( $status, $post_id );
if ( isset( $data[ $node->parent ] ) ) {
return $data[ $node->parent ];
}
}
return $parent;
}
/**
* Returns a node's parent node of the specified type.
*
* @since 1.8.3
* @param string|object $node The node ID. Can also be a node object.
* @param string $type The type of parent to return. Either "column", "column-group" or "row".
* @return object The parent node.
*/
static public function get_node_parent_by_type( $node, $type = '' ) {
// Get node object if node ID set
if ( ! is_object( $node ) ) {
$node = self::get_node( $node );
}
// Return early if no node object found or node has no parent
if ( empty( $node ) || empty( $node->parent ) ) {
return;
}
// Helper array of parent types and their categories for each node type
$parent_types = array(
'module' => array(
'type' => 'column',
'category' => 'columns',
),
'column' => array(
'type' => 'column-group',
'category' => 'groups',
),
'column-group' => array(
'type' => 'row',
'category' => 'rows',
),
);
// Helper array of node type hierarchies
$hierarchy = array(
'module' => 10,
'column' => 20,
'column-group' => 30,
'row' => 40,
);
// Set immediate parent type of the node when:
// - type is not of allowed types
// - type is the same as node type
// - type is lower in hierarchy than the node type
if ( ! in_array( $type, array_keys( $hierarchy ) ) || $type == $node->type || $hierarchy[ $parent_types[ $node->type ]['type'] ] > $hierarchy[ $type ] ) {
$type = $parent_types[ $node->type ]['type'];
}
// Get all layout nodes, categorized
$nodes = array_filter( self::get_categorized_nodes() );
// Null out the output initially
$output = '';
// Parse layout nodes to get the correct output
if ( ! empty( $nodes ) ) {
while ( empty( $output ) ) {
if ( ! empty( $node->parent ) && isset( $nodes[ $parent_types[ $node->type ]['category'] ] ) ) {
$break_while = true;
foreach ( $nodes[ $parent_types[ $node->type ]['category'] ] as $parent ) {
if ( $parent->node == $node->parent ) {
$break_while = false;
if ( $parent_types[ $node->type ]['type'] == $type ) {
// We have got the type we wanted! Set the output and break from while and foreach loops.
$output = $parent;
break; // From foreach
}
// We now need node parents to crawl the tree
$node = $parent;
break; // From foreach
}
}
// If we get this far without changing $break_while, something is wrong
if ( $break_while ) {
break; // From while
}
} else {
break; // From while
}
}
}
return $output;
}
/**
* Returns an array of child nodes for a parent.
*
* @since 1.0
* @param string|object $parent_id Either the parent node id or parent node object.
* @param string $status The node status. Either draft or published.
* @return array
*/
static public function get_child_nodes( $parent_id, $status = null ) {
$parent = is_object( $parent_id ) ? $parent_id : self::get_node( $parent_id );
$template_post_id = self::is_node_global( $parent );
$template_node_id = null;
$status = $template_post_id && ! self::is_post_node_template() ? 'published' : $status;
$data = self::get_layout_data( $status, $template_post_id );
$nodes = array();
if ( $template_post_id ) {
$template_node_id = apply_filters( 'fl_builder_parent_template_node_id', $parent->template_node_id, $parent, $data );
}
if ( is_object( $parent ) ) {
foreach ( $data as $node_id => $node ) {
if ( ( isset( $node->parent ) && $node->parent == $parent->node )
|| ( $template_node_id && $template_node_id == $node->parent ) ) {
$nodes[ $node_id ] = $node;
}
}
}
return $nodes;
}
/**
* Returns all child nodes and children of those children
* for a single node.
*
* @since 1.6.3
* @param string $parent_id The parent node id.
* @return array
*/
static public function get_nested_nodes( $parent_id ) {
$children = self::get_child_nodes( $parent_id );
foreach ( $children as $child_id => $child ) {
$grand_children = self::get_child_nodes( $child_id );
if ( count( $grand_children ) > 0 ) {
$children = array_merge( $children, $grand_children );
foreach ( $grand_children as $grand_child_id => $grand_child ) {
$nested = self::get_nested_nodes( $grand_child_id );
if ( count( $nested ) > 0 ) {
$children = array_merge( $children, $nested );
}
}
}
}
return $children;
}
/**
* Returns an array of all nodes for a layout, categorized by type.
*
* @since 1.6.3
* @return array
*/
static public function get_categorized_nodes() {
// global $get_categorized_nodes;
$nodes = array(
'rows' => array(),
'groups' => array(),
'columns' => array(),
'modules' => array(),
);
// if ( ! empty( $get_categorized_nodes ) ) {
// return $get_categorized_nodes;
// }
if ( self::is_post_user_template( 'module' ) ) {
$nodes['modules'] = self::get_all_modules();
} elseif ( self::is_post_user_template( 'column' ) ) {
$root_col = self::get_node_template_root( 'column' );
$nodes['columns'][ $root_col->node ] = $root_col;
$col_children = self::get_nodes( null, $root_col );
foreach ( $col_children as $col_child ) {
if ( 'module' == $col_child->type ) {
$module = self::get_module( $col_child );
if ( $module ) {
$nodes['modules'][ $col_child->node ] = $module;
}
} elseif ( 'column-group' == $col_child->type ) {
$nodes['groups'][ $col_child->node ] = $col_child;
$group_cols = self::get_nodes( 'column', $col_child );
foreach ( $group_cols as $group_col ) {
$nodes['columns'][ $group_col->node ] = $group_col;
$modules = self::get_modules( $group_col );
foreach ( $modules as $module ) {
$nodes['modules'][ $module->node ] = $module;
}
}
}
}
} else {
$rows = self::get_nodes( 'row' );
foreach ( $rows as $row ) {
$nodes['rows'][ $row->node ] = $row;
$groups = self::get_nodes( 'column-group', $row );
foreach ( $groups as $group ) {
$nodes['groups'][ $group->node ] = $group;
$cols = self::get_nodes( 'column', $group );
foreach ( $cols as $col ) {
$nodes['columns'][ $col->node ] = $col;
$col_children = self::get_nodes( null, $col );
foreach ( $col_children as $col_child ) {
if ( 'module' == $col_child->type ) {
$module = self::get_module( $col_child );
if ( $module ) {
$nodes['modules'][ $col_child->node ] = $module;
}
} elseif ( 'column-group' == $col_child->type ) {
$nodes['groups'][ $col_child->node ] = $col_child;
$group_cols = self::get_nodes( 'column', $col_child );
foreach ( $group_cols as $group_col ) {
$nodes['columns'][ $group_col->node ] = $group_col;
$modules = self::get_modules( $group_col );
foreach ( $modules as $module ) {
$nodes['modules'][ $module->node ] = $module;
}
}
}
}
}
}
}
}
// $get_categorized_nodes = $nodes;
return $nodes;
}
/**
* Returns node settings that are merged with the
* default or preview settings.
*
* @since 1.0
* @param object|string $node A node object or node ID.
* @param bool $filter Whether to filter the settings or not.
* @return object
*/
static public function get_node_settings( $node, $filter = true ) {
$node = is_object( $node ) ? $node : self::get_node( $node );
$post_data = self::get_post_data();
// Get the node settings for a node template's root node?
if ( self::is_node_template_root( $node ) && ! self::is_post_node_template( false, $node->type ) ) {
$template_post_id = self::get_node_template_post_id( $node->template_id );
$template_data = self::get_layout_data( 'published', $template_post_id );
// Fallback to draft data if we don't have published data.
if ( ! isset( $template_data[ $node->template_node_id ] ) ) {
$template_data = self::get_layout_data( 'draft', $template_post_id );
}
// Set the node settings to the template node settings.
if ( isset( $template_data[ $node->template_node_id ] ) ) {
$template_node = $template_data[ $node->template_node_id ];
$template_settings = clone $template_node->settings;
if ( 'column' == $node->type ) {
$template_settings->size = $node->settings->size;
}
$node->settings = $template_settings;
}
}
// Get either the preview settings or saved node settings merged with the defaults.
if ( isset( $post_data['node_preview'] ) && isset( $post_data['node_id'] ) && $post_data['node_id'] == $node->node ) {
if ( ! isset( $post_data['node_preview_processed_settings'] ) ) {
$settings = $post_data['node_preview'];
$settings = (object) array_merge( (array) $node->settings, (array) $settings );
$settings = self::process_node_settings( $node, $settings );
self::update_post_data( 'node_preview_processed_settings', $settings );
} else {
$settings = $post_data['node_preview_processed_settings'];
}
} else {
$settings = self::get_node_settings_with_defaults_merged( $node->type, $node->settings );
}
return ! $filter ? $settings : apply_filters( 'fl_builder_node_settings', $settings, $node );
}
/**
* Returns node settings that are merged with the defaults. In general,
* you should use get_node_settings instead of this method unless you
* don't want any of the other logic that it applies.
*
* @since 2.2
* @param string $type A node type.
* @param object $settings A node settings object.
* @return object
*/
static public function get_node_settings_with_defaults_merged( $type, $settings ) {
$defaults = array();
if ( 'row' == $type ) {
$defaults = FLBuilderModel::get_row_defaults();
} elseif ( 'column' == $type ) {
$defaults = FLBuilderModel::get_col_defaults();
} elseif ( 'module' == $type ) {
$defaults = FLBuilderModel::get_module_defaults( $settings->type );
}
$settings = (object) array_merge( (array) $defaults, (array) $settings );
if ( 'row' == $type ) {
$settings = FLBuilderModel::merge_nested_form_defaults( 'general', 'row', $settings );
} elseif ( 'column' == $type ) {
$settings = FLBuilderModel::merge_nested_form_defaults( 'general', 'col', $settings );
} elseif ( 'module' == $type ) {
$settings = FLBuilderModel::merge_nested_module_defaults( $settings->type, $settings );
}
return $settings;
}
/**
* Returns node settings that have been processed with
* specific logic based on the type of node.
*
* @since 1.0
* @param object $node A node object.
* @param object $new_settings The new node settings.
* @return object
*/
static public function process_node_settings( $node, $new_settings ) {
if ( 'row' == $node->type ) {
$new_settings = self::process_row_settings( $node, $new_settings );
$new_settings = self::sanitize_settings( $new_settings, 'row', 'general' );
}
if ( 'column' == $node->type ) {
$new_settings = self::process_col_settings( $node, $new_settings );
$new_settings = self::sanitize_settings( $new_settings, 'col', 'general' );
}
if ( 'module' == $node->type ) {
$new_settings = self::process_module_settings( $node, $new_settings );
$new_settings = self::sanitize_settings( $new_settings, $node->settings->type, 'module' );
}
return $new_settings;
}
/**
* Returns the default settings for a node.
*
* @since 1.0
* @param object $node A node object.
* @return object
*/
static public function get_node_defaults( $node ) {
$defaults = array();
if ( 'row' == $node->type ) {
$defaults = self::get_row_defaults();
} elseif ( 'column' == $node->type ) {
$defaults = self::get_col_defaults();
} elseif ( 'module' == $node->type ) {
$defaults = self::get_module_defaults( $node->settings->type );
}
return $defaults;
}
/**
* Get the placeholder data for node spacing breakpoint fields.
*
* @since 2.6
* @param string $type The type of node.
* @param string $property Either padding or margins.
* @param string $size The breakpoint size key.
* @return object
*/
static public function get_node_spacing_breakpoint_placeholders( $type, $property, $size ) {
$global_settings = self::get_global_settings();
$sizes = array( 'large', 'medium', 'responsive' );
$fallbacks = array_reverse( array_slice( $sizes, 0, array_search( $size, $sizes ) ) );
$sides = array( 'top', 'right', 'bottom', 'left' );
$placeholders = array();
foreach ( $sides as $side ) {
$key = $type . '_' . $property . '_' . $side . '_' . $size;
$placeholders[ $side ] = '';
if ( '' === $global_settings->{ $key } ) {
foreach ( $fallbacks as $fallback ) {
$fallback_key = $type . '_' . $property . '_' . $side . '_' . $fallback;
if ( '' !== $global_settings->{ $fallback_key } ) {
$placeholders[ $side ] = $global_settings->{ $fallback_key };
break;
}
}
} else {
$placeholders[ $side ] = $global_settings->{ $key };
}
}
return $placeholders;
}
/**
* Callback for the uasort function.
*
* @since 1.0
* @param int $a The first position.
* @param int $b The second position.
* @return int
*/
static public function order_nodes( $a, $b ) {
return (int) $a->position - (int) $b->position;
}
/**
* Counts the number of nodes in a parent.
*
* @since 1.0
* @param string $type The type of nodes to count.
* @param string $parent_id The parent node id.
* @return int
*/
static public function count_nodes( $type = 'row', $parent_id = null ) {
return count( self::get_nodes( $type, $parent_id ) );
}
/**
* Returns the index of the next available
* position in a parent node.
*
* @since 1.0
* @param string $type The type of nodes to count.
* @param string $parent_id The parent node id.
* @return int
*/
static public function next_node_position( $type = 'row', $parent_id = null ) {
$nodes = self::get_nodes( $type, $parent_id );
$last = array_pop( $nodes );
return $last ? $last->position + 1 : 0;
}
/**
* Deletes a node.
*
* @since 1.0
* @param string $node_id The ID of the node to delete.
* @return void
*/
static public function delete_node( $node_id = null ) {
// Get the layout data.
$data = self::get_layout_data();
// Return if the node doesn't exist.
if ( ! isset( $data[ $node_id ] ) ) {
return;
}
// Get the node.
$node = $data[ $node_id ];
// Call the delete method if we're deleting a module.
self::call_module_delete( $node );
// Delete the node.
unset( $data[ $node_id ] );
// Get the sibling nodes.
if ( 'row' === $node->type ) {
$siblings = self::get_nodes( 'row' );
} else {
$siblings = self::get_nodes( null, $node->parent );
}
// Reorder sibling nodes.
$position = 0;
foreach ( $siblings as $sibling_id => $sibling ) {
if ( isset( $data[ $sibling_id ] ) ) {
$data[ $sibling_id ]->position = $position;
$position++;
}
}
// Delete the node's children.
self::delete_child_nodes_from_data( $node, $data );
// Update the layout data.
self::update_layout_data( $data );
}
/**
* Deletes all child nodes for a parent.
*
* @since 1.0
* @param object $parent The parent node object.
* @param object $data The data array to delete from.
* @return void
*/
static public function delete_child_nodes_from_data( $parent, &$data ) {
$children = self::get_nodes( null, $parent );
foreach ( $children as $child_id => $child ) {
// Call the delete method if we're deleting a module.
self::call_module_delete( $child );
// Delete the node.
unset( $data[ $child_id ] );
// Delete the node's children.
self::delete_child_nodes_from_data( $child, $data );
}
}
/**
* Calls the delete method for a node
* that is a module.
*
* @since 1.0
* @param object $node A module node.
* @return void
*/
static public function call_module_delete( $node ) {
if ( 'module' == $node->type && isset( self::$modules[ $node->settings->type ] ) ) {
$class = get_class( self::$modules[ $node->settings->type ] );
$instance = new $class();
$instance->node = $node->node;
$instance->parent = $node->parent;
$instance->settings = $node->settings;
$instance->delete();
$instance->remove();
}
}
/**
* Repositions a node within a parent.
*
* @since 1.0
* @param string $node_id A node ID.
* @param int $position The new position.
* @param string $type The type of node to order.
* @return void
*/
static public function reorder_node( $node_id = null, $position = 0 ) {
$data = self::get_layout_data();
$node = $data[ $node_id ];
$type = ! $node->parent ? $node->type : null;
$nodes = self::get_nodes( $type, $node->parent );
$new_pos = 0;
// Make sure node positions start at zero.
foreach ( $nodes as $node ) {
$data[ $node->node ]->position = $new_pos;
$new_pos++;
}
// Get the node and remove it from the array.
$node = $data[ $node_id ];
$removed = array_splice( $nodes, $node->position, 1 );
$new_pos = 0;
// Reposition it in the array.
array_splice( $nodes, $position, 0, $removed );
// Update the position data.
$updated_nodes = array();
foreach ( $nodes as $node ) {
$data[ $node->node ]->position = $new_pos;
// Get node fragments for redux
$updated_nodes[ $node->node ] = new StdClass();
$updated_nodes[ $node->node ]->position = $new_pos;
$new_pos++;
}
// Update the layout data.
self::update_layout_data( $data );
return array(
'nodeId' => $node_id,
'nodeType' => $data[ $node_id ]->type,
'moduleType' => 'module' === $data[ $node_id ]->type ? $data[ $node_id ]->settings->type : null,
'parent' => $node->parent,
'updatedNodes' => $updated_nodes,
);
}
/**
* Moves a node to another parent.
*
* @since 1.0
* @param string $node_id ID of the node to move.
* @param int $new_parent_id ID of the new parent.
* @param int $position The position in the new parent.
* @return void
*/
static public function move_node( $node_id = null, $new_parent_id = null, $position = 0 ) {
$data = self::get_layout_data();
$new_parent = self::get_node( $new_parent_id );
$node = self::get_node( $node_id );
$siblings = self::get_nodes( null, $node->parent );
$sibling_pos = 0;
// Set the node's new parent.
$data[ $node_id ]->parent = $new_parent->node;
// Remove the node from the $siblings array.
unset( $siblings[ $node_id ] );
// Reorder old siblings.
foreach ( $siblings as $sibling ) {
$data[ $sibling->node ]->position = $sibling_pos;
$sibling_pos++;
}
// Update the layout data.
self::update_layout_data( $data );
// Set the node's new order.
$reordered = self::reorder_node( $node_id, $position );
// Get updated node fragments for redux
$updated = $reordered['updatedNodes'];
$updated[ $node_id ] = new StdClass();
$updated[ $node_id ]->position = intval( $position );
$updated[ $node_id ]->parent = $new_parent_id;
return array(
'nodeId' => $node_id,
'nodeType' => $node->type,
'moduleType' => 'module' === $node->type ? $node->settings->type : null,
'parent' => $new_parent_id,
'updatedNodes' => $updated,
);
}
/**
* Adds a row to the current layout.
*
* @since 1.0
* @param string $cols The type of column layout to use.
* @param int $position The position of the new row.
* @param string $module Optional. The node ID of an existing module to move to this row.
* @return object The new row object.
*/
static public function add_row( $cols = '1-col', $position = false, $module = null ) {
$data = self::get_layout_data();
$settings = self::get_row_defaults();
$row_node_id = self::generate_node_id();
// Add the row.
$data[ $row_node_id ] = new StdClass();
$data[ $row_node_id ]->node = $row_node_id;
$data[ $row_node_id ]->type = 'row';
$data[ $row_node_id ]->parent = null;
$data[ $row_node_id ]->position = self::next_node_position( 'row' );
$data[ $row_node_id ]->settings = $settings;
// Update the layout data.
self::update_layout_data( $data );
// Position the row.
if ( false !== $position ) {
self::reorder_node( $row_node_id, $position );
}
// Add a column group.
$group = self::add_col_group( $row_node_id, $cols, 0 );
// Move an existing module to the row.
if ( $module ) {
$cols = self::get_nodes( 'column', $group->node );
$col = array_shift( $cols );
self::move_node( $module, $col->node, 0 );
}
// Return the updated row.
return self::get_node( $row_node_id );
}
/**
* Copies a row and adds it to the current layout.
*
* @since 1.0
* @param string $node_id Node ID of the row to copy.
* @param object $settings These settings will be used for the copy if present.
* @param string $settings_id The ID of the node who's settings were passed.
* @return void
*/
static public function copy_row( $node_id = null, $settings = null, $settings_id = null ) {
$layout_data = self::get_layout_data();
$row = self::get_node( $node_id );
$new_row_id = self::generate_node_id();
$col_groups = self::get_nodes( 'column-group', $row );
$new_nodes = array();
$template_cols = array();
// Add the new row.
$layout_data[ $new_row_id ] = clone $row;
$layout_data[ $new_row_id ]->settings = clone $row->settings;
$layout_data[ $new_row_id ]->node = $new_row_id;
// Unset row template data.
if ( isset( $layout_data[ $new_row_id ]->template_id ) ) {
unset( $layout_data[ $new_row_id ]->template_id );
unset( $layout_data[ $new_row_id ]->template_node_id );
unset( $layout_data[ $new_row_id ]->template_root_node );
}
// Get the new child nodes.
foreach ( $col_groups as $col_group ) {
$new_nodes[ $col_group->node ] = clone $col_group;
$cols = self::get_nodes( 'column', $col_group );
foreach ( $cols as $col ) {
$new_nodes[ $col->node ] = clone $col;
$new_nodes[ $col->node ]->settings = clone $col->settings;
$nodes = self::get_nodes( null, $col );
foreach ( $nodes as $node ) {
$new_nodes[ $node->node ] = clone $node;
if ( 'module' == $node->type ) {
$new_nodes[ $node->node ]->settings = self::clone_module_settings( $node->settings );
} elseif ( 'column-group' == $node->type ) {
$nested_cols = self::get_nodes( 'column', $node );
foreach ( $nested_cols as $nested_col ) {
$new_nodes[ $nested_col->node ] = clone $nested_col;
$new_nodes[ $nested_col->node ]->settings = clone $nested_col->settings;
$modules = self::get_nodes( 'module', $nested_col );
foreach ( $modules as $module ) {
$new_nodes[ $module->node ] = clone $module;
$new_nodes[ $module->node ]->settings = self::clone_module_settings( $module->settings );
}
}
}
}
}
}
// Apply settings that were passed if we have them.
if ( $settings && $settings_id ) {
if ( $settings_id === $row->node ) {
$layout_data[ $new_row_id ]->settings = (object) array_merge( (array) $row->settings, (array) $settings );
} else {
$new_nodes[ $settings_id ]->settings = (object) array_merge( (array) $new_nodes[ $settings_id ]->settings, (array) $settings );
}
}
// Generate new child ids.
$new_nodes = self::generate_new_node_ids( $new_nodes );
// Set col group parent ids to the new row id and unset template data.
foreach ( $new_nodes as $child_node_id => $child ) {
// Check for column template's new node id.
if ( isset( $child->template_node_id ) ) {
$template_cols[ $child->template_node_id ] = $child_node_id;
}
if ( 'column-group' == $child->type ) {
if ( $child->parent == $row->node || ( isset( $row->template_node_id ) && $child->parent == $row->template_node_id ) ) {
$new_nodes[ $child_node_id ]->parent = $new_row_id;
}
} elseif ( 'module' == $child->type ) {
if ( isset( $template_cols[ $child->parent ] ) ) {
$new_nodes[ $child_node_id ]->parent = $template_cols[ $child->parent ];
}
}
if ( isset( $new_nodes[ $child_node_id ]->template_id ) ) {
unset( $new_nodes[ $child_node_id ]->template_id );
unset( $new_nodes[ $child_node_id ]->template_node_id );
}
}
// Merge the child data.
$layout_data = array_merge( $layout_data, $new_nodes );
// Update the layout data.
self::update_layout_data( $layout_data );
// Position the new row.
self::reorder_node( $new_row_id, $row->position + 1 );
// Return the new row.
return self::get_node( $new_row_id );
}
/**
* Returns the default settings for row nodes.
*
* @since 1.0
* @return object
*/
static public function get_row_defaults() {
$settings = self::get_settings_form_defaults( 'row' );
$settings = self::merge_nested_form_defaults( 'general', 'row', $settings );
return $settings;
}
/**
* Runs row specific logic on new row settings.
*
* @since 1.0
* @param object $row A row node.
* @param object $new_settings The new settings object.
* @return object
*/
static public function process_row_settings( $row, $new_settings ) {
// Cache background video data.
if ( 'video' == $new_settings->bg_type ) {
// Video Fallback Photo
if ( ! empty( $new_settings->bg_video_fallback_src ) ) {
$fallback = $new_settings->bg_video_fallback_src;
} else {
$fallback = '';
}
if ( 'wordpress' == $new_settings->bg_video_source ) {
// Video MP4
$mp4 = FLBuilderPhoto::get_attachment_data( $new_settings->bg_video );
if ( $mp4 ) {
$parts = explode( '.', $mp4->filename );
$mp4->extension = array_pop( $parts );
$new_settings->bg_video_data = $mp4;
$new_settings->bg_video_data->fallback = $fallback;
} else {
$new_settings->bg_video_data = new stdClass();
$new_settings->bg_video_data->url = '';
$new_settings->bg_video_data->width = '';
$new_settings->bg_video_data->height = '';
$new_settings->bg_video_data->extension = '';
$new_settings->bg_video_data->fallback = '';
}
// Video WebM
$webm = FLBuilderPhoto::get_attachment_data( $new_settings->bg_video_webm );
if ( $webm ) {
$parts = explode( '.', $webm->filename );
$webm->extension = array_pop( $parts );
$new_settings->bg_video_webm_data = $webm;
$new_settings->bg_video_webm_data->fallback = $fallback;
} else {
$new_settings->bg_video_webm_data = new stdClass();
$new_settings->bg_video_webm_data->url = '';
$new_settings->bg_video_webm_data->width = '';
$new_settings->bg_video_webm_data->height = '';
$new_settings->bg_video_webm_data->extension = '';
$new_settings->bg_video_webm_data->fallback = '';
}
}
}
// Cache background slideshow data.
if ( 'slideshow' == $new_settings->bg_type && 'wordpress' == $new_settings->ss_source && class_exists( 'FLSlideshowModule' ) ) {
// Make sure we have a photo data object.
if ( ! isset( $row->settings->ss_photo_data ) ) {
$row->settings->ss_photo_data = new StdClass();
}
// Hijack the slideshow module to get WordPress photo data.
$ss = new FLSlideshowModule();
$ss->settings = new StdClass();
$ss->settings->photos = $new_settings->ss_photos;
$ss->settings->photo_data = $row->settings->ss_photo_data;
$new_settings->ss_photo_data = $ss->get_wordpress_photos();
}
return $new_settings;
}
/**
* Returns background data for a row.
*
* @since 1.0
* @param object $row A row node.
* @return object
*/
static public function get_row_bg_data( $row ) {
$data = null;
// Background Video
if ( 'video' == $row->settings->bg_type ) {
if ( isset( $row->settings->bg_video_data ) ) {
$data = array();
$data['mp4'] = $row->settings->bg_video_data;
}
if ( isset( $row->settings->bg_video_webm_data ) ) {
if ( ! $data ) {
$data = array();
}
$data['webm'] = $row->settings->bg_video_webm_data;
}
} elseif ( 'slideshow' == $row->settings->bg_type && isset( $row->settings->ss_photo_data ) ) {
$data = $row->settings->ss_photo_data;
}
return $data;
}
/**
* Returns the source for a row background slideshow.
*
* @since 1.0
* @param object $row A row node.
* @return string
*/
static public function get_row_slideshow_source( $row ) {
// Make sure we have a photo data object.
if ( ! isset( $row->settings->ss_photo_data ) ) {
$row->settings->ss_photo_data = new StdClass();
}
// This class does not exist in Lite version.
if ( ! class_exists( 'FLSlideshowModule' ) ) {
return false;
}
// Hijack the slideshow module to get the source.
$ss = new FLSlideshowModule();
$ss->settings = new StdClass();
$ss->settings->source = $row->settings->ss_source;
$ss->settings->photos = $row->settings->ss_photos;
$ss->settings->feed_url = $row->settings->ss_feed_url;
$ss->settings->photo_data = $row->settings->ss_photo_data;
// Return the slideshow source.
return $ss->get_source();
}
/**
* Set the max-width of a specific row.
*
* @since 2.0
* @param int Row node id
* @param int Width
* @return void
*/
static public function resize_row_content( $node_id, $width ) {
$data = self::get_layout_data();
$row = self::get_node( $node_id );
$row->settings->max_content_width = $width;
$data[ $node_id ] = $row;
self::update_layout_data( $data );
}
/**
* Adds a column group to a row in the current layout.
*
* @since 1.0
* @param string $node_id A row node ID.
* @param string $cols The type of column group layout or the ID of an existing column to add.
* @param int $position The position of the new column group.
* @param string $module Optional. The node ID of an existing module to move to this group.
* @return object The new column group object.
*/
static public function add_col_group( $node_id = null, $cols = '1-col', $position = false, $module = null ) {
$data = self::get_layout_data();
$group_node_id = self::generate_node_id();
$parent = self::get_node( $node_id );
$old_group = null;
// Add the column group.
$data[ $group_node_id ] = new StdClass();
$data[ $group_node_id ]->node = $group_node_id;
$data[ $group_node_id ]->type = 'column-group';
$data[ $group_node_id ]->parent = $node_id;
$data[ $group_node_id ]->position = self::next_node_position( null, $node_id );
$data[ $group_node_id ]->settings = '';
// Add node template data.
if ( self::is_node_global( $parent ) ) {
$data[ $group_node_id ]->template_id = $parent->template_id;
$data[ $group_node_id ]->template_node_id = $group_node_id;
}
// Add new columns?
if ( isset( self::$row_layouts[ $cols ] ) ) {
for ( $i = 0; $i < count( self::$row_layouts[ $cols ] ); $i++ ) {
$col_node_id = self::generate_node_id();
$data[ $col_node_id ] = new StdClass();
$data[ $col_node_id ]->node = $col_node_id;
$data[ $col_node_id ]->type = 'column';
$data[ $col_node_id ]->parent = $group_node_id;
$data[ $col_node_id ]->position = $i;
$data[ $col_node_id ]->settings = new StdClass();
$data[ $col_node_id ]->settings->size = self::$row_layouts[ $cols ][ $i ];
if ( self::is_node_global( $parent ) ) {
$data[ $col_node_id ]->template_id = $parent->template_id;
$data[ $col_node_id ]->template_node_id = $col_node_id;
}
}
} elseif ( isset( $data[ $cols ] ) ) {
$old_group = $data[ $cols ]->parent;
$siblings = self::get_nodes( 'column', $old_group );
$sibling_pos = 0;
// Add the column to the group.
$data[ $cols ]->parent = $group_node_id;
$data[ $cols ]->position = 0;
$data[ $cols ]->settings->size = 100;
if ( self::is_node_global( $parent ) ) {
$data[ $cols ]->template_id = $parent->template_id;
$data[ $cols ]->template_node_id = $data[ $cols ]->node;
}
// Remove the column from the $siblings array.
unset( $siblings[ $cols ] );
// Reorder old siblings.
foreach ( $siblings as $sibling ) {
$data[ $sibling->node ]->position = $sibling_pos;
$sibling_pos++;
}
}
// Update the layout data.
self::update_layout_data( $data );
// Delete an existing column's old group if empty or resize it.
if ( $old_group ) {
if ( 0 === count( self::get_nodes( 'column', $old_group ) ) ) {
self::delete_node( $old_group );
} else {
self::reset_col_widths( $old_group );
}
}
// Position the column group.
if ( false !== $position ) {
self::reorder_node( $group_node_id, $position );
}
// Move an existing module to the group.
if ( $module ) {
$cols = self::get_nodes( 'column', $group_node_id );
$col = array_shift( $cols );
self::move_node( $module, $col->node, 0 );
}
// Return the column group.
return self::get_node( $group_node_id );
}
/**
* Runs column specific logic on new column settings.
*
* @since 1.0
* @param object $col A column node.
* @param object $new_settings The new settings object.
* @return object
*/
static public function process_col_settings( $col, $new_settings ) {
$post_data = self::get_post_data();
// Don't process for preview nodes or if column is parent.
if ( isset( $post_data['node_preview'] ) || empty( $col->parent ) ) {
return $new_settings;
}
// Resize sibling cols if needed.
$new_settings->size = self::resize_col( $col->node, $new_settings->size );
// Update other sibling vars as needed.
$equal_height = false;
$content_alignment = false;
$responsive_order = false;
// Adjust sibling equal height?
if ( $col->settings->equal_height != $new_settings->equal_height ) {
$equal_height = $new_settings->equal_height;
}
// Adjust sibling content alignment?
if ( $col->settings->content_alignment != $new_settings->content_alignment ) {
$content_alignment = $new_settings->content_alignment;
}
// Adjust sibling responsive order?
if ( $col->settings->responsive_order != $new_settings->responsive_order ) {
$responsive_order = $new_settings->responsive_order;
}
// Update the siblings?
if ( false !== $equal_height || false !== $content_alignment || false !== $responsive_order ) {
$data = self::get_layout_data();
$cols = self::get_nodes( 'column', $col->parent );
foreach ( $cols as $node_id => $node ) {
if ( false !== $equal_height ) {
$data[ $node_id ]->settings->equal_height = $equal_height;
}
if ( false !== $content_alignment ) {
$data[ $node_id ]->settings->content_alignment = $content_alignment;
}
if ( false !== $responsive_order ) {
$data[ $node_id ]->settings->responsive_order = $responsive_order;
}
}
self::update_layout_data( $data );
}
return $new_settings;
}
/**
* Deletes a column.
*
* @since 1.0
* @param string $node_id Node ID of the column to delete (can also be a group).
* @param int $new_width New width of the remaining columns.
* @return void
*/
static public function delete_col( $node_id = null, $new_width = 100 ) {
$col = self::get_node( $node_id );
// Delete the column.
self::delete_node( $node_id );
// Return if the node we just deleted was a group.
if ( 'column-group' == $col->type ) {
return;
}
// Get the group
$group = self::get_node( $col->parent );
// Get the group children.
$cols = self::get_nodes( 'column', $group->node );
// Delete the group if empty.
if ( count( $cols ) === 0 ) {
self::delete_node( $group->node );
} else {
// Get the layout data.
$data = self::get_layout_data();
// Loop through the columns.
foreach ( $cols as $col_id => $col ) {
// Set the new size.
$data[ $col_id ]->settings->size = round( $new_width, 3 );
}
// Update the layout data.
self::update_layout_data( $data );
}
}
/**
* Moves a column within a group.
*
* @since 1.9
* @param string $node_id
* @param int $position
* @return void
*/
static public function reorder_col( $node_id, $position = 0 ) {
$col = self::get_node( $node_id );
self::reorder_node( $node_id, $position );
self::reset_col_widths( $col->parent );
}
/**
* Moves a column from one group to another.
*
* @since 1.9
* @param string $col_id
* @param string $group_id
* @param int $position
* @param array $resize
* @return void
*/
static public function move_col( $col_id, $group_id, $position, $resize = array() ) {
$col = self::get_node( $col_id );
$old_group = self::get_node( $col->parent );
self::move_node( $col_id, $group_id, $position );
if ( 0 === count( self::get_nodes( 'column', $old_group ) ) ) {
self::delete_node( $old_group->node );
self::reset_col_widths( $group_id );
} else {
self::reset_col_widths( $resize );
}
}
/**
* Resizes a column.
*
* @since 1.0
* @param string $node_id Node ID of the column to resize.
* @param int $new_width New width of the column.
* @return int The new width
*/
static public function resize_col( $node_id = null, $new_width = 100 ) {
$data = self::get_layout_data();
$col = $data[ $node_id ];
$group = $data[ $col->parent ];
$cols = array_values( self::get_nodes( 'column', $group->node ) );
$pos = $col->position;
$siblings = array();
$siblings_width = 0;
$num_cols = count( $cols );
$min_width = 8;
$max_width = 100 - $min_width;
// Since version 2.5. Allow single column to be resized.
if ( 1 == $num_cols || ! is_numeric( $new_width ) ) {
return absint( $new_width );
}
// Find the sibling column to absorb this resize.
for ( $i = 0; $i < count( $cols ); $i++ ) {
if ( $col->node == $cols[ $i ]->node ) {
if ( isset( $cols[ $i + 1 ] ) ) {
$sibling = $cols[ $i + 1 ];
} else {
$sibling = $cols[ $i - 1 ];
}
break;
}
}
// Find other siblings.
foreach ( $cols as $c ) {
if ( $col->node == $c->node ) {
continue;
}
if ( $sibling->node == $c->node ) {
continue;
}
$siblings[] = $c;
$max_width -= $c->settings->size;
$siblings_width += $c->settings->size;
}
// Make sure the new width isn't too small.
if ( $new_width < $min_width ) {
$new_width = $min_width;
}
// Make sure the new width isn't too big.
if ( $new_width > $max_width ) {
$new_width = $max_width;
}
// Save new sibling size.
$data[ $sibling->node ]->settings->size = round( 100 - $siblings_width - $new_width, 3 );
// Save new column size.
$data[ $col->node ]->settings->size = $new_width;
// Update the layout data.
self::update_layout_data( $data );
// Return the new size.
return $new_width;
}
/**
* Resizes a column and its sibling using the provided widths.
*
* @since 1.6.4
* @param string $col_id Node ID of the column to resize.
* @param int $col_width New width of the column.
* @param string $sibling_id Node ID of the sibling to resize.
* @param int $sibling_width New width of the sibling.
* @return Array affected node fragments
*/
static public function resize_cols( $col_id = null, $col_width = null, $sibling_id = null, $sibling_width = null ) {
$data = self::get_layout_data();
// Save the column width.
$data[ $col_id ]->settings->size = $col_width;
// Save the sibling width.
$data[ $sibling_id ]->settings->size = $sibling_width;
// Update the layout data.
self::update_layout_data( $data );
// Return node fragments for redux
$updated_nodes = array(
$col_id => array(
'settings' => array(
'size' => $col_width,
),
),
$sibling_id => array(
'settings' => array(
'size' => $sibling_width,
),
),
);
// Passed in array for consistency with other responses
return array( 'updatedNodes' => $updated_nodes );
}
/**
* Resets the widths of all columns in a group.
*
* @since 1.6.4
* @param string|array $group_id Node ID of the group whose columns to reset or an array of group IDs.
* @return void
*/
static public function reset_col_widths( $group_id = null ) {
if ( ! $group_id ) {
return;
}
if ( 'array' == gettype( $group_id ) ) {
foreach ( $group_id as $id ) {
self::reset_col_widths( $id );
}
return;
}
$data = self::get_layout_data();
$post_data = self::get_post_data();
$cols = self::get_nodes( 'column', $group_id );
$width = round( 100 / count( $cols ), 3 );
foreach ( $cols as $col_id => $col ) {
$data[ $col_id ]->settings->size = $width;
}
self::update_layout_data( $data );
}
/**
* Adds a column to a column group in the current layout.
*
* @since 1.9
* @param string $node_id A column group node ID.
* @param int $position The position of the new column.
* @return object The new column object.
*/
static public function add_col( $node_id = null, $position = false ) {
$group = self::get_node( $node_id );
$cols = self::get_nodes( 'column', $group );
$num_cols = count( $cols );
$i = 0;
$sibling = false;
$insert = 'before';
foreach ( $cols as $col ) {
if ( $i == $position ) {
$sibling = $col;
break;
}
$i++;
}
if ( ! $sibling ) {
$sibling = $col;
$insert = 'after';
}
self::add_cols( $sibling->node, $insert );
$cols = self::get_nodes( 'column', $group );
$col_ids = array_keys( $cols );
return $cols[ $col_ids[ $position ] ];
}
/**
* Inserts a column (or columns) before or after another column.
*
* @since 1.6.4
* @param string $node_id Node ID of the column to insert before or after.
* @param string $insert Either before or after.
* @param string $type The type of column(s) to insert.
* @param boolean $nested Whether these columns are nested or not.
* @param string $module Optional. The node ID of an existing module to move to this group.
* @return object
*/
static public function add_cols( $col_id, $insert = 'before', $type = '1-col', $nested = false, $module = null ) {
$data = self::get_layout_data();
$col = self::get_node( $col_id );
$parent = self::get_node( $col->parent );
$cols = self::get_nodes( 'column', $col->parent );
$global = self::is_node_global( $parent );
$num_new_cols = count( self::$row_layouts[ $type ] );
$num_cols = count( $cols );
$max_cols = $nested ? 4 : 12;
$reposition = false;
$position = 0;
// Make sure we have 12 columns or less.
if ( $num_cols + $num_new_cols > $max_cols ) {
$num_new_cols = $num_new_cols - ( $num_cols + $num_new_cols - $max_cols );
$num_cols = $max_cols;
} else {
$num_cols += $num_new_cols;
}
// Get the new width.
if ( 6 === $num_cols ) {
$new_width = 16.65;
} elseif ( 7 === $num_cols ) {
$new_width = 14.28;
} else {
$new_width = round( 100 / $num_cols, 3 );
}
// Get the new column position.
if ( 'before' == $insert ) {
$new_col_position = $col->position - 1 < 0 ? 0 : $col->position;
} else {
$new_col_position = $col->position + 1;
}
// Add the new columns.
for ( $i = 0; $i < $num_new_cols; $i++ ) {
$new_col_id = self::generate_node_id();
$data[ $new_col_id ] = new StdClass();
$data[ $new_col_id ]->node = $new_col_id;
$data[ $new_col_id ]->type = 'column';
$data[ $new_col_id ]->parent = $parent->node;
$data[ $new_col_id ]->position = $new_col_position;
$data[ $new_col_id ]->settings = new StdClass();
$data[ $new_col_id ]->settings->size = $new_width;
// Add node template data.
if ( $global ) {
$data[ $new_col_id ]->template_id = $parent->template_id;
$data[ $new_col_id ]->template_node_id = $new_col_id;
}
$new_col_position++;
}
// Resize sibling columns and set their new position.
foreach ( $cols as $sibling_col_id => $sibling_col ) {
$data[ $sibling_col_id ]->settings->size = $new_width;
if ( $sibling_col_id == $col_id ) {
$reposition = true;
if ( 'before' == $insert ) {
$data[ $sibling_col_id ]->position = $new_col_position;
$new_col_position++;
}
} elseif ( $reposition ) {
$data[ $sibling_col_id ]->position = $new_col_position;
$new_col_position++;
} else {
$data[ $sibling_col_id ]->position = $position;
$position++;
}
}
// Update the layout data.
self::update_layout_data( $data );
// Move an existing module to the group.
if ( $module ) {
self::move_node( $module, $new_col_id, 0 );
}
// Return the column group.
return $parent;
}
/**
* Adds a parent node for a column if a parent with the supplied
* parent ID doesn't exist.
*
* @since 2.1
* @param string $parent_id The node ID of the parent to look for.
* @param int $position The position of the parent.
* @return string|null The new parent ID or null if none exists.
*/
static public function add_col_parent( $parent_id = null, $position = null ) {
$data = self::get_layout_data();
$parent = ! $parent_id ? null : self::get_node( $parent_id );
if ( ! $parent ) {
// Add a new row if we don't have a parent, but don't add column.
$row = self::add_row( null, $position );
$col_groups = self::get_nodes( 'column-group', $row->node );
$col_group = array_shift( $col_groups );
$parent_id = $col_group->node;
} elseif ( 'row' == $parent->type ) {
// Add a new column group if the parent is a row, but don't add column.
$col_group = self::add_col_group( $parent->node, null, $position );
$parent_id = $col_group->node;
}
return $parent_id;
}
/**
* Returns a column's parent node of the specified type.
*
* @since 2.1
* @param string $type The type of parent to return.
* @param string|object $column_id The columns's node ID. Can also be a column object.
* @return object The parent node.
*/
static public function get_col_parent( $type, $column_id ) {
$column = is_object( $column_id ) ? $column_id : self::get_node( $column_id );
$nodes = self::get_categorized_nodes();
foreach ( $nodes['groups'] as $group ) {
if ( $group->node == $column->parent ) {
if ( 'column-group' == $type ) {
return $group;
}
foreach ( $nodes['rows'] as $row ) {
if ( $row->node == $group->parent ) {
return $row;
}
}
}
}
return null;
}
/**
* Copies a column and adds it to the current layout.
*
* @since 2.0
* @param string $node_id Node ID of the column to copy.
* @param object $settings These settings will be used for the copy if present.
* @param string $settings_id The ID of the node who's settings were passed.
* @return void
*/
static public function copy_col( $node_id = null, $settings = null, $settings_id = null ) {
$layout_data = self::get_layout_data();
$col = self::get_node( $node_id );
$new_col_id = self::generate_node_id();
$nodes = self::get_nodes( null, $col );
$parent = self::get_node_parent( $node_id );
$new_nodes = array();
// Add the new column.
$layout_data[ $new_col_id ] = clone $col;
$layout_data[ $new_col_id ]->settings = clone $col->settings;
$layout_data[ $new_col_id ]->node = $new_col_id;
// Unset column template data.
if ( isset( $layout_data[ $new_col_id ]->template_id ) ) {
// Get the column root parent on a page.
if ( isset( $layout_data[ $new_col_id ]->template_root_node ) ) {
$parent = self::get_node( $layout_data[ $new_col_id ]->parent );
}
// Check if parent is a global node.
if ( self::is_node_global( $parent ) ) {
$layout_data[ $new_col_id ]->template_id = $parent->template_id;
$layout_data[ $new_col_id ]->template_node_id = $new_col_id;
} else {
unset( $layout_data[ $new_col_id ]->template_id );
unset( $layout_data[ $new_col_id ]->template_node_id );
}
unset( $layout_data[ $new_col_id ]->template_root_node );
}
// Get the new child nodes.
foreach ( $nodes as $node ) {
$new_nodes[ $node->node ] = clone $node;
if ( 'module' == $node->type ) {
$new_nodes[ $node->node ]->settings = self::clone_module_settings( $node->settings );
} elseif ( 'column-group' == $node->type ) {
$nested_cols = self::get_nodes( 'column', $node );
foreach ( $nested_cols as $nested_col ) {
$new_nodes[ $nested_col->node ] = clone $nested_col;
$new_nodes[ $nested_col->node ]->settings = clone $nested_col->settings;
$modules = self::get_nodes( 'module', $nested_col );
foreach ( $modules as $module ) {
$new_nodes[ $module->node ] = clone $module;
$new_nodes[ $module->node ]->settings = self::clone_module_settings( $module->settings );
}
}
}
}
// Apply settings that were passed if we have them.
if ( $settings && $settings_id ) {
if ( $settings_id === $col->node ) {
$layout_data[ $new_col_id ]->settings = (object) array_merge( (array) $col->settings, (array) $settings );
} else {
$new_nodes[ $settings_id ]->settings = (object) array_merge( (array) $new_nodes[ $settings_id ]->settings, (array) $settings );
}
}
// Generate new child ids.
$new_nodes = self::generate_new_node_ids( $new_nodes );
// Set child parent ids to the new column id and unset template data.
foreach ( $new_nodes as $child_node_id => $child ) {
if ( $child->parent == $col->node || ( isset( $col->template_node_id ) && $child->parent == $col->template_node_id ) ) {
$new_nodes[ $child_node_id ]->parent = $new_col_id;
}
if ( isset( $new_nodes[ $child_node_id ]->template_id ) ) {
// Check if the column is global.
if ( isset( $layout_data[ $new_col_id ]->template_node_id ) ) {
$new_nodes[ $child_node_id ]->template_id = $parent->template_id;
$new_nodes[ $child_node_id ]->template_node_id = $child_node_id;
} else {
unset( $new_nodes[ $child_node_id ]->template_id );
unset( $new_nodes[ $child_node_id ]->template_node_id );
}
}
}
// Merge the child data.
$layout_data = array_merge( $layout_data, $new_nodes );
// Update the layout data.
self::update_layout_data( $layout_data );
// Position the new column.
self::reorder_node( $new_col_id, $col->position + 1 );
// Reset the column widths.
self::reset_col_widths( $col->parent );
// Return the new column.
return self::get_node( $new_col_id );
}
/**
* Returns the default settings for column nodes.
*
* @since 1.0
* @return object
*/
static public function get_col_defaults() {
$settings = self::get_settings_form_defaults( 'col' );
$settings = self::merge_nested_form_defaults( 'general', 'col', $settings );
return $settings;
}
/**
* Loads the classes for core builder modules.
*
* @since 1.0
* @return void
*/
static public function load_modules() {
$paths = glob( FL_BUILDER_DIR . 'modules/*' );
/**
* Filter the modules paths.
* @see fl_builder_load_modules_paths
*/
$paths = apply_filters( 'fl_builder_load_modules_paths', $paths );
$module_path = '';
// Make sure we have an array.
if ( ! is_array( $paths ) ) {
return;
}
// Load all found modules.
foreach ( $paths as $path ) {
// Make sure we have a directory.
if ( ! is_dir( $path ) ) {
continue;
}
// Get the module slug.
$slug = basename( $path );
// Paths to check.
$module_path = $slug . '/' . $slug . '.php';
$child_path = get_stylesheet_directory() . '/fl-builder/modules/' . $module_path;
$theme_path = get_template_directory() . '/fl-builder/modules/' . $module_path;
$builder_path = FL_BUILDER_DIR . 'modules/' . $module_path;
// Check for the module class in a child theme.
if ( is_child_theme() && file_exists( $child_path ) ) {
require_once $child_path;
} elseif ( file_exists( $theme_path ) ) {
require_once $theme_path;
} elseif ( file_exists( $builder_path ) ) {
require_once $builder_path;
}
}
/**
* After modules are included.
* @see fl_builder_register_extensions
*/
do_action( 'fl_builder_register_extensions' );
}
/**
* Registers a module with the builder.
*
* @since 1.0
* @param string $class The module's PHP class name.
* @param array $form The module's settings form.
* @return void
*/
static public function register_module( $class, $form ) {
if ( class_exists( $class ) ) {
// Create a new instance of the module.
$instance = new $class();
// Log an error if a module with this slug already exists.
if ( isset( self::$modules[ $instance->slug ] ) ) {
/* translators: %s: module filename */
error_log( sprintf( _x( 'A module with the filename %s.php already exists! Please namespace your module filenames to ensure compatibility with Beaver Builder.', '%s stands for the module filename', 'fl-builder' ), $instance->slug ) );
return;
}
/**
* Use this filter to override the modules that are enabled in the builder.
* @see fl_builder_register_module
* @link https://docs.wpbeaverbuilder.com/beaver-builder/developer/tutorials-guides/common-beaver-builder-filter-examples
*/
$instance->enabled = apply_filters( 'fl_builder_register_module', $instance->enabled, $instance );
// Save the instance in the modules array.
self::$modules[ $instance->slug ] = $instance;
self::$modules[ $instance->slug ]->form = apply_filters( 'fl_builder_register_settings_form', $form, $instance->slug );
self::$modules[ $instance->slug ]->form['advanced'] = self::$settings_forms['module_advanced'];
/**
* Use this filter to modify the config array for a settings form when it is registered.
* @see fl_builder_register_module_settings_form
* @link https://docs.wpbeaverbuilder.com/beaver-builder/developer/tutorials-guides/common-beaver-builder-filter-examples
*/
self::$modules[ $instance->slug ]->form = apply_filters( 'fl_builder_register_module_settings_form', self::$modules[ $instance->slug ]->form, $instance->slug );
}
}
/**
* Registers an alias to a module with its own name,
* category and default settings.
*
* @since 1.10
* @param string $alias The alias key.
* @param array $config The alias config.
* @return void
*/
static public function register_module_alias( $alias, $config ) {
if ( isset( self::$module_aliases[ $alias ] ) ) {
/* translators: %s: module alias key */
_doing_it_wrong( __CLASS__ . '::register_module_alias', sprintf( _x( 'The module alias %s already exists! Please namespace your module aliases to ensure compatibility with Beaver Builder.', '%s stands for the module alias key', 'fl-builder' ), $alias ), '1.10' );
return;
}
if ( ! $config['module'] || ! isset( self::$modules[ $config['module'] ] ) ) {
return;
}
$module = self::$modules[ $config['module'] ];
$instance = new stdClass;
$instance->alias = $alias;
$instance->slug = isset( $config['module'] ) ? $config['module'] : null;
$instance->name = isset( $config['name'] ) ? $config['name'] : $instance->slug;
$instance->description = isset( $config['description'] ) ? $config['description'] : '';
$instance->category = isset( $config['category'] ) ? $config['category'] : null;
$instance->group = isset( $config['group'] ) ? $config['group'] : null;
$instance->settings = isset( $config['settings'] ) ? $config['settings'] : array();
$instance->enabled = isset( $config['enabled'] ) ? $config['enabled'] : true;
$instance->icon = isset( $config['icon'] ) ? $module->get_icon( $config['icon'] ) : FLBuilderModule::get_default_icon();
self::$module_aliases[ $alias ] = $instance;
}
/**
* Returns the default settings for a module alias.
*
* @since 1.10
* @param string $alias The alias key.
* @return array|null
*/
static public function get_module_alias_settings( $alias ) {
if ( isset( self::$module_aliases[ $alias ] ) ) {
return self::$module_aliases[ $alias ]->settings;
}
return null;
}
/**
* Checks to see if a module of a certain type has
* been registered.
*
* @since 1.9
* @param array $type The module's type slug.
* @return void
*/
static public function is_module_registered( $type ) {
return isset( self::$modules[ $type ] );
}
/**
* Returns an array of modules that are enabled by default.
*
* @since 2.1
* @return array
*/
static public function get_default_enabled_modules() {
$default = array_keys( self::$modules );
$deprecated = self::get_deprecated_modules();
// Remove deprecated modules from the defaults.
foreach ( $default as $key => $slug ) {
if ( in_array( $slug, $deprecated ) ) {
unset( $default[ $key ] );
}
}
return array_values( $default );
}
/**
* @since 2.4.1
*/
static public function get_deprecated_modules() {
// These modules are deprecated and disabled by default.
$deprecated = array(
'social-buttons',
);
return $deprecated;
}
/**
* Returns an array of all modules that are enabled.
*
* @since 1.0
* @return array
*/
static public function get_enabled_modules() {
$setting = self::get_admin_settings_option( '_fl_builder_enabled_modules', true );
if ( ! $setting ) {
// Fallback to the defaults if no saved setting.
$setting = self::get_default_enabled_modules();
} elseif ( in_array( 'all', $setting ) ) {
// Redefine $setting in case new modules have been installed since the last save.
$setting = array_keys( self::$modules );
$setting[] = 'all';
}
foreach ( self::$modules as $module_slug => $module ) {
if ( ! $module->enabled && in_array( $module_slug, $setting ) ) {
$key = array_search( $module_slug, $setting );
unset( $setting[ $key ] );
}
}
/**
* Array of enabled modules.
* @see fl_builder_enabled_modules
*/
return apply_filters( 'fl_builder_enabled_modules', $setting );
}
/**
* Returns an array of module group slugs and names.
*
* @since 2.0
* @return array
*/
static public function get_module_groups() {
$groups = array();
$templates = FLBuilderModel::get_module_templates_data();
// Add module groups.
foreach ( self::$modules as $module ) {
if ( ! $module->group || ! $module->enabled ) {
continue;
}
// Check if widgets are enabled
if ( 'widget' == $module->slug && ! in_array( 'widget', self::get_enabled_modules() ) ) {
continue;
}
$slug = sanitize_key( $module->group );
if ( ! isset( $groups[ $slug ] ) ) {
$groups[ $slug ] = $module->group;
}
}
// Add module alias groups.
foreach ( self::$module_aliases as $alias => $config ) {
if ( ! $config->group || ! $config->enabled ) {
continue;
}
$slug = sanitize_key( $config->group );
if ( ! isset( $groups[ $slug ] ) ) {
$groups[ $slug ] = $config->group;
}
}
// Add module template groups.
if ( isset( $templates['groups'] ) ) {
foreach ( $templates['groups'] as $slug => $data ) {
if ( ! isset( $groups[ $slug ] ) ) {
$groups[ $slug ] = $data['name'];
}
}
}
ksort( $groups );
/**
* Returns an array of module group slugs and names.
* @see fl_builder_module_groups
* @since 2.2.6
*/
return apply_filters( 'fl_builder_module_groups', $groups );
}
/**
* Returns an array of module category slugs => names
*
* @since 2.0
* @return array
*/
static public function get_module_categories() {
$categories = array();
/**
* Use this filter to add custom module categories that will show up before the default module categories in the builder’s UI.
* @see fl_builder_module_categories
* @link https://docs.wpbeaverbuilder.com/beaver-builder/developer/tutorials-guides/common-beaver-builder-filter-examples
*/
foreach ( apply_filters( 'fl_builder_module_categories', array() ) as $custom_category ) {
$categories[ $custom_category ] = array();
}
// Build the default category arrays.
$categories[ __( 'Basic', 'fl-builder' ) ] = array();
$categories[ __( 'Media', 'fl-builder' ) ] = array();
$categories[ __( 'Actions', 'fl-builder' ) ] = array();
$categories[ __( 'Layout', 'fl-builder' ) ] = array();
$categories[ __( 'Info', 'fl-builder' ) ] = array();
$categories[ __( 'Posts', 'fl-builder' ) ] = array();
$categories[ __( 'Advanced', 'fl-builder' ) ] = array();
$categories[ __( 'Other', 'fl-builder' ) ] = array();
return $categories;
}
/**
* Returns an array of categorized modules.
*
* @since 1.0
* @param bool $show_disabled Whether to include disabled modules in the result.
* @return array
*/
static public function get_categorized_modules( $show_disabled = false ) {
$enabled_modules = self::get_enabled_modules();
$widgets = null;
$categories = self::get_module_categories();
$other_key = __( 'Other', 'fl-builder' );
$widgets_key = __( 'WordPress Widgets', 'fl-builder' );
// Build the categories array.
foreach ( self::$modules as $module ) {
if ( ! $module->enabled ) {
continue;
} elseif ( ! in_array( $module->slug, $enabled_modules ) && ! $show_disabled ) {
continue;
} elseif ( 'widget' == $module->slug ) {
$widgets = self::get_wp_widgets();
} elseif ( isset( $module->category ) ) {
if ( ! isset( $categories[ $module->category ] ) ) {
$categories[ $module->category ] = array();
}
$categories[ $module->category ][ $module->name ] = $module;
} else {
$categories[ $other_key ][ $module->name ] = $module;
}
}
// Add module aliases.
foreach ( self::$module_aliases as $alias => $config ) {
if ( ! $config->enabled || ! $config->slug || ! $config->category ) {
continue;
}
if ( ! isset( $categories[ $config->category ] ) ) {
$categories[ $config->category ] = array();
}
$categories[ $config->category ][ $config->name ] = $config;
}
// Add widgets if we have them.
if ( $widgets ) {
$categories[ $widgets_key ] = $widgets;
}
// Sort the modules.
foreach ( $categories as $title => $modules ) {
if ( count( $categories[ $title ] ) == 0 ) {
unset( $categories[ $title ] );
} else {
ksort( $categories[ $title ] );
}
}
// Return sorted categories.
return $categories;
}
/**
* Similar to get_categorized_modules() but creates a flat list.
*
* @since 2.0
* @param bool $show_disabled Should show disabled?
* @return array
*/
static public function get_uncategorized_modules( $show_disabled = false ) {
$enabled_modules = self::get_enabled_modules();
$modules = array();
$aliases = self::$module_aliases;
$widgets = FLBuilderModel::get_wp_widgets();
foreach ( self::$modules as $module ) {
if ( ! $module->enabled ) {
continue;
} elseif ( ! in_array( $module->slug, $enabled_modules ) && ! $show_disabled ) {
continue;
} elseif ( 'widget' === $module->slug ) {
continue;
}
$module = clone $module;
$module->kind = 'module';
$module->isWidget = false; // @codingStandardsIgnoreLine
$module->isAlias = false; // @codingStandardsIgnoreLine
$module->group = $module->group ? array( sanitize_key( $module->group ) ) : array( 'standard' );
if ( ! isset( $module->icon ) || '' == $module->icon ) {
$module->icon = FLBuilderModule::get_default_icon();
}
// Remove backend-only & instance properties.
unset( $module->css );
unset( $module->js );
unset( $module->editor_export );
unset( $module->node );
unset( $module->parent );
unset( $module->partial_refresh );
unset( $module->position );
unset( $module->settings );
unset( $module->form );
unset( $module->dir );
$modules[] = $module;
}
// Add module aliases.
foreach ( $aliases as $alias => $config ) {
if ( ! $config->enabled || ! $config->slug || ! $config->category ) {
continue;
}
if ( ! isset( $categories[ $config->category ] ) ) {
$categories[ $config->category ] = array();
}
$config->kind = 'module';
$config->isWidget = false; // @codingStandardsIgnoreLine
$config->isAlias = true; // @codingStandardsIgnoreLine
$config->group = $config->group ? array( sanitize_key( $config->group ) ) : array( 'standard' );
$modules[] = $config;
}
// Add WordPress widgets.
if ( in_array( 'widget', $enabled_modules ) ) {
foreach ( $widgets as $widget ) {
$data = new stdClass;
$widget = (object) $widget;
$data->id = $widget->id;
$data->name = $widget->name;
$data->class = $widget->class;
$data->category = $widget->fl_category;
$data->kind = 'module';
$data->isWidget = true; // @codingStandardsIgnoreLine
$data->isAlias = false; // @codingStandardsIgnoreLine
$data->description = isset( $widget->widget_options['description'] ) ? $widget->widget_options['description'] : '';
$data->group = array( sanitize_key( __( 'WordPress Widgets', 'fl-builder' ) ) );
if ( ! isset( $widget->icon ) ) {
$data->icon = FLBuilderModule::get_widget_icon();
}
$modules[] = $data;
}
}
return $modules;
}
/**
* Returns an instance of a module.
*
* @since 1.0
* @param string|object $node_id A module node ID or object.
* @return object|bool The module or false if it doesn't exist.
*/
static public function get_module( $node_id ) {
$module = is_object( $node_id ) ? $node_id : self::get_node( $node_id );
if ( self::is_module_registered( $module->settings->type ) ) {
$class = get_class( self::$modules[ $module->settings->type ] );
$instance = new $class();
$instance->node = $module->node;
$instance->parent = $module->parent;
$instance->position = $module->position;
$instance->settings = $module->settings;
$instance->type = 'module';
$instance->form = self::$modules[ $module->settings->type ]->form;
$instance->icon = isset( $module->icon ) ? $module->icon : FLBuilderModule::get_default_icon();
if ( isset( $module->template_id ) ) {
$instance->template_id = $module->template_id;
$instance->template_node_id = $module->template_node_id;
}
if ( isset( $module->template_root_node ) ) {
$instance->template_root_node = true;
}
return $instance;
}
return false;
}
/**
* Returns an array of all modules in the current layout
* or in a column if a column id or object is supplied.
*
* @since 1.0
* @param string|object $col_id A column ID or object.
* @return array
*/
static public function get_modules( $col_id = null ) {
$col = is_object( $col_id ) ? $col_id : self::get_node( $col_id );
$modules = self::get_nodes( 'module', $col );
$instances = array();
$i = 0;
foreach ( $modules as $module ) {
if ( self::is_module_registered( $module->settings->type ) ) {
$class = get_class( self::$modules[ $module->settings->type ] );
$instances[ $i ] = new $class();
$instances[ $i ]->node = $module->node;
$instances[ $i ]->parent = $module->parent;
$instances[ $i ]->position = $module->position;
$instances[ $i ]->settings = $module->settings;
$instances[ $i ]->type = 'module';
$instances[ $i ]->icon = isset( $module->icon ) ? $module->icon : FLBuilderModule::get_default_icon();
$instances[ $i ]->form = self::$modules[ $module->settings->type ]->form;
if ( isset( $module->template_id ) ) {
$instances[ $i ]->template_id = $module->template_id;
$instances[ $i ]->template_node_id = $module->template_node_id;
}
if ( isset( $module->template_root_node ) ) {
$instances[ $i ]->template_root_node = true;
}
$i++;
}
}
return $instances;
}
/**
* Returns an array of all modules in the current layout.
*
* @since 1.0
* @return array
*/
static public function get_all_modules() {
return self::get_modules();
}
/**
* Add a new module to a column in the current layout.
*
* @since 1.0
* @param string $type The type of module to add.
* @param array $settings The new module's settings.
* @param string $parent_id The new module's parent node ID.
* @param int $position The new module's position.
* @return object The new module object.
*/
static public function add_module( $type = null, $settings = array(), $parent_id = null, $position = false ) {
$data = self::get_layout_data();
$parent = self::get_node( $parent_id );
$module_node_id = self::generate_node_id();
$settings->type = $type;
if ( ! self::$modules[ $type ] ) {
return false;
}
// Run module update method.
$class = get_class( self::$modules[ $type ] );
$instance = new $class();
$instance->node = $module_node_id;
$instance->settings = $settings;
$settings = $instance->update( $settings );
// Save the module.
$data[ $module_node_id ] = new StdClass();
$data[ $module_node_id ]->node = $module_node_id;
$data[ $module_node_id ]->type = 'module';
$data[ $module_node_id ]->parent = $parent_id;
$data[ $module_node_id ]->position = self::next_node_position( 'module', $parent_id );
$data[ $module_node_id ]->settings = $settings;
// Add node template data.
if ( self::is_node_global( $parent ) ) {
$data[ $module_node_id ]->template_id = $parent->template_id;
$data[ $module_node_id ]->template_node_id = $module_node_id;
}
// Update the layout data.
self::update_layout_data( $data );
// Position the module.
if ( false !== $position ) {
self::reorder_node( $module_node_id, $position );
}
// Send back the inserted module.
return self::get_module( $module_node_id );
}
/**
* Adds a parent node for a module if a parent with the supplied
* parent ID doesn't exist.
*
* @since 1.6.3
* @param string $parent_id The node ID of the parent to look for.
* @param int $position The position of the parent.
* @return string|null The new parent ID or null if none exists.
*/
static public function add_module_parent( $parent_id = null, $position = null ) {
$parent = ! $parent_id ? null : self::get_node( $parent_id );
if ( ! $parent ) {
// Add a new row if we don't have a parent.
$row = self::add_row( '1-col', $position );
$col_groups = self::get_nodes( 'column-group', $row->node );
$col_group = array_shift( $col_groups );
$cols = self::get_nodes( 'column', $col_group->node );
$parent = array_shift( $cols );
$parent_id = $parent->node;
} elseif ( 'row' == $parent->type ) {
// Add a new column group if the parent is a row.
$col_group = self::add_col_group( $parent->node, '1-col', $position );
$cols = self::get_nodes( 'column', $col_group->node );
$parent = array_shift( $cols );
$parent_id = $parent->node;
} elseif ( 'column-group' == $parent->type ) {
// Add a new column if the parent is a column group.
$parent = self::add_col( $parent->node, $position );
$parent_id = $parent->node;
}
return $parent_id;
}
/**
* Returns a module's parent node of the specified type.
*
* @since 1.7
* @param string $type The type of parent to return.
* @param string|object $module_id The module's node ID. Can also be a module object.
* @return object The parent node.
*/
static public function get_module_parent( $type, $module_id ) {
$module = is_object( $module_id ) ? $module_id : self::get_module( $module_id );
$nodes = self::get_categorized_nodes();
foreach ( $nodes['columns'] as $column ) {
if ( $column->node == $module->parent ) {
if ( 'column' == $type ) {
return $column;
}
foreach ( $nodes['groups'] as $group ) {
if ( $group->node == $column->parent ) {
if ( 'column-group' == $type ) {
return $group;
}
foreach ( $nodes['rows'] as $row ) {
if ( $row->node == $group->parent ) {
return $row;
}
}
}
}
}
}
return null;
}
/**
* Add a new module with default settings to a column
* in the current layout.
*
* @since 1.0
* @param string $parent_id The new module's parent node ID.
* @param string $type The type of module to add.
* @param int $position The new module's position.
* @return object The new module object.
* @return array $defaults Default settings for the module.
*/
static public function add_default_module( $parent_id = null, $type = null, $position = null, $defaults = null ) {
$parent = ( 0 === $parent_id ) ? null : self::get_node( $parent_id );
$settings = self::get_module_defaults( $type );
$module_node_id = self::generate_node_id();
// Add a new parent if one is needed.
if ( ! $parent || 'row' == $parent->type || 'column-group' == $parent->type ) {
$parent_id = self::add_module_parent( $parent_id, $position );
$parent = self::get_node( $parent_id );
$position = null;
}
// Merge default settings if present.
if ( $defaults ) {
$settings = (object) array_merge( (array) $settings, $defaults );
}
// Run module update method.
$class = get_class( self::$modules[ $type ] );
$instance = new $class();
$instance->node = $module_node_id;
$instance->settings = $settings;
$settings = $instance->update( $settings );
// Save the module.
$data = self::get_layout_data();
$data[ $module_node_id ] = new StdClass();
$data[ $module_node_id ]->node = $module_node_id;
$data[ $module_node_id ]->type = 'module';
$data[ $module_node_id ]->parent = $parent_id;
$data[ $module_node_id ]->position = self::next_node_position( 'module', $parent_id );
$data[ $module_node_id ]->settings = $settings;
// Add node template data.
if ( self::is_node_global( $parent ) ) {
$data[ $module_node_id ]->template_id = $parent->template_id;
$data[ $module_node_id ]->template_node_id = $module_node_id;
}
// Update the layout data.
self::update_layout_data( $data );
// Position the module.
if ( null !== $position ) {
self::reorder_node( $module_node_id, $position );
}
// Send back the inserted module.
return self::get_module( $module_node_id );
}
/**
* Make a copy of a module.
*
* @since 1.0
* @param string $node_id Node ID of the module to copy.
* @param object $settings These settings will be used for the copy if present.
* @return object The new module object.
*/
static public function copy_module( $node_id = null, $settings = null ) {
$module = self::get_module( $node_id );
if ( $settings ) {
$module->settings = (object) array_merge( (array) $module->settings, (array) $settings );
}
return self::add_module( $module->settings->type, $module->settings, $module->parent, $module->position + 1 );
}
/**
* Run module specific logic on new node settings.
*
* @since 1.0
* @param object $module A module node object.
* @param object $new_settings The new settings.
* @return object
*/
static public function process_module_settings( $module, $new_settings ) {
// Get a new node instance to work with.
$class = get_class( self::$modules[ $module->settings->type ] );
$instance = new $class();
$instance->node = $module->node;
$instance->parent = $module->parent;
$instance->settings = $module->settings;
// Run node delete to clear any cache.
$instance->delete();
// Run node update.
$instance->settings = $new_settings;
$new_settings = $instance->update( $new_settings );
return $new_settings;
}
/**
* Returns a cloned settings object for a module.
*
* @since 1.9
* @param object $settings
* @return object
*/
static public function clone_module_settings( $settings ) {
$new_settings = new stdClass;
foreach ( $settings as $key => $val ) {
$new_settings->$key = $val;
}
return $new_settings;
}
/**
* Returns the default settings for a module or
* all modules if type is null.
*
* @since 1.0
* @param string $type The type of module.
* @return object|array
*/
static public function get_module_defaults( $type = null ) {
if ( $type ) {
$defaults = new StdClass();
if ( isset( self::$modules[ $type ]->form ) ) {
$defaults = self::get_settings_form_defaults( $type );
$defaults = self::merge_nested_module_defaults( $type, $defaults );
$defaults->type = $type;
}
} else {
$defaults = array();
foreach ( self::$modules as $module ) {
$defaults[ $module->slug ] = self::get_module_defaults( $module->slug );
}
}
return $defaults;
}
/**
* Merges the default settings for nested forms in a module.
*
* @since 1.7
* @param string $type The type of module.
* @param object $settings The module settings object.
* @return object
*/
static public function merge_nested_module_defaults( $type, $settings ) {
return self::merge_nested_form_defaults( 'module', $type, $settings );
}
/**
* Returns an array of data for each core WordPress widget.
*
* @since 1.0
* @return array
*/
static public function get_wp_widgets() {
global $wp_widget_factory;
$widgets = array();
/**
* Array of known widgets that won't work in the builder.
* @see fl_get_wp_widgets_exclude
*/
$exclude = apply_filters( 'fl_get_wp_widgets_exclude', array(
'WP_Widget_Media_Audio',
'WP_Widget_Media_Image',
'WP_Widget_Media_Video',
'WP_Widget_Media_Gallery',
'WP_Widget_Text',
'WP_Widget_Custom_HTML',
'WP_Widget_Block',
) );
foreach ( $wp_widget_factory->widgets as $class => $widget ) {
if ( in_array( $class, $exclude ) ) {
continue;
}
$widget->class = $class;
$widget->isWidget = true; // @codingStandardsIgnoreLine
$widget->fl_category = __( 'WordPress Widgets', 'fl-builder' );
$widgets[ $widget->name ] = $widget;
}
ksort( $widgets );
return $widgets;
}
/**
* Returns an array of data for all registered sidebars.
*
* @since 1.0
* @return array
*/
static public function get_wp_sidebars() {
global $wp_registered_sidebars;
$sidebars = array();
foreach ( $wp_registered_sidebars as $sidebar ) {
$sidebars[ $sidebar['name'] ] = $sidebar;
}
ksort( $sidebars );
return $sidebars;
}
/**
* Returns an array of column group data.
*
* @since 2.0
* @return array
*/
static public function get_column_groups() {
$cols = array(
array(
'name' => __( '1 Column', 'fl-builder' ),
'id' => '1-col',
'count' => 1,
),
array(
'name' => __( '2 Columns', 'fl-builder' ),
'id' => '2-cols',
'count' => 2,
),
array(
'name' => __( '3 Columns', 'fl-builder' ),
'id' => '3-cols',
'count' => 3,
),
array(
'name' => __( '4 Columns', 'fl-builder' ),
'id' => '4-cols',
'count' => 4,
),
array(
'name' => __( '5 Columns', 'fl-builder' ),
'id' => '5-cols',
'count' => 5,
),
array(
'name' => __( '6 Columns', 'fl-builder' ),
'id' => '6-cols',
'count' => 6,
),
array(
'name' => __( 'Left Sidebar', 'fl-builder' ),
'id' => 'left-sidebar',
'count' => 2,
),
array(
'name' => __( 'Right Sidebar', 'fl-builder' ),
'id' => 'right-sidebar',
'count' => 2,
),
array(
'name' => __( 'Left & Right Sidebar', 'fl-builder' ),
'id' => 'left-right-sidebar',
'count' => 3,
),
);
return $cols;
}
/**
* Loads the files for all core builder settings.
*
* @since 1.0
* @return void
*/
static public function load_settings() {
require_once FL_BUILDER_DIR . 'includes/global-settings.php';
require_once FL_BUILDER_DIR . 'includes/layout-settings.php';
require_once FL_BUILDER_DIR . 'includes/row-settings.php';
require_once FL_BUILDER_DIR . 'includes/column-settings.php';
require_once FL_BUILDER_DIR . 'includes/module-settings.php';
}
/**
* Register a settings form with the builder.
*
* @since 1.0
* @param string $id The form id.
* @param array $form The form data.
* @return void
*/
static public function register_settings_form( $id, $form ) {
/**
* Use this filter to modify the config array for a settings form when it is registered.
* @see fl_builder_register_settings_form
* @link https://docs.wpbeaverbuilder.com/beaver-builder/developer/tutorials-guides/common-beaver-builder-filter-examples
*/
self::$settings_forms[ $id ] = apply_filters( 'fl_builder_register_settings_form', $form, $id );
// Since 2.0 we need to store the form ID on each tab to ensure that
// it's always available for rendering forms in JS on the frontend.
if ( isset( self::$settings_forms[ $id ]['tabs'] ) ) {
foreach ( self::$settings_forms[ $id ]['tabs'] as $tab_id => $tab ) {
self::$settings_forms[ $id ]['tabs'][ $tab_id ]['form_id'] = $id;
}
} else {
self::$settings_forms[ $id ]['form_id'] = $id;
}
}
/**
* Returns the data for a settings form.
*
* @since 1.0
* @param string $id The form id.
* @return array
*/
static public function get_settings_form( $id ) {
return isset( self::$settings_forms[ $id ] ) ? self::$settings_forms[ $id ] : false;
}
/**
* Returns an array of fields in a settings form.
*
* @since 1.0
* @param array|string $form The form data array or the form key. If key, group must be set as well.
* @param string The form group. Either general or module.
* @return array
*/
static public function get_settings_form_fields( $form, $group = null ) {
$fields = array();
if ( 'string' === gettype( $form ) ) {
if ( 'general' === $group ) {
$form = FLBuilderModel::$settings_forms[ $form ]['tabs'];
} elseif ( 'module' === $group ) {
$form = FLBuilderModel::$modules[ $form ]->form;
} else {
return $fields;
}
}
foreach ( (array) $form as $tab ) {
if ( isset( $tab['sections'] ) ) {
foreach ( $tab['sections'] as $section ) {
if ( isset( $section['fields'] ) ) {
foreach ( $section['fields'] as $name => $field ) {
if ( ! isset( $field['type'] ) ) {
continue;
}
$fields[ $name ] = $field;
}
}
}
}
}
return $fields;
}
/**
* Returns a settings object with the defaults for a form.
*
* @since 1.0
* @param string $type The type of form.
* @return object
*/
static public function get_settings_form_defaults( $type ) {
// Check to see if the defaults are cached first.
if ( isset( self::$settings_form_defaults[ $type ] ) ) {
return self::$settings_form_defaults[ $type ];
}
// They aren't cached, let's get them.
$defaults = new StdClass();
// Check the registered forms first.
if ( isset( self::$settings_forms[ $type ] ) ) {
$form_type = $type;
$tabs = self::$settings_forms[ $type ]['tabs'];
} elseif ( isset( self::$modules[ $type ] ) ) {
$form_type = $type . '-module';
$tabs = self::$modules[ $type ]->form;
} else {
return $defaults;
}
// Get the fields.
$fields = self::get_settings_form_fields( $tabs );
// Handle a few special cases before getting the defaults.
foreach ( $fields as $name => $field ) {
// Add the root name if needed later for synthetic fields like
// the dimension fields being synthesized below.
$fields[ $name ]['root_name'] = $name;
$field['root_name'] = $name;
// Handle dimension fields. We have to do it this way for backwards compat
// with old margin, padding, and border fields as the settings expect margin_top
// or margin_bottom to exist instead of just the margin key.
if ( 'dimension' == $field['type'] ) {
if ( isset( $field['keys'] ) ) {
$keys = array_keys( $field['keys'] );
} else {
$keys = array( 'top', 'right', 'bottom', 'left' );
}
foreach ( $keys as $position ) {
$fields[ $name . '_' . $position ] = $field;
}
unset( $fields[ $name ] );
}
}
// Loop through the fields and get the defaults.
foreach ( $fields as $name => $field ) {
$default = isset( $field['default'] ) ? $field['default'] : '';
$is_multiple = isset( $field['multiple'] ) && true === $field['multiple'];
$supports_multiple = 'editor' != $field['type'] && 'service' != $field['type'];
$responsive = isset( $field['responsive'] ) && $field['responsive'] ? $field['responsive'] : false;
// Get the default unit if this field has more than one unit.
if ( isset( $field['units'] ) && is_array( $field['units'] ) && count( $field['units'] ) > 1 ) {
$default_unit = isset( $field['default_unit'] ) ? $field['default_unit'] : $field['units'][0];
} else {
$default_unit = null;
}
// Set the default.
if ( $is_multiple && $supports_multiple ) {
$defaults->$name = is_array( $default ) ? $default : array( $default );
} else {
foreach ( array( 'default', 'large', 'medium', 'responsive' ) as $device ) {
if ( ! $responsive && 'default' !== $device ) {
continue;
}
$response_suffix = ( 'default' == $device ? '' : '_' . $device );
$responsive_name = $name . $response_suffix;
$unit_name = $field['root_name'] . $response_suffix . '_unit';
// Add the default value.
if ( is_array( $responsive ) && isset( $responsive['default'] ) && isset( $responsive['default'][ $device ] ) ) {
$defaults->{ $responsive_name } = $responsive['default'][ $device ];
} elseif ( 'default' == $device ) {
$defaults->$name = $default;
} else {
$defaults->{ $responsive_name } = '';
}
// Add the unit default value.
if ( null !== $default_unit ) {
if ( is_array( $responsive ) && isset( $responsive['default_unit'] ) && isset( $responsive['default_unit'][ $device ] ) ) {
$defaults->{ $unit_name } = $responsive['default_unit'][ $device ];
} else {
$defaults->{ $unit_name } = $default_unit;
}
}
// Add the photo source default value.
if ( 'photo' === $field['type'] ) {
$defaults->{ $name . $response_suffix . '_src' } = '';
}
// Add the link target and nofollow default values.
if ( 'link' === $field['type'] ) {
if ( isset( $field['show_target'] ) && $field['show_target'] ) {
$defaults->{ $name . '_target' } = '_self';
}
if ( isset( $field['show_nofollow'] ) && $field['show_nofollow'] ) {
$defaults->{ $name . '_nofollow' } = 'no';
}
}
}
}
}
/**
* Use this filter to change the defaults for any of the settings forms in the builder including global, row, column and module settings.
* @see fl_builder_settings_form_defaults
* @link https://docs.wpbeaverbuilder.com/beaver-builder/developer/tutorials-guides/common-beaver-builder-filter-examples
*/
self::$settings_form_defaults[ $type ] = apply_filters( 'fl_builder_settings_form_defaults', $defaults, $form_type );
return self::$settings_form_defaults[ $type ];
}
/**
* Merges the default settings for nested forms.
*
* @since 1.10.8
* @param string $type The type of form. Either general or module.
* @param string $form The form ID.
* @param object $settings The module settings object.
* @return object
*/
static public function merge_nested_form_defaults( $type, $form, $settings ) {
// Get the fields.
if ( 'module' === $type && isset( self::$modules[ $form ] ) ) {
$fields = self::get_settings_form_fields( self::$modules[ $form ]->form );
} elseif ( isset( self::$settings_forms[ $form ] ) ) {
$fields = self::get_settings_form_fields( self::$settings_forms[ $form ]['tabs'] );
} else {
return $settings;
}
// Loop through the settings.
foreach ( $settings as $key => $val ) {
// Make sure this field is a nested form.
if ( ! isset( $fields[ $key ]['form'] ) ) {
continue;
}
// Get the nested form defaults.
$nested_defaults = self::get_settings_form_defaults( $fields[ $key ]['form'] );
// Merge the defaults.
if ( is_array( $val ) ) {
foreach ( $val as $nested_key => $nested_val ) {
$settings->{ $key }[ $nested_key ] = (object) array_merge( (array) $nested_defaults, (array) $nested_val );
}
} elseif ( ! empty( $settings->{ $key } ) ) {
$settings->{ $key } = (object) array_merge( (array) $nested_defaults, (array) $settings->{ $key } );
} else {
$settings->{ $key } = (object) $nested_defaults;
}
}
return $settings;
}
/**
* Save the settings for a node.
*
* @since 1.0
* @param string $node_id The node ID.
* @param object $settings The settings to save.
* @return void
*/
static public function save_settings( $node_id = null, $settings = null ) {
$node = self::get_node( $node_id );
if ( ! FLBuilderModel::user_has_unfiltered_html() && true !== self::verify_settings( $settings ) ) {
return array(
'node_id' => $node->node,
'settings' => $node->settings,
'layout' => FLBuilderAJAXLayout::render(),
);
}
$new_settings = (object) array_merge( (array) $node->settings, (array) $settings );
$template_post_id = self::is_node_global( $node );
$post_id = self::get_post_id();
// Process the settings.
$new_settings = self::process_node_settings( $node, $new_settings );
// Save the settings to the node.
$data = self::get_layout_data();
$data[ $node_id ]->settings = $new_settings;
// Update the layout data.
self::update_layout_data( $data );
// Save settings for a global node template?
if ( $template_post_id && $template_post_id !== $post_id && ! self::is_post_node_template( false, $node->type ) ) {
// Get the template data.
$template_data = self::get_layout_data( 'published', $template_post_id );
// Update the template node settings.
$template_data[ $node->template_node_id ]->settings = $new_settings;
// Save the template data.
self::update_layout_data( $template_data, 'published', $template_post_id );
self::update_layout_data( $template_data, 'draft', $template_post_id );
// Delete the template asset cache.
self::delete_all_asset_cache( $template_post_id );
self::delete_node_template_asset_cache( $template_post_id );
}
// Return the processed settings and new layout.
return array(
'node_id' => $node->node,
'settings' => $new_settings,
'layout' => FLBuilderAJAXLayout::render(),
);
}
/**
* Verify the settings for a node to make sure they
* can be saved safely.
*
* @since 2.4.1
* @param object $settings The settings to verify.
* @return bool
*/
static public function verify_settings( $settings ) {
return self::verify_settings_kses( $settings );
}
/**
* Verify the settings for a node by running them through wp_kses.
* Any settings that have changed mean unallowed code was entered.
*
* @since 2.4.1
* @param object $settings The settings to verify.
* @return bool
*/
public static function verify_settings_kses( $settings ) {
if ( ! has_filter( 'safe_style_css', '__return_empty_array' ) ) {
add_filter( 'safe_style_css', '__return_empty_array' );
}
foreach ( $settings as $key => $value ) {
if ( is_string( $value ) ) {
$value = stripslashes( $value );
$sanitized = wp_kses_post( $value );
if ( json_encode( $sanitized ) !== json_encode( self::fix_kses( $value ) ) ) {
remove_filter( 'safe_style_css', '__return_empty_array' );
$output = array(
'diff' => wp_text_diff( $value, $sanitized, array( 'show_split_view' => false ) ),
'value' => self::fix_kses( $value ),
'parsed' => $sanitized,
'key' => $key,
);
return $output;
}
} else {
if ( is_object( $value ) || is_array( $value ) ) {
if ( ! self::verify_settings_kses( $value ) ) {
remove_filter( 'safe_style_css', '__return_empty_array' );
return false;
}
}
}
}
remove_filter( 'safe_style_css', '__return_empty_array' );
return true;
}
/**
* Add a space to self closing tags and other things if there isn't one because kses will and checks will fail.
* @since 2.4.2
*/
static public function fix_kses( $value ) {
// fix & -> &
$value = preg_replace( '/&([a-z0-9#]+);/i', '&$1;', $value );
$value = preg_replace( '#(&)(?!(.*);)#i', '&', $value );
// fix
->
$value = preg_replace( '#(<[a-z]+)(\/>)#', '$1 $2', $value );
return $value;
}
/**
* Sanitizes settings for a form.
*
* @since 2.0
* @param string $form
* @param string $group
* @param object $settings
* @return object
*/
static public function sanitize_settings( $settings, $form, $group ) {
$fields = FLBuilderModel::get_settings_form_fields( $form, $group );
foreach ( $settings as $name => $value ) {
if ( ! isset( $fields[ $name ] ) ) {
continue;
} elseif ( isset( $fields[ $name ]['sanitize'] ) ) {
$settings->$name = call_user_func_array( $fields[ $name ]['sanitize'], array( $value ) );
}
}
return $settings;
}
/**
* Adds slashes to settings going into the database as WordPress
* removes them when we save using update_metadata. This is done
* to ensure slashes in user input aren't removed.
*
* @since 1.5.6
* @param mixed $data The data to slash.
* @return mixed The slashed data.
*/
static public function slash_settings( $data ) {
if ( is_array( $data ) ) {
foreach ( $data as $key => $val ) {
$data[ $key ] = self::slash_settings( $val );
}
} elseif ( is_object( $data ) ) {
foreach ( $data as $key => $val ) {
$data->$key = self::slash_settings( $val );
}
} elseif ( is_string( $data ) ) {
$data = wp_slash( $data );
}
return $data;
}
/**
* Merge defaults into a settings object.
*
* @since 1.0
* @param object $settings Reference to a settings object.
* @param array $defaults The defaults to merge in.
* @return void
*/
static public function default_settings( &$settings, $defaults ) {
foreach ( $defaults as $name => $value ) {
if ( ! isset( $settings->$name ) ) {
$settings->$name = $value;
}
}
}
/**
* Get the global builder settings.
*
* @since 1.0
* @return object
*/
static public function get_global_settings() {
if ( null === self::$global_settings ) {
$settings = get_option( '_fl_builder_settings' );
$defaults = self::get_settings_form_defaults( 'global' );
if ( ! $settings ) {
$settings = new StdClass();
}
// Special handling for color scheme
if ( is_array( $settings ) ) {
$settings['color_scheme'] = FLBuilderUserSettings::get_color_scheme();
} else {
$settings->color_scheme = FLBuilderUserSettings::get_color_scheme();
}
// Merge in defaults and cache settings
self::$global_settings = (object) array_merge( (array) $defaults, (array) $settings );
self::$global_settings = self::merge_nested_form_defaults( 'general', 'global', self::$global_settings );
self::$global_settings = apply_filters( 'fl_builder_get_global_settings', self::$global_settings );
}
return self::$global_settings;
}
/**
* Save the global builder settings.
*
* @since 1.0
* @param array $settings The new global settings.
* @return object
*/
static public function save_global_settings( $settings = array() ) {
$old_settings = self::get_global_settings();
$settings = self::sanitize_global( $settings );
$new_settings = (object) array_merge( (array) $old_settings, (array) $settings );
/**
* Special handling for color scheme
* Color scheme is actually a user setting, not a global setting. It just appears in the global settings form.
*/
if ( property_exists( $new_settings, 'color_scheme' ) ) {
FLBuilderUserSettings::save_color_scheme( $new_settings->color_scheme );
// No need to store this in global settings
unset( $new_settings->color_scheme );
}
// delete all posts assets.
self::delete_asset_cache_for_all_posts();
// remove old global settings.
self::$global_settings = null;
// apply filter beofre update.
$new_settings = apply_filters( 'fl_builder_before_save_global_settings', $new_settings );
// update db with new settings.
FLBuilderUtils::update_option( '_fl_builder_settings', $new_settings );
return self::get_global_settings();
}
/**
* Sanitize global options on save.
* @since 2.2.2
*/
static public function sanitize_global( $settings ) {
$fields = self::get_settings_form_fields( 'global', 'general' );
foreach ( $settings as $name => $value ) {
if ( ! isset( $fields[ $name ] ) ) {
continue;
} elseif ( isset( $fields[ $name ]['sanitize'] ) ) {
$settings[ $name ] = call_user_func_array( $fields[ $name ]['sanitize'], array( $value ) );
}
}
return $settings;
}
/**
* Duplicate the current post.
*
* @since 1.0
* @return int The new post ID.
*/
static public function duplicate_post( $post_id = false ) {
global $wpdb;
$post_id = ( ! $post_id ) ? self::get_post_id() : $post_id;
$post = get_post( $post_id );
$current_user = wp_get_current_user();
$template_id = false;
// Duplicate the post.
$data = array(
'comment_status' => $post->comment_status,
'ping_status' => $post->ping_status,
'post_author' => $current_user->ID,
'post_content' => $post->post_content,
'post_excerpt' => $post->post_excerpt,
'post_name' => $post->post_name . '-copy',
'post_parent' => $post->post_parent,
'post_password' => $post->post_password,
'post_status' => 'draft',
/* translators: %s: post/page title */
'post_title' => sprintf( _x( 'Copy of %s', '%s stands for post/page title.', 'fl-builder' ), $post->post_title ),
'post_type' => $post->post_type,
'to_ping' => $post->to_ping,
'menu_order' => $post->menu_order,
);
// Get the new post id.
$new_post_id = wp_insert_post( $data );
// Duplicate post meta.
$post_meta = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM {$wpdb->postmeta} WHERE post_id = %d", $post_id ) );
if ( count( $post_meta ) !== 0 ) {
foreach ( $post_meta as $meta_info ) {
$meta_key = $meta_info->meta_key;
if ( '_fl_builder_template_id' == $meta_key ) {
$meta_value = self::generate_node_id();
} else {
$meta_value = addslashes( $meta_info->meta_value );
}
// @codingStandardsIgnoreStart
$wpdb->query( "INSERT INTO {$wpdb->postmeta} (post_id, meta_key, meta_value) values ({$new_post_id}, '{$meta_key}', '{$meta_value}')" );
// @codingStandardsIgnoreEnd
}
}
// Flush the cache so new meta is returned in wp meta functions.
wp_cache_flush();
// Duplicate post terms.
$taxonomies = get_object_taxonomies( $post->post_type );
foreach ( $taxonomies as $taxonomy ) {
$post_terms = wp_get_object_terms( $post_id, $taxonomy );
for ( $i = 0; $i < count( $post_terms ); $i++ ) {
wp_set_object_terms( $new_post_id, $post_terms[ $i ]->slug, $taxonomy, true );
}
}
// Get the duplicated layout data.
$data = self::get_layout_data( 'published', $new_post_id );
// Generate new node ids.
$data = self::generate_new_node_ids( $data );
// Update template ID and template node ID
$template_id = get_post_meta( $new_post_id, '_fl_builder_template_id', true );
$global = get_post_meta( $post_id, '_fl_builder_template_global', true );
if ( $template_id && $global ) {
foreach ( $data as $node_id => $node ) {
$data[ $node_id ]->template_id = $template_id;
$data[ $node_id ]->template_node_id = $node_id;
}
}
// Save the duplicated layout data.
self::update_layout_data( $data, 'published', $new_post_id );
// Also update draft data
self::update_layout_data( $data, 'draft', $new_post_id );
// Return the new post id.
return $new_post_id;
}
/**
* Deletes all layout data and asset cache for a post.
*
* @since 1.0
* @param int $post_id The post ID to delete data and cache for.
* @return void
*/
static public function delete_post( $post_id ) {
// If this is a global template, unlink it from other posts.
self::unlink_global_node_template_from_all_posts( $post_id );
// Delete all published and draft data.
self::delete_layout_data( 'published', $post_id );
self::delete_layout_data( 'draft', $post_id );
// Delete all css and js.
self::delete_all_asset_cache( $post_id );
}
/**
* Save a revision of a builder layout.
*
* @since 1.0
* @param int $post_id
* @return void
*/
static public function save_revision( $post_id, $post, $update ) {
$parent_id = wp_is_post_revision( $post_id );
if ( $parent_id ) {
$parent = get_post( $parent_id );
$data = self::get_layout_data( 'published', $parent->ID );
$settings = self::get_layout_settings( 'published', $parent->ID );
if ( ! empty( $data ) ) {
self::update_layout_data( $data, 'published', $post_id );
self::update_layout_settings( $settings, 'published', $post_id );
}
}
}
/**
* Limit the amount of revisions possible for fl-builder-template type.
* @since 2.1.5
*/
static public function limit_revisions( $num, $post ) {
if ( 'fl-builder-template' == $post->post_type ) {
/**
* Limit the amount of revisions for the fl-builder-template type.
* @see fl_builder_template_revisions
*/
$num = apply_filters( 'fl_builder_template_revisions', 25 );
}
return $num;
}
/**
* Maybe save a post revision when templates/rows etc are published.
* @since 2.1.5
*/
static public function save_layout_revision( $post_id ) {
add_filter( 'wp_save_post_revision_post_has_changed', array( __CLASS__, 'save_layout_revision_changed_filter' ), 10, 3 );
wp_save_post_revision( $post_id );
remove_filter( 'wp_save_post_revision_post_has_changed', array( __CLASS__, 'save_layout_revision_changed_filter' ), 10, 3 );
}
/**
* Filter save_layout_revision_changed_filter return true here if the builder data has $post_has_changed
* this forces a post revision.
* @since 2.1.5
*/
static public function save_layout_revision_changed_filter( $post_has_changed, $last_revision, $post ) {
// get builder data for compare
$old = serialize( get_post_meta( $last_revision->ID, '_fl_builder_data', true ) );
$new = serialize( get_post_meta( $post->ID, '_fl_builder_data', true ) );
return $old != $new;
}
/**
* Restore a revision of a builder layout.
*
* @since 1.0
* @param int $post_id
* @param int $revision_id
* @return void
*/
static public function restore_revision( $post_id, $revision_id ) {
$post = get_post( $post_id );
$revision = get_post( $revision_id );
if ( $revision ) {
$data = self::get_layout_data( 'published', $revision->ID );
$settings = self::get_layout_settings( 'published', $revision->ID );
if ( ! empty( $data ) ) {
self::update_layout_data( $data, 'published', $post_id );
self::update_layout_data( $data, 'draft', $post_id );
self::update_layout_settings( $settings, 'published', $post_id );
self::update_layout_settings( $settings, 'draft', $post_id );
} else {
self::delete_layout_data( 'published', $post_id );
self::delete_layout_data( 'draft', $post_id );
self::delete_layout_settings( 'published', $post_id );
self::delete_layout_settings( 'draft', $post_id );
}
self::delete_all_asset_cache( $post_id );
}
}
/**
* Get all of the layout data for a post. We use get_metadata
* here instead of get_post_meta to ensure revisions are queried accordingly.
*
* @since 1.0
* @param string $status Either published or draft.
* @param int $post_id The ID of the post to get data for.
* @return array
*/
static public function get_layout_data( $status = null, $post_id = null ) {
$post_id = ! $post_id ? self::get_post_id() : $post_id;
$status = ! $status ? self::get_node_status() : $status;
// Get layout metadata.
if ( 'published' == $status || 'revision' == get_post_type( $post_id ) ) {
if ( isset( self::$published_layout_data[ $post_id ] ) ) {
$data = self::$published_layout_data[ $post_id ];
} else {
$data = get_metadata( 'post', $post_id, '_fl_builder_data', true );
$data = self::clean_layout_data( $data );
$data = FLBuilderSettingsCompat::filter_layout_data( $data );
self::$published_layout_data[ $post_id ] = apply_filters( 'fl_builder_get_layout_metadata', $data, $status, $post_id );
}
} elseif ( 'draft' == $status ) {
if ( isset( self::$draft_layout_data[ $post_id ] ) ) {
$data = self::$draft_layout_data[ $post_id ];
} else {
$data = get_metadata( 'post', $post_id, '_fl_builder_draft', true );
$data = self::clean_layout_data( $data );
$data = FLBuilderSettingsCompat::filter_layout_data( $data );
self::$draft_layout_data[ $post_id ] = apply_filters( 'fl_builder_get_layout_metadata', $data, $status, $post_id );
}
}
// Make sure we have an array.
if ( empty( $data ) ) {
$data = array();
}
// Clone the layout data to ensure the cache remains intact.
foreach ( $data as $node_id => $node ) {
if ( is_object( $node ) ) {
$data[ $node_id ] = clone $node;
}
}
/**
* Return the data.
* @see fl_builder_layout_data
*/
return apply_filters( 'fl_builder_layout_data', $data, $status, $post_id );
}
/**
* Update the layout data for a post. We use update_metadata
* here instead of update_post_meta to ensure revisions are updated accordingly.
*
* @since 1.0
* @param array $data The layout data to update.
* @param string $status Either published or draft.
* @param int $post_id The ID of the post to update.
* @return void
*/
static public function update_layout_data( $data, $status = null, $post_id = null ) {
$post_id = ! $post_id ? self::get_post_id() : $post_id;
$status = ! $status ? self::get_node_status() : $status;
$key = 'published' == $status ? '_fl_builder_data' : '_fl_builder_draft';
$raw_data = get_metadata( 'post', $post_id, $key );
/**
* @since 2.6
* @see fl_builder_enable_small_data_mode
*/
if ( apply_filters( 'fl_builder_enable_small_data_mode', false ) ) {
$data = self::slash_settings( self::clean_layout_data( $data ) );
if ( 'published' === $status ) {
foreach ( $data as $node_id => $node ) {
if ( isset( $node->settings ) ) {
$data[ $node_id ]->settings = (object) self::array_remove_by_values( (array) $node->settings, array( '', null, array() ) );
}
}
}
} else {
$data = self::slash_settings( self::clean_layout_data( $data ) );
}
// Update the data.
if ( 0 === count( $raw_data ) ) {
add_metadata( 'post', $post_id, $key, $data );
} else {
update_metadata( 'post', $post_id, $key, $data );
}
// Cache the data.
if ( 'published' == $status ) {
self::$published_layout_data[ $post_id ] = $data;
} elseif ( 'draft' == $status ) {
self::$draft_layout_data[ $post_id ] = $data;
}
}
/**
* Delete the layout data for a post.
*
* @since 1.0
* @param string $status Either published or draft.
* @param int $post_id The ID of the post to delete data.
* @return void
*/
static public function delete_layout_data( $status = null, $post_id = null ) {
// Make sure we have a status to delete.
if ( ! $status ) {
return;
}
// Get the post id.
$post_id = ! $post_id ? self::get_post_id() : $post_id;
// Get the data to delete.
$data = self::get_layout_data( $status, $post_id );
// Delete the nodes.
foreach ( $data as $node ) {
self::call_module_delete( $node );
}
// Update the layout data.
self::update_layout_data( array(), $status, $post_id );
}
/**
* Ensures the integrity of layout data key/value pairs.
*
* Also makes sure we're not serializing any FLBuilderModule
* instances because those are too big and bloat the data array.
*
* @since 1.0
* @param array $data An array of layout data.
* @return array
*/
static public function clean_layout_data( $data = array() ) {
$cleaned = array();
if ( is_array( $data ) ) {
foreach ( $data as $node ) {
if ( is_object( $node ) && isset( $node->node ) ) {
if ( is_a( $node, 'FLBuilderModule' ) ) {
$cleaned[ $node->node ] = new StdClass();
$cleaned[ $node->node ]->node = $node->node;
$cleaned[ $node->node ]->type = $node->type;
$cleaned[ $node->node ]->parent = $node->parent;
$cleaned[ $node->node ]->position = $node->position;
$cleaned[ $node->node ]->settings = $node->settings;
} else {
$cleaned[ $node->node ] = $node;
}
$cleaned[ $node->node ]->global = self::is_node_global( $node );
}
}
}
return $cleaned;
}
/**
* Remove all empty values from the settings object recursively to save ~60% db size
* @since 2.3
* @param array $haystack
* @param array $values
* @param array $whitelist
*
* @return array
*/
static public function array_remove_by_values( $haystack, $values, $whitelist = array( 'animation', 'style', 'post_columns' ) ) {
foreach ( $haystack as $key => $value ) {
if ( is_array( $value ) ) {
$haystack[ $key ] = self::array_remove_by_values( $haystack[ $key ], $values );
}
if ( in_array( $haystack[ $key ], $values, true ) && ! in_array( $key, $whitelist ) ) {
unset( $haystack[ $key ] );
}
}
return $haystack;
}
/**
* Detect if the current layout has previously drafted changes.
*
* @since 2.0
* @return bool
*/
static public function layout_has_drafted_changes() {
$post_id = FLBuilderModel::get_post_id();
$published = serialize( self::get_layout_data( 'published', $post_id ) );
$draft = serialize( self::get_layout_data( 'draft', $post_id ) );
if ( $published != $draft ) {
return true;
}
return false;
}
/**
* Get the builder settings for a layout.
*
* @since 1.7
* @param string $status Either published or draft.
* @param int $post_id The ID of the post to get settings for.
* @return object
*/
static public function get_layout_settings( $status = null, $post_id = null ) {
$status = ! $status ? self::get_node_status() : $status;
$post_id = ! $post_id ? self::get_post_id() : $post_id;
$key = 'published' == $status ? '_fl_builder_data_settings' : '_fl_builder_draft_settings';
$settings = get_metadata( 'post', $post_id, $key, true );
$defaults = self::get_settings_form_defaults( 'layout' );
if ( ! $settings ) {
$settings = new StdClass();
}
$settings = (object) array_merge( (array) $defaults, (array) $settings );
return apply_filters( 'fl_builder_layout_settings', $settings, $status, $post_id );
}
/**
* Updates the layout settings for a post.
*
* @since 1.7
* @param array $settings The new layout settings.
* @param string $status Either published or draft.
* @param int $post_id The ID of the post to update.
* @return object
*/
static public function update_layout_settings( $settings = array(), $status = null, $post_id = null ) {
$status = ! $status ? self::get_node_status() : $status;
$post_id = ! $post_id ? self::get_post_id() : $post_id;
$key = 'published' == $status ? '_fl_builder_data_settings' : '_fl_builder_draft_settings';
$raw_settings = get_metadata( 'post', $post_id, $key );
$old_settings = self::get_layout_settings( $status, $post_id );
$new_settings = (object) array_merge( (array) $old_settings, (array) $settings );
if ( 0 === count( $raw_settings ) ) {
add_metadata( 'post', $post_id, $key, self::slash_settings( $new_settings ) );
} else {
update_metadata( 'post', $post_id, $key, self::slash_settings( $new_settings ) );
}
return $new_settings;
}
/**
* Called via AJAX to save the layout settings.
*
* @since 1.7
* @param array $settings The new layout settings.
* @param string $status Either published or draft.
* @param int $post_id The ID of the post to update.
* @return object
*/
static public function save_layout_settings( $settings = array(), $status = null, $post_id = null ) {
return self::update_layout_settings( $settings, $status, $post_id );
}
/**
* Delete the layout settings for a post.
*
* @since 1.7
* @param string $status Either published or draft.
* @param int $post_id The ID of a post whose settings to delete.
* @return void
*/
static public function delete_layout_settings( $status = null, $post_id = null ) {
$status = ! $status ? self::get_node_status() : $status;
$post_id = ! $post_id ? self::get_post_id() : $post_id;
$key = 'published' == $status ? '_fl_builder_data_settings' : '_fl_builder_draft_settings';
update_metadata( 'post', $post_id, $key, array() );
}
/**
* Merge two sets of layout settings together.
*
* @since 1.7
* @param object $settings The layout settings to merge into.
* @param object $merge_settings The layout settings to merge.
* @return object
*/
static public function merge_layout_settings( $settings, $merge_settings ) {
$keys = array( 'css', 'js' );
foreach ( $keys as $key ) {
if ( empty( $merge_settings->{$key} ) ) {
continue;
} elseif ( strstr( $settings->{$key}, $merge_settings->{$key} ) ) {
continue;
} else {
if ( ! empty( $settings->{$key} ) ) {
$settings->{$key} .= "\n";
}
$settings->{$key} .= $merge_settings->{$key};
}
}
return $settings;
}
/**
* Clears a draft layout and saves a new draft using
* the currently published layout data.
*
* @since 1.0
* @return void
*/
static public function clear_draft_layout() {
$post_id = self::get_post_id();
$data = self::get_layout_data( 'published', $post_id );
$settings = self::get_layout_settings( 'published', $post_id );
// Delete the old draft layout.
self::delete_layout_data( 'draft' );
// Save the new draft layout.
self::update_layout_data( $data, 'draft', $post_id );
// Save the new draft layout settings.
self::update_layout_settings( $settings, 'draft', $post_id );
// Clear the asset cache.
self::delete_all_asset_cache( $post_id );
}
/**
* Saves layout data when a user chooses to publish.
*
* @since 1.0
* @param bool $publish Whether to publish the parent post or not.
* @return void
*/
static public function save_layout( $publish = true ) {
$editor_content = FLBuilder::render_editor_content();
$post_id = self::get_post_id();
$data = self::get_layout_data( 'draft', $post_id );
$settings = self::get_layout_settings( 'draft', $post_id );
/**
* This action allows you to hook into before the data is saved for a layout.
* @see fl_builder_before_save_layout
* @link https://docs.wpbeaverbuilder.com/beaver-builder/developer/tutorials-guides/common-beaver-builder-filter-examples
*/
do_action( 'fl_builder_before_save_layout', $post_id, $publish, $data, $settings );
// Delete the old published layout.
self::delete_layout_data( 'published', $post_id );
self::delete_layout_settings( 'published', $post_id );
// Save the new published layout.
self::update_layout_data( $data, 'published', $post_id );
self::update_layout_settings( $settings, 'published', $post_id );
// Clear the asset cache.
self::delete_all_asset_cache( $post_id );
self::delete_node_template_asset_cache( $post_id );
// Enable the builder to take over the post content.
self::enable();
// Get the post status.
$post_status = get_post_status( $post_id );
// Publish the post?
if ( $publish ) {
$is_draft = strstr( $post_status, 'draft' );
$is_pending = strstr( $post_status, 'pending' );
if ( current_user_can( 'publish_posts' ) ) {
$post_status = $is_draft || $is_pending ? 'publish' : $post_status;
} elseif ( $is_draft ) {
$post_status = 'pending';
}
}
// Update the post with stripped down content.
wp_update_post(array(
'ID' => self::get_post_id(),
'post_status' => $post_status,
'post_content' => $editor_content,
));
// Rerender the assets for this layout.
FLBuilder::render_assets();
self::cleanup_post_data( $post_id );
/**
* This action allows you to hook into after the data is saved for a layout.
* @see fl_builder_after_save_layout
* @link https://docs.wpbeaverbuilder.com/beaver-builder/developer/tutorials-guides/common-beaver-builder-filter-examples
*/
do_action( 'fl_builder_after_save_layout', $post_id, $publish, $data, $settings );
}
/**
* Publishes the current builder layout only if the parent post
* is still a draft. The layout will be published but the parent
* post will remain a draft so the post can be scheduled and the
* layout can be viewed while the builder is not active. If the
* parent post is already published, nothing happens.
*
* @since 1.6.1
* @return void
*/
static public function save_draft() {
$post_id = self::get_post_id();
$post_status = get_post_status( $post_id );
if ( strstr( $post_status, 'draft' ) ) {
self::save_layout( false );
}
self::cleanup_post_data( $post_id, false );
/**
* After draft is saved.
* @see fl_builder_after_save_draft
*/
do_action( 'fl_builder_after_save_draft', $post_id, $post_status );
}
static public function cleanup_post_data( $post_id, $history = true ) {
// remove any post lock
delete_post_meta( $post_id, '_edit_lock' );
if ( ! $history ) {
return;
}
// delete old states
FLBuilderHistoryManager::delete_states( $post_id );
}
/**
* Duplicates a layout for WPML when the copy from original
* button has been clicked.
*
* @since 1.1.7
* @param int $original_post_id
* @param int $new_post_id
* @return array
*/
static public function duplicate_wpml_layout( $original_post_id = null, $new_post_id = null ) {
$post_data = self::get_post_data();
$original_post_id = isset( $post_data['original_post_id'] ) ? $post_data['original_post_id'] : $original_post_id;
$new_post_id = isset( $post_data['post_id'] ) ? $post_data['post_id'] : $new_post_id;
$enabled = get_post_meta( $original_post_id, '_fl_builder_enabled', true );
$published = self::get_layout_data( 'published', $original_post_id );
$draft = self::get_layout_data( 'draft', $original_post_id );
$response = array(
'enabled' => false,
'has_layout' => false,
);
if ( ! empty( $enabled ) ) {
update_post_meta( $new_post_id, '_fl_builder_enabled', true );
$response['enabled'] = true;
}
if ( ! empty( $published ) ) {
self::update_layout_data( $published, 'published', $new_post_id );
$response['has_layout'] = true;
}
if ( ! empty( $draft ) ) {
self::update_layout_data( $draft, 'draft', $new_post_id );
$response['has_layout'] = true;
}
return $response;
}
/**
* Returns the type of templates that are enabled.
*
* @since 1.1.3
* @return string
*/
static public function get_enabled_templates() {
$value = self::get_admin_settings_option( '_fl_builder_enabled_templates', true );
return ! $value ? 'enabled' : $value;
}
/**
* Checks to see if the current post is a user template.
*
* @since 1.6.3
* @param string $type The type of user template to check for.
* @return bool
*/
static public function is_post_user_template( $type = null ) {
$post = FLBuilderModel::get_post();
if ( ! $post ) {
return false;
} elseif ( 'fl-builder-template' == $post->post_type ) {
if ( null === $type ) {
return true;
} else {
$saved_type = self::get_user_template_type( $post->ID );
if ( $saved_type == $type ) {
return true;
}
}
}
return false;
}
/**
* Saves a user defined template via AJAX.
*
* @since 1.1.3
* @return void
*/
static public function save_user_template( $settings = array() ) {
// Save the user template post.
$post_id = wp_insert_post(array(
'post_title' => $settings['name'],
'post_type' => 'fl-builder-template',
'post_status' => 'publish',
'ping_status' => 'closed',
'comment_status' => 'closed',
));
// Set the template type.
wp_set_post_terms( $post_id, 'layout', 'fl-builder-template-type' );
// Add category
$cat = isset( $settings['category'] ) ? $settings['category'] : '';
$cat_added = '';
if ( __( 'Uncategorized', 'fl-builder' ) !== $cat && 'uncategorized' !== $cat ) {
$cat_added = wp_set_object_terms( $post_id, $cat, 'fl-builder-template-category' );
}
// Get the layout data and settings to copy.
$data = self::get_layout_data();
$layout_settings = self::get_layout_settings();
// Generate new node ids.
$data = self::generate_new_node_ids( $data );
// Save the template layout data and settings.
self::update_layout_data( $data, 'published', $post_id );
self::update_layout_settings( $layout_settings, 'published', $post_id );
// Enable the builder for this template.
update_post_meta( $post_id, '_fl_builder_enabled', true );
/**
* Allow extensions to hook into saving a user template.
* @see fl_builder_after_save_user_template
*/
do_action( 'fl_builder_after_save_user_template', $post_id );
$response = array(
'name' => $settings['name'],
'id' => get_post_meta( $post_id, '_fl_builder_template_id', true ),
'postId' => $post_id,
'image' => FLBuilder::plugin_url() . 'img/templates/blank.jpg',
'kind' => 'template',
'content' => 'layout',
'type' => 'user',
'isGlobal' => false,
'link' => add_query_arg( 'fl_builder', '', get_permalink( $post_id ) ),
'category' => array(),
);
if ( is_array( $cat_added ) && ! empty( $cat_added ) ) {
$term = get_term( $cat_added[0] );
$response['category'][ $term->slug ] = $term->name;
} else {
$response['category']['uncategorized'] = __( 'Uncategorized', 'fl-builder' );
}
return $response;
}
/**
* Returns data for all user defined templates.
*
* @since 1.1.3
* @since 1.5.7 Added support for template categories.
* @param string $type The type of user template to return.
* @return array
*/
static public function get_user_templates( $type = 'layout' ) {
if ( isset( self::$get_user_templates_cache[ $type ] ) && ! is_multisite() ) {
return self::$get_user_templates_cache[ $type ];
}
$categorized = array(
'uncategorized' => array(
'name' => _x( 'Uncategorized', 'Default user template category.', 'fl-builder' ),
'templates' => array(),
),
);
$posts = get_posts( array(
'post_type' => 'fl-builder-template',
'orderby' => 'menu_order title',
'order' => 'ASC',
'posts_per_page' => '-1',
'suppress_filters' => false,
'tax_query' => array(
array(
'taxonomy' => 'fl-builder-template-type',
'field' => 'slug',
'terms' => $type,
),
),
) );
$templates = array();
// Loop through templates posts and build the templates array.
foreach ( $posts as $post ) {
if ( has_post_thumbnail( $post->ID ) ) {
$image_data = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'medium_large' );
if ( is_array( $image_data ) ) {
$image = $image_data[0];
} else {
$image = FLBuilder::plugin_url() . 'img/templates/blank.jpg';
}
} else {
$image = FLBuilder::plugin_url() . 'img/templates/blank.jpg';
}
$templates[] = array(
'id' => get_post_meta( $post->ID, '_fl_builder_template_id', true ),
'postId' => $post->ID,
'name' => $post->post_title,
'image' => $image,
'kind' => 'template',
'type' => 'user',
'content' => FLBuilderModel::get_user_template_type( $post->ID ),
'isGlobal' => FLBuilderModel::is_post_global_node_template( $post->ID ),
'link' => add_query_arg( 'fl_builder', '', get_permalink( $post->ID ) ),
'category' => array(),
);
}
// Loop through templates and build the categorized array.
foreach ( $templates as $i => $template ) {
$cats = get_the_terms( $template['postId'], 'fl-builder-template-category' );
if ( ! $cats ) {
$cats = array();
}
if ( 0 === count( $cats ) || is_wp_error( $cats ) ) {
$template['category'] = array(
'uncategorized' => __( 'Uncategorized', 'fl-builder' ),
);
$categorized['uncategorized']['templates'][] = $template;
} else {
foreach ( $cats as $cat ) {
$template['category'][ $cat->slug ] = $cat->name;
}
foreach ( $cats as $cat ) {
if ( ! isset( $categorized[ $cat->slug ] ) ) {
$categorized[ $cat->slug ] = array(
'name' => $cat->name,
'templates' => array(),
);
}
$categorized[ $cat->slug ]['templates'][] = $template;
}
}
$templates[ $i ] = $template;
}
// Unset the uncategorized cat if no templates.
if ( 0 === count( $categorized['uncategorized']['templates'] ) ) {
unset( $categorized['uncategorized'] );
}
// sort the categories.
asort( $categorized );
self::$get_user_templates_cache[ $type ] = array(
'templates' => $templates,
'categorized' => $categorized,
);
return self::$get_user_templates_cache[ $type ];
}
/**
* Returns the template type for a user template.
*
* @since 1.6.3
* @param int $template_id The post ID of the template.
* @return string
*/
static public function get_user_template_type( $template_id = null ) {
if ( $template_id && isset( self::$node_template_types[ $template_id ] ) ) {
return self::$node_template_types[ $template_id ];
}
$post = $template_id ? get_post( $template_id ) : FLBuilderModel::get_post();
if ( ! is_object( $post ) || 'fl-builder-template' != $post->post_type ) {
return '';
} else {
$terms = get_the_terms( $post->ID, 'fl-builder-template-type' );
if ( ! $terms ) {
$terms = array();
}
$type = ( is_wp_error( $terms ) || 0 === count( $terms ) ) ? 'layout' : $terms[0]->slug;
self::$node_template_types[ $template_id ] = $type;
return $type;
}
}
/**
* Delete a user defined template.
*
* @since 1.1.3
* @param int $template_id The post ID of the template to delete.
* @return void
*/
static public function delete_user_template( $template_id = null ) {
if ( isset( $template_id ) ) {
wp_trash_post( $template_id, true );
}
}
/**
* Apply a user defined template to the current layout.
*
* @since 1.1.3
* @param int|object $template The post ID of the template to apply or a template data object.
* @param bool $append Whether to append the new template or replacing the existing layout.
* @return array
*/
static public function apply_user_template( $template = null, $append = false ) {
$data = array();
if ( $template ) {
// Delete existing nodes and settings?
if ( ! $append ) {
self::delete_layout_data( 'draft' );
self::delete_layout_settings( 'draft' );
}
// Insert new nodes if this is not a blank template.
if ( 'blank' != $template ) {
// Get the template data if $template is not an object.
if ( ! is_object( $template ) ) {
$template_id = $template;
$template = new StdClass();
$template->nodes = self::get_layout_data( 'published', $template_id );
$template->settings = self::get_layout_settings( 'published', $template_id );
}
// Get new ids for the template nodes.
$template->nodes = self::generate_new_node_ids( $template->nodes );
// Get the existing layout data and settings.
$layout_data = self::get_layout_data();
$layout_settings = self::get_layout_settings();
// Reposition rows if we are appending.
if ( $append ) {
$row_position = self::next_node_position( 'row' );
foreach ( $template->nodes as $node_id => $node ) {
if ( 'row' == $node->type ) {
$template->nodes[ $node_id ]->position += $row_position;
}
}
}
// Merge the layout data and settings.
$data = array_merge( $layout_data, $template->nodes );
$settings = self::merge_layout_settings( $layout_settings, $template->settings );
// Update the layout data and settings.
self::update_layout_data( $data );
self::update_layout_settings( $settings );
// Delete old asset cache.
self::delete_asset_cache();
}
}
// Return the layout.
return array(
'layout_css' => isset( $settings ) ? $settings->css : null,
'layout' => FLBuilderAJAXLayout::render(),
'config' => FLBuilderUISettingsForms::get_node_js_config(),
'newNodes' => $data,
);
}
/**
* Returns true if the node templates UI is enabled, false if not.
*
* @since 1.6.3
* @return bool
*/
static public function node_templates_enabled() {
$enabled_templates = self::get_enabled_templates();
if ( true === FL_BUILDER_LITE ) {
return false;
}
if ( 'core' == $enabled_templates || 'disabled' == $enabled_templates ) {
return false;
}
return true;
}
/**
* Checks to see if the current post is a node template.
*
* @since 1.6.3
* @param int $post_id If supplied, this post will be checked instead.
* @param string $type If supplied, this node type will be checked instead of all types.
* @return bool
*/
static public function is_post_node_template( $post_id = false, $type = null ) {
$post_id = $post_id ? $post_id : self::get_post_id();
$post = get_post( $post_id );
if ( ! $post ) {
return false;
} elseif ( 'fl-builder-template' == $post->post_type ) {
$saved_type = self::get_user_template_type( $post->ID );
if ( $type ) {
return $type === $saved_type;
} elseif ( in_array( $saved_type, array( 'row', 'column', 'module' ) ) ) {
return true;
}
}
return false;
}
/**
* Checks to see if the current post is a global node template.
*
* @since 1.6.3
* @param int $post_id If supplied, this post will be checked instead.
* @return bool
*/
static public function is_post_global_node_template( $post_id = false ) {
$post_id = $post_id ? $post_id : self::get_post_id();
if ( ! self::is_post_node_template( $post_id ) ) {
return false;
}
$global = get_post_meta( $post_id, '_fl_builder_template_global', true );
if ( ! $global ) {
return false;
}
return true;
}
/**
* Checks to see if a node is a global node.
*
* @since 1.6.3
* @param object $node The node object to check.
* @return bool|int
*/
static public function is_node_global( $node ) {
if ( ! isset( $node->template_id ) ) {
return false;
}
return self::get_node_template_post_id( $node->template_id );
}
/**
* Check the visibility settings that has been sets from any type of node (rows/columns/modules)
* This will be applied ONLY when the builder is not active.
*
* @param object $node The type of object to check
* @return bool
*/
static public function is_node_visible( $node ) {
global $wp_the_query;
$is_visible = true;
if ( isset( $node->settings->visibility_display ) && ( '' != $node->settings->visibility_display ) ) {
// For logged out users
if ( 'logged_out' == $node->settings->visibility_display && ! is_user_logged_in() ) {
$is_visible = true;
} elseif ( 'logged_in' == $node->settings->visibility_display && is_user_logged_in() ) {
$is_visible = true;
// User capability setting
if ( isset( $node->settings->visibility_user_capability ) && ! empty( $node->settings->visibility_user_capability ) ) {
if ( self::current_user_has_capability( trim( $node->settings->visibility_user_capability ) ) ) {
$is_visible = true;
} else {
$is_visible = false;
}
}
} elseif ( 0 == $node->settings->visibility_display ) {
$is_visible = false;
} else {
$is_visible = false;
}
}
return apply_filters( 'fl_builder_is_node_visible', $is_visible, $node );
}
/**
* Checks if a node has visibility rules or not.
*
* @param object $node
* @return bool
*/
static public function node_has_visibility_rules( $node ) {
$rules = false;
if ( isset( $node->settings->visibility_display ) && ( '' !== $node->settings->visibility_display ) ) {
$rules = true;
}
if ( isset( $node->settings->responsive_display ) && ( '' !== $node->settings->responsive_display ) ) {
$breakpoints = explode( ',', $node->settings->responsive_display );
if ( count( $breakpoints ) < 4 ) {
$rules = true;
}
}
return $rules;
}
/**
* Returns visibility rule.
*
* @param object $node
* @return bool
*/
static public function node_visibility_rules( $node ) {
$rule = isset( $node->settings->visibility_display ) ? $node->settings->visibility_display : '';
$text = '';
switch ( $rule ) {
case 'logged_in':
$text = __( 'Logged In', 'fl-builder' );
break;
case 'logged_out':
$text = __( 'Logged Out', 'fl-builder' );
break;
case 'logic':
$text = __( 'Logic', 'fl-builder' );
break;
default:
$text = __( 'Breakpoint', 'fl-builder' );
}
return array(
'text' => $text,
'type' => $rule,
);
}
/**
* Checks to see if a node is the root node of a global template.
*
* @since 1.6.3
* @param object $node The node object to check.
* @return bool|int
*/
static public function is_node_template_root( $node ) {
return self::is_node_global( $node ) && isset( $node->template_root_node );
}
/**
* Get an array of node template info.
*
* @since 1.6.3
* @param string $type The type of node template to get.
* @return array
*/
static public function get_node_templates( $type = '' ) {
$posts = get_posts( array(
'post_type' => 'fl-builder-template',
'orderby' => 'title',
'order' => 'ASC',
'posts_per_page' => '-1',
'tax_query' => array(
array(
'taxonomy' => 'fl-builder-template-type',
'field' => 'slug',
'terms' => $type,
),
),
) );
$templates = array();
foreach ( $posts as $post ) {
$templates[] = array(
'id' => get_post_meta( $post->ID, '_fl_builder_template_id', true ),
'global' => get_post_meta( $post->ID, '_fl_builder_template_global', true ),
'link' => add_query_arg( 'fl_builder', '', get_permalink( $post->ID ) ),
'name' => $post->post_title,
);
}
return $templates;
}
/**
* Returns the root node for a node template.
*
* @since 1.6.3
* @param string $type The type of node template.
* @param array $nodes The node template data.
* @return object
*/
static public function get_node_template_root( $type = '', $nodes = array() ) {
if ( '' != $type ) {
$nodes = count( $nodes ) > 0 ? $nodes : self::get_nodes( $type );
}
foreach ( $nodes as $node ) {
if ( $type == $node->type ) {
// Root parent for column template should be null.
if ( 'column' == $type && $node->parent ) {
continue;
}
return $node;
}
}
return false;
}
/**
* Uses a node template ID to retrieve its post ID.
*
* @since 1.6.3
* @param string $template_id The node template ID as stored in the template's post meta.
* @return int
*/
static public function get_node_template_post_id( $template_id ) {
if ( isset( self::$node_template_post_ids[ $template_id ] ) ) {
return self::$node_template_post_ids[ $template_id ];
} else {
$posts = get_posts( array(
'post_type' => 'fl-builder-template',
'post_status' => array( 'any', 'trash' ),
'posts_per_page' => '-1',
'post_status' => 'any',
'meta_key' => '_fl_builder_template_id',
'meta_value' => $template_id,
) );
if ( 0 === count( $posts ) ) {
return false;
}
$post_id = apply_filters( 'fl_builder_node_template_post_id', $posts[0]->ID );
self::$node_template_post_ids[ $template_id ] = $post_id;
return $post_id;
}
}
/**
* Returns the edit URL for a node template.
*
* @since 1.6.3
* @param string $template_id The node template ID as stored in the template's post meta.
* @return string
*/
static public function get_node_template_edit_url( $template_id ) {
return self::get_edit_url( self::get_node_template_post_id( $template_id ) );
}
/**
* Returns an array of posts that have the global node template
* with the specified ID.
*
* @since 1.6.3
* @param int $post_id The post ID of the global node template.
* @return array
*/
static public function get_posts_with_global_node_template( $post_id = false ) {
$posts = array();
if ( self::is_post_global_node_template( $post_id ) ) {
$template_id = get_post_meta( $post_id, '_fl_builder_template_id', true );
$query = new WP_Query( array(
'meta_query' => array(
'relation' => 'OR',
array(
'key' => '_fl_builder_data',
'value' => $template_id,
'compare' => 'LIKE',
),
array(
'key' => '_fl_builder_draft',
'value' => $template_id,
'compare' => 'LIKE',
),
),
'post_type' => 'any',
'post_status' => 'any',
'post__not_in' => array( $post_id ),
) );
$posts = $query->posts;
}
return $posts;
}
/**
* Saves a node template.
*
* @since 1.6.3
* @param string $template_node_id The ID of the node to save as a template.
* @param string $settings The settings for this template.
* @return void
*/
static public function save_node_template( $template_node_id, $settings ) {
$root_node = self::get_node( $template_node_id );
$nodes = self::get_nested_nodes( $template_node_id );
$template_id = self::generate_node_id();
$original_parent = $root_node->parent;
$original_position = $root_node->position;
// Save the node template post.
$post_id = wp_insert_post( array(
'post_title' => $settings['name'],
'post_type' => 'fl-builder-template',
'post_status' => 'publish',
'ping_status' => 'closed',
'comment_status' => 'closed',
) );
// Set the template type.
wp_set_post_terms( $post_id, $root_node->type, 'fl-builder-template-type' );
// Reset the root node's position.
$root_node->position = 0;
// Remove root parent for column template.
if ( 'column' == $root_node->type ) {
$root_node->parent = null;
$root_node->settings->size = 100;
}
// Add the root node to the nodes array.
$nodes[ $root_node->node ] = $root_node;
// Generate new node ids.
$nodes = self::generate_new_node_ids( $nodes );
// Get the root node from the template data since its ID changed.
$root_node = self::get_node_template_root( $root_node->type, $nodes );
// Add the template ID and template node ID for global templates.
if ( $settings['global'] ) {
foreach ( $nodes as $node_id => $node ) {
if ( false == $nodes[ $node_id ]->global ) {
$nodes[ $node_id ]->template_id = $template_id;
$nodes[ $node_id ]->template_node_id = $node_id;
if ( $node_id == $root_node->node ) {
$nodes[ $node_id ]->template_root_node = true;
} elseif ( isset( $nodes[ $node_id ]->template_root_node ) ) {
unset( $nodes[ $node_id ]->template_root_node );
}
}
}
} else {
foreach ( $nodes as $node_id => $node ) {
if ( isset( $nodes[ $node_id ]->template_id ) ) {
unset( $nodes[ $node_id ]->template_id );
}
if ( isset( $nodes[ $node_id ]->template_node_id ) ) {
unset( $nodes[ $node_id ]->template_node_id );
}
if ( isset( $nodes[ $node_id ]->template_root_node ) ) {
unset( $nodes[ $node_id ]->template_root_node );
}
}
}
// Save the template layout data.
self::update_layout_data( $nodes, 'published', $post_id );
self::update_layout_data( $nodes, 'draft', $post_id );
// Enable the builder for this template.
update_post_meta( $post_id, '_fl_builder_enabled', true );
// Add the template ID post meta. We use a custom ID for node
// templates in case templates are imported since their WordPress
// IDs will change, breaking global templates.
update_post_meta( $post_id, '_fl_builder_template_id', $template_id );
// Add the template global flag post meta.
update_post_meta( $post_id, '_fl_builder_template_global', $settings['global'] );
// Delete the existing node and apply the template for global templates.
if ( $settings['global'] ) {
// Delete the existing node.
self::delete_node( $template_node_id );
// Apply the global template.
$root_node = self::apply_node_template( $template_id, $original_parent, $original_position );
}
// Return an array of template settings.
return array(
'id' => $template_id,
'global' => $settings['global'] ? true : false,
'link' => add_query_arg( 'fl_builder', '', get_permalink( $post_id ) ),
'name' => $settings['name'],
'type' => $root_node->type,
'layout' => $settings['global'] ? FLBuilderAJAXLayout::render( $root_node->node, $template_node_id ) : null,
'config' => $settings['global'] ? FLBuilderUISettingsForms::get_node_js_config() : null,
'postID' => $post_id,
'template_id' => $template_id,
'template_node_id' => $root_node->node,
'template_root_node' => true,
'parent' => $original_parent,
'position' => $original_position,
'settings' => $root_node->settings,
);
}
/**
* Sets the default type for a node template when created in wp-admin.
*
* @since 1.6.3
* @param int $post_ID The post ID for the template.
* @param object $post The post object for the template.
* @param bool $update Whether this is a new post or an update.
* @return void
*/
static public function set_node_template_default_type( $post_id, $post, $update ) {
global $pagenow;
if ( 'admin.php' == $pagenow && isset( $_GET['import'] ) ) {
return;
}
$post_data = self::get_post_data();
if ( $update || 'fl-builder-template' != $post->post_type ) {
return;
}
if ( isset( $post_data['fl_action'] ) && 'duplicate_post' == $post_data['fl_action'] ) {
return;
}
if ( isset( $_GET['duplicate_layout'] ) ) {
return;
}
$type = wp_get_post_terms( $post_id, 'fl-builder-template-type' );
if ( 0 === count( $type ) ) {
wp_set_post_terms( $post_id, 'layout', 'fl-builder-template-type' );
}
}
/**
* Deletes a node template via AJAX.
*
* @since 1.6.3
* @param string $template_id The ID of node template to delete.
* @return void
*/
static public function delete_node_template( $template_id ) {
// Make sure we have a template ID.
if ( ! isset( $template_id ) ) {
return;
}
// Get the post ID for the template.
$template_post_id = self::get_node_template_post_id( $template_id );
// Bail if we don't have a post ID.
if ( ! $template_post_id ) {
return;
}
// Unlink if this is a global template.
self::unlink_global_node_template_from_all_posts( $template_post_id );
// Delete the template post.
wp_trash_post( $template_post_id, true );
}
/**
* Unlinks all instances of a global node template in all posts.
*
* @since 1.6.3
* @param int $template_post_id The post ID of the template to unlink.
* @return void
*/
static public function unlink_global_node_template_from_all_posts( $template_post_id ) {
if ( self::is_post_global_node_template( $template_post_id ) ) {
$posts = self::get_posts_with_global_node_template( $template_post_id );
$template_id = get_post_meta( $template_post_id, '_fl_builder_template_id', true );
foreach ( $posts as $post ) {
self::unlink_global_node_template_from_post( 'published', $post->ID, $template_post_id, $template_id );
self::unlink_global_node_template_from_post( 'draft', $post->ID, $template_post_id, $template_id );
self::delete_all_asset_cache( $post->ID );
}
}
}
/**
* Unlinks all instances of a global node template from a post's
* layout data with the specified status. Since only the root node
* of a global template is saved to a posts layout data, the child
* nodes will be saved to the post when the global template is unlinked.
*
* @since 1.6.3
* @param string $status The status of the layout data. Either draft or published.
* @param int $post_id The ID of the post to unlink from.
* @param string $template_post_id The post ID of the template to unlink from the layout data.
* @param string $template_id The ID of the template to unlink from the layout data.
* @return void
*/
static public function unlink_global_node_template_from_post( $status, $post_id, $template_post_id, $template_id ) {
$template_data = self::get_layout_data( $status, $template_post_id );
$layout_data = self::get_layout_data( $status, $post_id );
$update = false;
// Loop through the layout data.
foreach ( $layout_data as $node_id => $node ) {
// Check to see if this is the global template node to unlink.
if ( isset( $node->template_id ) && $node->template_id == $template_id ) {
// Generate new node ids for the template data.
$new_data = self::generate_new_node_ids( $template_data );
// Get the root node from the template data.
$root_node = self::get_node_template_root( $node->type, $new_data );
// Remove the root node from the template data since it's already in the layout.
unset( $new_data[ $root_node->node ] );
// Update the settings for the root node in this layout.
$layout_data[ $node_id ]->settings = $root_node->settings;
// Update children with the new parent node ID.
foreach ( $new_data as $i => $n ) {
if ( $n->parent == $root_node->node ) {
$new_data[ $i ]->parent = $node->node;
}
}
// Add the template data to the layout data.
$layout_data = array_merge( $layout_data, $new_data );
// Set the update flag.
$update = true;
}
}
// Only update if we need to.
if ( $update ) {
// Remove template info from the layout data.
foreach ( $layout_data as $node_id => $node ) {
if ( isset( $node->template_id ) && $node->template_id == $template_id ) {
unset( $layout_data[ $node_id ]->template_id );
unset( $layout_data[ $node_id ]->template_node_id );
unset( $layout_data[ $node_id ]->template_root_node );
}
}
// Update the layout data.
self::update_layout_data( $layout_data, $status, $post_id );
}
}
/**
* Deletes all instances of a global node template from all posts.
*
* @since 1.6.3
* @param int $template_post_id The post ID of the template to delete.
* @return void
*/
static public function delete_global_node_template_from_all_posts( $template_post_id ) {
if ( self::is_post_global_node_template( $template_post_id ) ) {
$posts = self::get_posts_with_global_node_template( $template_post_id );
$template_id = get_post_meta( $template_post_id, '_fl_builder_template_id', true );
foreach ( $posts as $post ) {
self::delete_global_node_template_from_post( 'published', $post->ID, $template_id );
self::delete_global_node_template_from_post( 'draft', $post->ID, $template_id );
self::delete_all_asset_cache( $post->ID );
}
}
}
/**
* Deletes all instances of a global node template from a post's
* layout data with the specified status.
*
* @since 1.6.3
* @param string $status The status of the layout data. Either draft or published.
* @param int $post_id The ID of the post to delete from.
* @param string $template_id The ID of the template to delete from the layout data.
* @return void
*/
static public function delete_global_node_template_from_post( $status, $post_id, $template_id ) {
$layout_data = self::get_layout_data( $status, $post_id );
$update = false;
// Loop through the nodes.
foreach ( $layout_data as $node_id => $node ) {
$siblings = array();
$position = 0;
// Check to see if this is the global template node to delete.
if ( isset( $node->template_id ) && $node->template_id == $template_id ) {
// Unset this node in the layout data.
unset( $layout_data[ $node_id ] );
// Find sibling nodes to update their position.
foreach ( $layout_data as $i => $n ) {
if ( $n->parent == $node->parent ) {
$siblings[ $i ] = $n;
}
}
// Sort the sibling nodes by position.
uasort( $siblings, array( 'FLBuilderModel', 'order_nodes' ) );
// Update sibling node positions.
foreach ( $siblings as $i => $n ) {
$layout_data[ $i ]->position = $position;
$position++;
}
// Set the update flag.
$update = true;
}
}
// Only update if we need to.
if ( $update ) {
self::update_layout_data( $layout_data, $status, $post_id );
}
}
/**
* Applies a node template to the current layout.
*
* @since 1.6.3
* @param int $template_id The node template ID.
* @param string $parent_id The new parent node ID for the template.
* @param int $position The position of the template within the layout.
* @param object $template Optional. Template data to use instead of pulling it with the template ID.
* @return void
*/
static public function apply_node_template( $template_id = null, $parent_id = null, $position = 0, $template = null ) {
$parent = ( 0 === $parent_id ) ? null : self::get_node( $parent_id );
$template_post_id = self::get_node_template_post_id( $template_id );
$is_col_template = false;
/**
* Allow extensions to hook into applying a node template.
* @see fl_builder_override_apply_node_template
*/
$override = apply_filters( 'fl_builder_override_apply_node_template', false, array(
'template_id' => $template_id,
'parent_id' => $parent_id,
'position' => $position,
'template' => $template,
'template_post_id' => $template_post_id,
) );
// Return if we got an override from the filter.
if ( $override ) {
return $override;
}
// Get the template data from $template if we have it.
if ( is_object( $template ) ) {
$template_data = $template->nodes;
$template_settings = $template->settings;
$type = $template->type;
$global = $template->global;
} else {
$template_data = self::get_layout_data( 'published', $template_post_id );
$template_settings = self::get_layout_settings( 'published', $template_post_id );
$type = self::get_user_template_type( $template_post_id );
$global = get_post_meta( $template_post_id, '_fl_builder_template_global', true );
}
// Filter the nodes for backwards compatibility with old settings.
$template_data = FLBuilderSettingsCompat::filter_layout_data( $template_data );
// Generate new node ids.
$template_data = self::generate_new_node_ids( $template_data );
// Get the root node from the template data.
$root_node = self::get_node_template_root( $type, $template_data );
// Handle module and column templates.
if ( 'module' == $root_node->type || 'column' == $root_node->type ) {
// Add a new parent for module or column node templates if needed.
if ( ! $parent || 'row' == $parent->type || 'column-group' == $parent->type ) {
if ( 'module' == $root_node->type ) {
$parent_id = self::add_module_parent( $parent_id, $position );
$position = null;
} elseif ( 'column' == $root_node->type ) {
$parent_id = self::add_col_parent( $parent_id, $position );
$is_col_template = self::is_node_global( $root_node );
}
$parent = self::get_node( $parent_id );
}
// Set the node's template data if the parent is a global node.
if ( self::is_node_global( $parent ) && ! $is_col_template ) {
$template_data[ $root_node->node ]->template_id = $parent->template_id;
$template_data[ $root_node->node ]->template_node_id = $root_node->node;
unset( $template_data[ $root_node->node ]->template_root_node );
$global = true;
}
}
// Update the root node's parent.
$template_data[ $root_node->node ]->parent = ! $parent_id ? null : $parent_id;
// Get the layout data and settings.
$layout_data = self::get_layout_data( 'draft' );
$layout_settings = self::get_layout_settings( 'draft' );
// Only merge the root node for global templates.
if ( $global ) {
$layout_data[ $root_node->node ] = $template_data[ $root_node->node ];
} else {
// Merge template data.
foreach ( $template_data as $node_id => $node ) {
unset( $template_data[ $node_id ]->template_id );
unset( $template_data[ $node_id ]->template_node_id );
unset( $template_data[ $node_id ]->template_root_node );
}
$layout_data = array_merge( $layout_data, $template_data );
// Merge template settings.
$layout_settings = self::merge_layout_settings( $layout_settings, $template_settings );
}
// Update the layout data and settings.
self::update_layout_data( $layout_data );
self::update_layout_settings( $layout_settings );
// Reorder the main template node.
if ( null !== $position ) {
self::reorder_node( $root_node->node, $position );
}
// Re-size column widths
if ( 'column' == $root_node->type && 'column-group' == $parent->type ) {
self::reset_col_widths( $parent_id );
}
// Delete old asset cache.
self::delete_asset_cache();
// Return the root node.
if ( 'module' == $root_node->type ) {
return self::get_module( $root_node->node );
} else {
return $root_node;
}
}
/**
* Registers a template data file with the builder.
*
* @since 1.8
* @param string|array $path The directory path to the template data file.
* @param array $meta The collection information for this template file.
* @return void
*/
static public function register_templates( $path = false, $args = array() ) {
// Check if the file exists if path is a string.
if ( is_string( $path ) && ! file_exists( $path ) ) {
return;
}
// Make sure one file exists if path is an array.
if ( is_array( $path ) ) {
$exists = false;
foreach ( $path as $file ) {
if ( file_exists( $file ) ) {
$exists = true;
}
}
if ( ! $exists ) {
return;
}
}
// Store the template data.
self::$templates[] = array(
'group' => isset( $args['group'] ) ? $args['group'] : false,
'path' => is_string( $path ) ? array( $path ) : $path,
);
}
/**
* Registers the core templates with the builder.
*
* @since 1.10.3
* @return void
*/
static private function register_core_templates() {
$templates = glob( FL_BUILDER_DIR . 'data/*' );
$paths = array();
// glob() will return false on error so cast as an array() just in case.
foreach ( (array) $templates as $template ) {
$basename = basename( $template );
if ( 'templates.dat' === $basename ) {
continue;
} elseif ( true !== FL_BUILDER_LITE && 'templates-config.dat' === $basename ) {
continue;
}
$paths[] = $template;
}
self::register_templates( $paths );
}
/**
* Applies a core template and can be overridden by extensions to
* apply something else that is being shown in the selector.
*
* @since 1.0
* @since 1.5.7. Added logic for overriding core templates.
* @param int $index The index of the template to apply.
* @param bool $append Whether to append the new template or replacing the existing layout.
* @param string $type The type of template to apply.
* @return void
*/
static public function apply_template( $index = 0, $append = false, $type = 'layout' ) {
/**
* Allow extensions to hook into applying a template.
* @see fl_builder_override_apply_template
*/
$override = apply_filters( 'fl_builder_override_apply_template', false, array(
'index' => $index,
'append' => $append,
'type' => $type,
) );
// Return if we have an override from the filter.
if ( $override ) {
return $override;
}
// Apply a core template.
return self::apply_core_template( $index, $append, $type );
}
/**
* Applies a core template and cannot be overridden by extensions.
*
* @since 1.10
* @param int $index The index of the template to apply.
* @param bool $append Whether to append the new template or replacing the existing layout.
* @param string $type The type of template to apply.
* @return array
*/
static public function apply_core_template( $index = 0, $append = false, $type = 'layout' ) {
$template = self::get_template( $index, $type );
$row_position = self::next_node_position( 'row' );
$data = array();
// Delete existing nodes and settings?
if ( ! $append ) {
self::delete_layout_data( 'draft' );
self::delete_layout_settings( 'draft' );
}
// Only move forward if we have template nodes.
if ( isset( $template->nodes ) ) {
// Get new ids for the template nodes.
$template->nodes = self::generate_new_node_ids( $template->nodes );
// Filter the nodes for backwards compatibility with old settings.
$template->nodes = FLBuilderSettingsCompat::filter_layout_data( $template->nodes );
// Get the existing layout data and settings.
$layout_data = self::get_layout_data();
$layout_settings = self::get_layout_settings();
// Reposition rows?
if ( $append ) {
foreach ( $template->nodes as $node_id => $node ) {
if ( 'row' == $node->type ) {
$template->nodes[ $node_id ]->position += $row_position;
}
}
}
// Merge and update the layout data.
$data = array_merge( $layout_data, $template->nodes );
self::update_layout_data( $data );
// Merge and update the layout settings.
if ( isset( $template->settings ) ) {
$settings = self::merge_layout_settings( $layout_settings, $template->settings );
self::update_layout_settings( $settings );
}
}
// Delete old asset cache.
self::delete_asset_cache();
// Return the layout.
return array(
'layout' => FLBuilderAJAXLayout::render(),
'config' => FLBuilderUISettingsForms::get_node_js_config(),
'newNodes' => $data,
);
}
/**
* Returns data for a core template.
*
* @since 1.0
* @param int $index The index of the template.
* @param string $type The type of template to get. Currently either layout, row or module.
* @return object
*/
static public function get_template( $index, $type = 'layout' ) {
$templates = self::get_templates( $type );
$template = isset( $templates[ $index ] ) ? $templates[ $index ] : false;
if ( $template && isset( $template->nodes ) ) {
$template->nodes = maybe_unserialize( $template->nodes );
}
return $template;
}
/**
* Returns data for all core or third party templates.
*
* @since 1.0
* @param string $type Either layout, row or module
* @param bool $cached
* @return array
*/
static public function get_templates( $type = 'layout', $cached = true ) {
// Pull from dat files if cached is false or we don't have saved data.
if ( ! $cached || ! self::$template_data ) {
self::$template_data = array();
foreach ( self::$templates as $args ) {
foreach ( $args['path'] as $path ) {
// Make sure the template file exists.
if ( ! file_exists( $path ) ) {
continue;
}
// Get the unserialized template data.
if ( stristr( $path, '.php' ) ) {
ob_start();
include $path;
$unserialized = unserialize( ob_get_clean() );
} else {
$unserialized = fl_maybe_fix_unserialize( file_get_contents( $path ) );
}
// Make sure we have an unserialized array.
if ( ! is_array( $unserialized ) ) {
continue;
}
// Group and cache the template data.
foreach ( $unserialized as $template_type => $template_data ) {
if ( ! isset( self::$template_data[ $template_type ] ) ) {
self::$template_data[ $template_type ] = array();
}
foreach ( $template_data as $key => $template ) {
// Add the main group to each template.
if ( ! isset( $template_data[ $key ]->group ) ) {
$template_data[ $key ]->group = $args['group'];
}
// Reserialize the node data as it's expensive to store in memory.
if ( isset( $template->nodes ) ) {
$template_data[ $key ]->nodes = serialize( $template_data[ $key ]->nodes );
}
}
self::$template_data[ $template_type ] = array_merge( self::$template_data[ $template_type ], $template_data );
}
}
}
}
$templates = isset( self::$template_data[ $type ] ) ? self::$template_data[ $type ] : array();
ksort( $templates );
/**
* @see fl_builder_get_templates
*/
return apply_filters( 'fl_builder_get_templates', $templates, $type );
}
/**
* Checks to see if any templates exist.
*
* @since 1.8
* @return bool
*/
static public function has_templates() {
return apply_filters( 'fl_builder_has_templates', ( count( self::get_templates() ) > 0 ) );
}
/**
* Returns template data needed for the template selector.
* Can also return data for row, column and module templates if
* a template type is passed.
*
* @since 1.5.7
* @param string $type Either layout, row or module
* @return array
*/
static public function get_template_selector_data( $type = 'layout' ) {
$type = apply_filters( 'fl_builder_template_selector_data_type', $type );
$categorized = array();
$templates = array();
$groups = array();
// This is needed for backwards compat with the old core templates category.
$core_categories = array(
'general' => 'General',
'landing' => 'Landing Pages',
'company' => 'Content Pages',
);
// Build the the templates array.
foreach ( self::get_templates( $type ) as $key => $template ) {
if ( 'module' == $type && isset( $template->nodes ) ) {
$nodes = maybe_unserialize( $template->nodes );
$node = array_shift( $nodes );
if ( ! isset( $node->settings ) || ! isset( self::$modules[ $node->settings->type ] ) ) {
continue;
}
}
if ( strstr( $template->image, '://' ) || strstr( $template->image, ';base64,' ) ) {
$image = $template->image;
} else {
$image = FLBuilder::plugin_url() . 'img/templates/' . ( empty( $template->image ) ? 'blank.jpg' : $template->image );
}
$templates[] = apply_filters( 'fl_builder_template_details', array(
'id' => $key,
'name' => $template->name,
'image' => $image,
'author' => '',
'category' => isset( $template->category ) ? $template->category : $template->categories,
'tags' => array(),
'group' => $template->group,
'type' => 'core',
'subtype' => $type,
'kind' => 'template',
'content' => ! in_array( $type, array( 'row', 'column', 'module' ) ) ? 'layout' : $type,
'premium' => isset( $template->premium ) ? ! ! $template->premium : false,
), $template );
}
// Build the categorized templates array and groups array.
foreach ( $templates as $i => $template ) {
// Make sure we have a template category and it's an array.
if ( ! isset( $template['category'] ) ) {
$template['category'] = array(
'uncategorized' => __( 'Uncategorized', 'fl-builder' ),
);
} elseif ( is_string( $template['category'] ) ) {
$template['category'] = array(
$template['category'] => $core_categories[ $template['category'] ],
);
}
// Get template group data.
$template_groups = array();
if ( ! $template['group'] ) {
// If we don't have a group, use categories as groups.
foreach ( $template['category'] as $cat_name ) {
$template_groups[] = __( $cat_name, 'fl-builder' ); // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
}
// Clear the categories since we're using groups instead.
$template['category'] = array(
'none' => '',
);
} elseif ( is_string( $template['group'] ) ) {
// Make sure template group is an array.
$template_groups = array( $template['group'] );
} else {
$template_groups = $template['group'];
}
// Add to the groups array.
$template['group'] = array();
foreach ( $template_groups as $group_name ) {
$group_key = sanitize_key( $group_name );
if ( ! isset( $groups[ $group_key ] ) ) {
$groups[ $group_key ] = array(
'name' => $group_name,
'categories' => array(),
);
}
foreach ( $template['category'] as $cat_key => $cat_name ) {
if ( ! isset( $groups[ $group_key ]['categories'][ $cat_key ] ) ) {
$groups[ $group_key ]['categories'][ $cat_key ] = array(
'name' => $cat_name,
);
}
}
ksort( $groups[ $group_key ]['categories'] );
$template['group'][] = $group_key;
}
// Add to the categorized array.
foreach ( $template['category'] as $cat_key => $cat_name ) {
// Add the category if we don't have it yet.
if ( ! isset( $categorized[ $cat_key ] ) ) {
$categorized[ $cat_key ] = array(
'name' => $cat_name,
'templates' => array(),
);
}
$categorized[ $cat_key ]['templates'][] = $template;
}
$templates[ $i ] = $template;
}
/**
* Return both the templates and categorized templates array.
* @see fl_builder_template_selector_data
*/
return apply_filters( 'fl_builder_template_selector_data', array(
'templates' => $templates,
'categorized' => $categorized,
'groups' => $groups,
), $type );
}
/**
* Returns data for row templates to be shown in the UI panel.
*
* @since 1.8
* @return array
*/
static public function get_row_templates_data() {
return apply_filters( 'fl_builder_row_templates_data', self::get_template_selector_data( 'row' ) );
}
/**
* Returns data for column templates to be shown in the UI panel.
*
* @since 2.1
* @return array
*/
static public function get_column_templates_data() {
return apply_filters( 'fl_builder_column_templates_data', self::get_template_selector_data( 'column' ) );
}
/**
* Returns data for module templates to be shown in the UI panel.
*
* @since 1.8
* @return array
*/
static public function get_module_templates_data() {
return apply_filters( 'fl_builder_module_templates_data', self::get_template_selector_data( 'module' ) );
}
/**
* Returns the config for pro modules if it exists.
*
* @since 2.4
* @return object
*/
static public function get_pro_modules_config() {
$path = FL_BUILDER_DIR . 'json/modules-config.json';
$config = new stdClass;
if ( file_exists( $path ) ) {
$config = json_decode( file_get_contents( $path ) );
}
return $config;
}
/**
* Get color presets.
*
* @since 1.6.4
* @return object
*/
static public function get_color_presets() {
$settings = get_option( '_fl_builder_color_presets', array() );
return apply_filters( 'fl_builder_color_presets', $settings );
}
/**
* Save color presets.
*
* @since 1.6.4
* @param array $presets The new color presets collection.
* @return object
*/
static public function save_color_presets( $presets = array() ) {
return FLBuilderUtils::update_option( '_fl_builder_color_presets', $presets );
}
/**
* Returns whether the UI has been white labeled or not.
*
* @since 2.1
* @return bool
*/
static public function is_white_labeled() {
if ( class_exists( 'FLBuilderWhiteLabel' ) ) {
return FLBuilderWhiteLabel::is_white_labeled();
}
return false;
}
/**
* Returns whether the inline editing is enabled.
*
* @since 2.1
* @return bool
*/
static public function is_inline_enabled() {
return apply_filters( 'fl_inline_editing_enabled', true );
}
/**
* Returns whether the Ace Editor error checking is enabled.
*
* @since 2.1
* @return bool
*/
static public function is_codechecking_enabled() {
/**
* Is code checking enabled?
* @see fl_code_checking_enabled
*/
$enabled = apply_filters( 'fl_code_checking_enabled', true );
/**
* Enable shortcodes in css/js
* @see fl_enable_shortcode_css_js
* @since 2.3
*/
if ( true === apply_filters( 'fl_enable_shortcode_css_js', false ) ) {
$enabled = false;
}
return $enabled;
}
/**
* Returns Ace Editor defaults as an array.
*
* @since 2.1
* @return array
*/
static public function ace_editor_settings() {
$defaults = array(
'enableBasicAutocompletion' => true,
'enableLiveAutocompletion' => true,
'enableSnippets' => false,
'showLineNumbers' => true,
'wrap' => true,
'showFoldWidgets' => false,
);
/**
* Default Ace editor settings
* @see fl_ace_editor_settings
* @since 2.1
*/
return apply_filters( 'fl_ace_editor_settings', $defaults );
}
/**
* Returns the custom branding string.
*
* @since 1.3.1
* @return string
*/
static public function get_branding() {
if ( class_exists( 'FLBuilderWhiteLabel' ) ) {
return FLBuilderWhiteLabel::get_branding();
}
return __( 'Beaver Builder', 'fl-builder' );
}
/**
* Returns the custom branding icon URL.
*
* @since 1.3.7
* @return string
*/
static public function get_branding_icon() {
if ( class_exists( 'FLBuilderWhiteLabel' ) ) {
return FLBuilderWhiteLabel::get_branding_icon();
}
return FLBuilder::plugin_url() . 'img/beaver.png';
}
/**
* Returns an array of slugs for all enabled icon sets.
*
* @since 1.4.6
* @return array
*/
static public function get_enabled_icons() {
$value = self::get_admin_settings_option( '_fl_builder_enabled_icons', true );
/**
* font-awesome should not be a key in this array, if it is it can cause issues.
*/
if ( is_array( $value ) ) {
$key = array_search( 'font-awesome', $value, true );
if ( false !== $key ) {
unset( $value[ $key ] );
}
}
return ! $value ? array( 'font-awesome-5-regular', 'font-awesome-5-solid', 'font-awesome-5-brands', 'foundation-icons', 'dashicons' ) : $value;
}
/**
* Check if the current user has the specific capabilities
*
* @param string $cap The capability to evaluate if it's single or multiple (comma separated) value
* @return bool
*/
static public function current_user_has_capability( $cap ) {
if ( strstr( $cap, ',' ) ) {
$parts = explode( ',', $cap );
foreach ( $parts as $part ) {
if ( current_user_can( trim( $part ) ) ) {
return true;
}
}
return false;
} else {
return current_user_can( $cap );
}
}
/**
* Returns the default settings for the builder's help button.
*
* @since 1.4.9
* @return array
*/
static public function get_help_button_defaults() {
$defaults = array(
'enabled' => true,
'tour' => true,
'video' => true,
'video_embed' => '',
'knowledge_base' => true,
'knowledge_base_url' => self::get_store_url( 'knowledge-base', array(
'utm_medium' => ( true === FL_BUILDER_LITE ? 'bb-lite' : 'bb-pro' ),
'utm_source' => 'builder-ui',
'utm_campaign' => 'kb-help-button',
) ),
'forums' => true,
'forums_url' => self::get_store_url( 'knowledge-base', array(
'utm_medium' => ( true === FL_BUILDER_LITE ? 'bb-lite' : 'bb-pro' ),
'utm_source' => 'builder-ui',
'utm_campaign' => 'forums-help-button',
) ),
);
return $defaults;
}
/**
* Returns the settings for the builder's help button.
*
* @since 1.4.9
* @return array
*/
static public function get_help_button_settings() {
if ( class_exists( 'FLBuilderWhiteLabel' ) ) {
return FLBuilderWhiteLabel::get_help_button_settings();
}
return self::get_help_button_defaults();
}
/**
* Get row resize settings
*
* @since 2.0
* @return array
*/
static public function get_row_resize_settings() {
$defaults = array(
'userCanResizeRows' => true,
'minAllowedWidth' => 300,
'maxAllowedWidth' => false,
);
$settings = apply_filters( 'fl_row_resize_settings', $defaults );
// Ensure everything is still defined after filter
$settings = wp_parse_args( $settings, $defaults );
// Min width can't go lower than 100px
if ( false == $settings['minAllowedWidth'] || $settings['minAllowedWidth'] < 100 ) {
$settings['minAllowedWidth'] = 100;
}
// Convert string numbers to int
if ( is_string( $settings['minAllowedWidth'] ) ) {
$settings['minAllowedWidth'] = intval( $settings['minAllowedWidth'] );
}
if ( is_string( $settings['maxAllowedWidth'] ) ) {
$settings['maxAllowedWidth'] = intval( $settings['maxAllowedWidth'] );
}
// Check user capability
if ( ! FLBuilderUserAccess::current_user_can( 'unrestricted_editing' ) ) {
$settings['userCanResizeRows'] = false;
}
return $settings;
}
/**
* Filter the row settings to remove max width field
*
* @since 2.0
* @return array
*/
static public function filter_row_settings_for_resize( $form, $id ) {
if ( 'row' == $id && ! FLBuilderModel::user_can_resize_rows() ) {
unset( $form['tabs']['style']['sections']['general']['fields']['max_content_width'] );
}
return $form;
}
/**
* Check if user has the ability to resize rows
*
* @since 2.0
* @return bool
*/
static public function user_can_resize_rows() {
$args = self::get_row_resize_settings();
return $args['userCanResizeRows'];
}
/**
* Returns an array of account data for all integrated services.
*
* @since 1.5.4
* @return array
*/
static public function get_services() {
return get_option( '_fl_builder_services', array() );
}
/**
* Updates the account data for an integrated service.
*
* @since 1.5.4
* @param string $service The service id.
* @param string $account The account name.
* @param array $data The account data.
* @return void
*/
static public function update_services( $service, $account, $data ) {
$services = self::get_services();
$account = sanitize_text_field( $account );
if ( ! isset( $services[ $service ] ) ) {
$services[ $service ] = array();
}
$services[ $service ][ $account ] = $data;
FLBuilderUtils::update_option( '_fl_builder_services', $services );
}
/**
* Deletes an account for an integrated service.
*
* @since 1.5.4
* @param string $service The service id.
* @param string $account The account name.
* @return void
*/
static public function delete_service_account( $service, $account ) {
$services = self::get_services();
if ( isset( $services[ $service ][ $account ] ) ) {
unset( $services[ $service ][ $account ] );
}
if ( 0 === count( $services[ $service ] ) ) {
unset( $services[ $service ] );
}
FLBuilderUtils::update_option( '_fl_builder_services', $services );
}
/**
* Returns an option from the database for
* the admin settings page.
*
* @since 1.5.7
* @param string $key The option key.
* @param bool $network_override Whether to allow the network admin setting to be overridden on subsites.
* @return mixed
*/
static public function get_admin_settings_option( $key, $network_override = true ) {
if ( is_network_admin() ) {
// Get the site-wide option if we're in the network admin.
$value = get_site_option( $key );
} elseif ( ! $network_override && class_exists( 'FLBuilderMultisiteSettings' ) ) {
// Get the site-wide option if there's no network override.
$value = get_site_option( $key );
} elseif ( class_exists( 'FLBuilderMultisiteSettings' ) ) {
// Network overrides are allowed. Return the subsite option if it exists.
$value = get_option( $key );
$value = false === $value ? get_site_option( $key ) : $value;
} else {
// This must be a single site install. Get the single site option.
$value = get_option( $key );
}
return $value;
}
/**
* Updates an option from the admin settings page.
*
* @since 1.5.7
* @param string $key The option key.
* @param mixed $value The value to update.
* @param bool $network_override Whether to allow the network admin setting to be overridden on subsites.
* @return mixed
*/
static public function update_admin_settings_option( $key, $value, $network_override = true ) {
if ( is_network_admin() ) {
// Update the site-wide option since we're in the network admin.
update_site_option( $key, $value );
} elseif ( $network_override && FLBuilderAdminSettings::multisite_support() && ! isset( $_POST['fl-override-ms'] ) ) {
// Delete the option if we don't have a network override.
delete_option( $key );
} else {
// Update the option for single install or subsite.
FLBuilderUtils::update_option( $key, $value );
}
}
/**
* Returns the plugin basename for Beaver Builder.
*
* @since 1.0
* @return string
*/
static public function plugin_basename() {
return plugin_basename( FL_BUILDER_DIR . 'fl-builder.php' );
}
/**
* Deletes almost all database data and asset cache for the builder.
* We don't delete _fl_builder_enabled, _fl_builder_data and _fl_builder_draft
* so layouts can be recovered should the plugin be installed again.
*
* @since 1.0
* @return void
*/
static public function uninstall_database() {
if ( current_user_can( 'delete_plugins' ) ) {
// Delete builder options.
delete_option( '_fl_builder_settings' );
delete_option( '_fl_builder_enabled_modules' );
delete_option( '_fl_builder_enabled_templates' );
delete_option( '_fl_builder_templates_override' );
delete_option( '_fl_builder_templates_override_rows' );
delete_option( '_fl_builder_templates_override_columns' );
delete_option( '_fl_builder_templates_override_modules' );
delete_option( '_fl_builder_post_types' );
delete_option( '_fl_builder_enabled_icons' );
delete_option( '_fl_builder_branding' );
delete_option( '_fl_builder_branding_icon' );
delete_option( '_fl_builder_theme_branding' );
delete_option( '_fl_builder_user_access' );
delete_option( '_fl_builder_help_button' );
delete_option( '_fl_builder_color_presets' );
// Delete builder user meta.
delete_metadata( 'user', 0, '_fl_builder_launched', 1, true );
// Delete uploaded files and folders.
$upload_dir = self::get_upload_dir();
fl_builder_filesystem()->rmdir( $upload_dir['path'], true );
// Deactivate and delete the plugin.
if ( ! function_exists( 'deactivate_plugins' ) ) {
require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
}
deactivate_plugins( array( self::plugin_basename() ), false, is_network_admin() );
delete_plugins( array( self::plugin_basename() ) );
// Redirect to the plugins page.
wp_redirect( admin_url( 'plugins.php?deleted=true&plugin_status=all&paged=1&s=' ) );
exit;
}
}
/**
* Be sure to return a unique string
* @since 2.5
*/
static public function uniqid( $prefix = '', $length = 12 ) {
$id = substr( str_shuffle( '0123456789abcdefghijklmnopqrstuvwxyz' ), 0, $length );
if ( preg_match( '/^[0-9]+$/', $id ) ) {
$id = self::uniqid( $prefix, $length );
}
return ( $prefix ) ? $prefix . '-' . $id : $id;
}
/**
* @since 2.6
*/
static public function user_has_unfiltered_html() {
return apply_filters( 'fl_user_has_unfiltered_html', current_user_can( 'unfiltered_html' ) );
}
/**
* @since 1.6.4.3
* @deprecated 1.8
*/
static public function get_theme_branding() {
_deprecated_function( __METHOD__, '1.8', 'FLBuilderWhiteLabel::get_theme_branding()' );
if ( class_exists( 'FLBuilderWhiteLabel' ) ) {
return FLBuilderWhiteLabel::get_theme_branding();
}
}
/**
* @since 1.0
* @deprecated 1.8
*/
static public function save_templates( $templates = array() ) {
_deprecated_function( __METHOD__, '1.8', 'FLBuilderCoreTemplatesAdmin::save_templates()' );
if ( class_exists( 'FLBuilderCoreTemplatesAdmin' ) ) {
FLBuilderCoreTemplatesAdmin::save_templates( $templates );
}
}
/**
* @since 1.0
* @deprecated 1.8
*/
static public function save_template( $settings ) {
_deprecated_function( __METHOD__, '1.8', 'FLBuilderCoreTemplatesAdmin::save_template()' );
if ( class_exists( 'FLBuilderCoreTemplatesAdmin' ) ) {
FLBuilderCoreTemplatesAdmin::save_template( $settings );
}
}
/**
* @since 1.0
* @deprecated 1.8
*/
static public function update_template( $old_index, $settings ) {
_deprecated_function( __METHOD__, '1.8', 'FLBuilderCoreTemplatesAdmin::update_template()' );
if ( class_exists( 'FLBuilderCoreTemplatesAdmin' ) ) {
FLBuilderCoreTemplatesAdmin::update_template( $old_index, $settings );
}
}
/**
* @since 1.0
* @deprecated 1.8
*/
static public function delete_template( $index ) {
_deprecated_function( __METHOD__, '1.8', 'FLBuilderCoreTemplatesAdmin::delete_template()' );
if ( class_exists( 'FLBuilderCoreTemplatesAdmin' ) ) {
FLBuilderCoreTemplatesAdmin::delete_template( $index );
}
}
/**
* @since 1.3.9
* @deprecated 1.10
*/
static public function get_editing_capability() {
_deprecated_function( __METHOD__, '1.10' );
return 'edit_posts';
}
/**
* @since 1.7
* @deprecated 1.10
*/
static public function current_user_has_editing_capability() {
_deprecated_function( __METHOD__, '1.10', 'FLBuilderUserAccess::current_user_can()' );
return FLBuilderUserAccess::current_user_can( 'unrestricted_editing' );
}
/**
* @since 1.6.3
* @deprecated 1.10
*/
static public function get_global_templates_editing_capability() {
_deprecated_function( __METHOD__, '1.10', 'FLBuilderUserAccess::current_user_can' );
return 'edit_posts';
}
/**
* @since 1.5.7
* @deprecated 1.10
*/
static public function user_templates_admin_enabled() {
_deprecated_function( __METHOD__, '1.10', 'FLBuilderUserAccess::current_user_can( "builder_admin" )' );
return FLBuilderUserAccess::current_user_can( 'builder_admin' );
}
/**
* @since 1.0
* @deprecated 2.0
*/
static public function get_module_category_slug( $name ) {
_deprecated_function( __METHOD__, '2.0' );
return sanitize_html_class( $name );
}
/**
* @since 1.8
* @deprecated 2.0
*/
static public function get_template_selector_filter_data() {
_deprecated_function( __METHOD__, '2.0' );
return array();
}
}
FLBuilderModel::init();