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(); /** * Cached global settings. * * @access private * @var array $global_settings */ private static $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 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 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(); /** * 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'); add_action('save_post', __CLASS__ . '::set_node_template_default_type', 10, 3); add_action('wp_restore_post_revision', __CLASS__ . '::restore_revision', 10, 2); /* Filters */ add_filter('heartbeat_received', __CLASS__ . '::lock_post', 10, 2); /* Core Templates */ self::register_templates( FL_BUILDER_DIR . 'data/templates.dat' ); } /** * Returns a builder edit URL for a post. * * @since 1.0 * @param int $post_id The post id to get an edit url for. * @return string */ static public function get_edit_url( $post_id = false ) { if ( false === $post_id ) { global $post; } else { $post = get_post( $post_id ); } return set_url_scheme( add_query_arg( 'fl_builder', '', get_permalink( $post->ID ) ) ); } /** * 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() ) { 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 = trailingslashit( FL_BUILDER_STORE_URL . $path ) . '?' . http_build_query( $params, '', '&' ); return apply_filters( 'fl_builder_store_url', $url, $path ); } /** * 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'] ); } $data = FLBuilderUtils::json_decode_deep( wp_unslash( $_POST['fl_builder_data'] ) ); foreach($data as $key => $val) { self::$post_data[$key] = $val; } } else if(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'; } 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() { return apply_filters('fl_builder_global_posts', array()); } /** * Returns the post id for the current post that * is being displayed or worked on. * * @since 1.0 * @since 1.5.9 Trying to use the global $wp_the_query instead of $post to get the post id. * @return int|bool The post id or false. */ static public function get_post_id() { global $wp_the_query; global $post; $post_data = self::get_post_data(); // Get a post ID sent in an AJAX request. if ( isset( $post_data['post_id'] ) ) { return $post_data['post_id']; } // Get a post ID from the main query. else if ( in_the_loop() && is_main_query() && isset( $wp_the_query->post ) ) { return $wp_the_query->post->ID; } // Get a post ID in a query outside of the main loop. else if ( isset( $post ) ) { return $post->ID; } // No post ID found. else { 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; } else if ( 0 === stripos( get_option( 'siteurl' ), 'https://' ) ) { return true; } else if ( 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; 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 ); if ( in_array( $post->post_type, $post_types ) && $user_can ) { return true; } } return false; } /** * 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']); } } /** * Checks to see if the builder layout is enabled * for the current post. * * @since 1.0 * @return bool */ static public function is_builder_enabled() { if(!is_admin() && post_password_required()) { return false; } else if(self::is_builder_active()) { return true; } else { $post_types = self::get_post_types(); $post = get_post(self::get_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() { if ( self::is_post_editable() && ! is_admin() && ! post_password_required() ) { $post_data = self::get_post_data(); if ( isset( $_GET['fl_builder'] ) ) { return true; } else if ( isset( $post_data['fl_builder'] ) ) { return true; } } return false; } /** * 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() ) { $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() { return self::is_builder_active() ? 'draft' : 'published'; } /** * 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() { update_post_meta(self::get_post_id(), '_fl_builder_enabled', false); } /** * 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() ) { $post = $wp_the_query->post; $published = self::get_layout_data( 'published' ); $draft = self::get_layout_data( 'draft' ); // Migrate existing post content to the builder? if ( empty( $published ) && empty( $draft ) && ! empty( $post->post_content ) ) { $row = self::add_row(); $cols = self::get_nodes( 'column' ); $col = array_shift( $cols ); $settings = self::get_module_defaults( 'rich-text' ); $settings->text = wpautop( $post->post_content ); self::add_module( 'rich-text', $settings, $col->node ); } // Create a new draft? else if ( 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 ); } } /** * 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(); $dir_name = basename( FL_BUILDER_DIR ); // We use bb-plugin for the lite version as well. if ( $dir_name == 'beaver-builder-lite-version' ) { $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 ( ! file_exists( $dir_info['path'] ) ) { // Create the directory. mkdir( $dir_info['path'] ); // Add an index file for security. file_put_contents( $dir_info['path'] . 'index.html', '' ); } 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( ! file_exists( $dir_info['path'] ) ) { // Create the directory. mkdir( $dir_info['path'] ); // Add an index file for security. file_put_contents( $dir_info['path'] . 'index.html', '' ); } 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() { $post_id = self::get_post_id(); $active = self::is_builder_active(); if($active) { return md5(uniqid()); } else { return 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(); if(isset($post_data['node_preview'])) { $suffix = '-layout-preview'; } else if(self::is_builder_active()) { $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; } /** * 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 ) { $info = self::get_asset_info(); $types = $type ? array( $type ) : array( 'css', 'css_partial', 'js', 'js_partial' ); foreach ( $types as $type ) { if ( isset( $info[ $type ] ) && file_exists( $info[ $type ] ) ) { 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 ) { $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 ( file_exists( $path ) ) { 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() { $cache_dir = self::get_cache_dir(); $css = glob( $cache_dir['path'] . '*.css' ); $js = glob( $cache_dir['path'] . '*.js' ); if ( is_array( $css ) ) { array_map( 'unlink', $css ); } if ( is_array( $js ) ) { array_map( '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 = 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; } // Return nodes of a certain type. 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 ); $status = $template_post_id && ! self::is_post_node_template() ? 'published' : $status; $data = self::get_layout_data( $status, $template_post_id ); $nodes = array(); foreach ( $data as $node_id => $node ) { if ( $node->parent == $parent->node || ( $template_post_id && $parent->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() { $nodes = array( 'rows' => array(), 'groups' => array(), 'columns' => array(), 'modules' => array(), ); if ( self::is_post_user_template( 'module' ) ) { $nodes['modules'] = self::get_all_modules(); } 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; } } else if ( '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; } } } } } } } } return $nodes; } /** * Returns node settings that are merged with the * default or preview settings. * * @since 1.0 * @param object $node A node object. * @return object */ static public function get_node_settings($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() ) { $template_post_id = self::get_node_template_post_id( $node->template_id ); $template_data = self::get_layout_data( 'published', $template_post_id ); $template_node = $template_data[ $node->template_node_id ]; $node->settings = $template_node->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 { $defaults = self::get_node_defaults($node); $settings = (object)array_merge((array)$defaults, (array)$node->settings); if ( 'module' == $node->type ) { $settings = self::merge_nested_module_defaults( $node->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($node->type == 'row') { $new_settings = self::process_row_settings($node, $new_settings); } if($node->type == 'column') { $new_settings = self::process_col_settings($node, $new_settings); } if($node->type == 'module') { $new_settings = self::process_module_settings($node, $new_settings); } 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($node->type == 'row') { $defaults = self::get_row_defaults(); } else if($node->type == 'column') { $defaults = self::get_col_defaults(); } else if($node->type == 'module') { $defaults = self::get_module_defaults($node->settings->type); } return $defaults; } /** * 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 ] ); // Reorder sibling nodes. $siblings = self::get_nodes( $node->type, $node->parent ); $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 = null, &$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($node->type == 'module' && isset(self::$modules[$node->settings->type])) { $class = get_class(self::$modules[$node->settings->type]); $instance = new $class(); $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. foreach($nodes as $node) { $data[$node->node]->position = $new_pos; $new_pos++; } // Update the layout data. self::update_layout_data($data); } /** * 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. self::reorder_node($node_id, $position); } /** * 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. * @return object The new row object. */ static public function add_row($cols = '1-col', $position = false) { $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($position !== false) { self::reorder_node($row_node_id, $position); } // Add a column group. self::add_col_group($row_node_id, $cols, 0); // Return the updated row. return self::get_node($row_node_id); } /** * Copys a row and adds it to the current layout. * * @since 1.0 * @param string $node_id Node ID of the row to copy. * @return void */ static public function copy_row( $node_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(); // 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 ); } else if ( '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 ); } } } } } } // 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 ) { if ( $child->type == 'column-group' ) { 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; } } 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() { return self::get_settings_form_defaults( 'row' ); } /** * Returns an array of spacing placeholders for row * margins and padding. * * @since 1.9 * @return array */ static public function get_row_spacing_placeholders() { $settings = FLBuilderModel::get_global_settings(); $placeholders = array(); // Default. $placeholders['row_margins'] = $settings->row_margins; $placeholders['row_padding'] = $settings->row_padding; // Medium. $placeholders['row_margins_medium'] = ( '' != $settings->row_margins_medium ) ? $settings->row_margins_medium : $settings->row_margins; $placeholders['row_padding_medium'] = ( '' != $settings->row_padding_medium ) ? $settings->row_padding_medium : $settings->row_padding; // Responsive row margins. if ( '' != $settings->row_margins_responsive ) { $placeholders['row_margins_responsive'] = $settings->row_margins_responsive; } else if ( $settings->auto_spacing ) { $placeholders['row_margins_responsive'] = 0; } else { $placeholders['row_margins_responsive'] = $placeholders['row_margins_medium']; } // Responsive row padding. if ( '' != $settings->row_padding_responsive ) { $placeholders['row_padding_tb_responsive'] = $settings->row_padding_responsive; $placeholders['row_padding_lr_responsive'] = $settings->row_padding_responsive; } else if ( $settings->auto_spacing ) { $placeholders['row_padding_tb_responsive'] = $placeholders['row_padding_medium']; $placeholders['row_padding_lr_responsive'] = 0; } else { $placeholders['row_padding_tb_responsive'] = $placeholders['row_padding_medium']; $placeholders['row_padding_lr_responsive'] = $placeholders['row_padding_medium']; } return $placeholders; } /** * 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 ( $new_settings->bg_type == 'video' ) { // Video Fallback Photo if ( ! empty( $new_settings->bg_video_fallback_src ) ) { $fallback = $new_settings->bg_video_fallback_src; } else { $fallback = ''; } if ( $new_settings->bg_video_source == 'wordpress' ) { // 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; } // 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; } } } // Cache background slideshow data. if($new_settings->bg_type == 'slideshow' && $new_settings->ss_source == 'wordpress') { // 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 ( $row->settings->bg_type == 'video' ) { 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; } } // Background Slideshow else if ( $row->settings->bg_type == 'slideshow' && 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(); } // 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(); } /** * 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. * @return object The new column group object. */ static public function add_col_group($node_id = null, $cols = '1-col', $position = false) { $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; } } } // Add an existing column. else { $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($position !== false) { self::reorder_node($group_node_id, $position); } // 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) { // 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); } // Resize the remaining columns. 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, 2); } // 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; // Don't resize if only one column or width isn't a number. if($num_cols == 1 || !is_numeric($new_width)) { return $col->settings->size; } // Find the sibling column to absorb this resize. if($pos === 0) { $sibling = $cols[1]; } else if($pos == $num_cols - 1) { $sibling = $cols[$num_cols - 2]; } else { $sibling = $cols[$pos + 1]; } // 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, 2); // 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 void */ 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 ); } /** * 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 ( '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 ), 2 ); 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 ( !vepressure-steam-sterilizer-with-ce-product/" title="Armenian" target="_blank">Armenian