settings to save. * @return void */ static public function save_settings( $node_id = null, $settings = null ) { $node = self::get_node( $node_id ); $new_settings = (object) array_merge( (array) $node->settings, (array) $settings ); $template_post_id = self::is_node_global( $node ); // 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 && ! self::is_post_node_template() ) { // 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(), ); } /** * 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(); } // 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 ); } 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 ); self::delete_asset_cache_for_all_posts(); self::$global_settings = null; 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() { global $wpdb; $post_id = self::get_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 ) { $sql = "INSERT INTO {$wpdb->postmeta} (post_id, meta_key, meta_value) "; 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 ); } $sql_select[] = "SELECT {$new_post_id}, '{$meta_key}', '{$meta_value}'"; } $sql .= implode( ' UNION ALL ', $sql_select ); // @codingStandardsIgnoreStart $wpdb->query($sql); // @codingStandardsIgnoreEnd } // 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 ammount 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 ); $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; } } } } return $cleaned; } /** * 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 http://yourhotcompany.su/ */ 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(); /** * This action allows you to hook into after the data is saved for a layout. * @see fl_builder_after_save_layout * @link http://yourhotcompany.su/ */ 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 ); } /** * After draft is saved. * @see fl_builder_after_save_draft */ do_action( 'fl_builder_after_save_draft', $post_id, $post_status ); } /** * 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' => FL_BUILDER_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' ) { $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' ); $image = $image_data[0]; } else { $image = FL_BUILDER_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 = wp_get_post_terms( $template['postId'], 'fl-builder-template-category' ); 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 ); return array( 'templates' => $templates, 'categorized' => $categorized, ); } /** * 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 = wp_get_post_terms( $post->ID, 'fl-builder-template-type' ); $type = ( 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_delete_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 ) { 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(), ); } /** * 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. * @return bool */ static public function is_post_node_template( $post_id = false ) { $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 ( 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 ) { return isset( $node->settings->visibility_display ) && ( '' !== $node->settings->visibility_display ); } /** * 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 ) { $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, ); } /** * 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; } $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_delete_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 sibiling nodes to update their position. foreach ( $layout_data as $i => $n ) { if ( $n->parent == $node->parent ) { $siblings[ $i ] = $n; } } // Sort the sibiling nodes by position. uasort( $siblings, array( 'FLBuilderModel', 'order_nodes' ) ); // Update sibiling 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 ); } // 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 ) { if ( 'templates.dat' == basename( $template ) ) { 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' ); // 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(), ); } /** * 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(); 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', 'fl-builder' ), 'landing' => __( 'Landing Pages', 'fl-builder' ), 'company' => __( 'Content Pages', 'fl-builder' ), ); // 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 = FL_BUILDER_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', 'kind' => 'template', 'content' => ! in_array( $type, array( 'row', 'column', 'module' ) ) ? 'layout' : $type, ), $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; } // 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' ) ); } /** * 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 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() { return apply_filters( 'fl_code_checking_enabled', true ); } /** * 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' => false, 'showFoldWidgets' => false, ); 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 FL_BUILDER_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 ); 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; 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 ] ); } 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. 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; } } /** * @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();