0, 'sm' => 576, 'md' => 768, 'lg' => 992, 'xl' => 1200 ]; const COLS_MOBILE_BREAKPOINT = 'md'; private $collection;//the collection of css rules private $module_types = []; private $sek_model; // property "is_global_stylesheet" has been added when fixing https://github.com/presscustomizr/nimble-builder/issues/273 private $is_global_stylesheet; private $parent_level_model = array(); public $customizer_active_locations = '_not_set_';//June 2020 => added to prevent printing css for not active locations public $current_sniffed_location = '_not_set_';//June 2020 => added to prevent printing css for not active locations public $sniffed_locations = [];//oct 2020 => will populate a collection of location sniffed while parsing the sek_model public $sniffed_modules = [];//oct 2020 => will populate a collection of modules sniffed while parsing the sek_model private $needs_wp_comments_stylesheet = false;//April 2021 => for site templates public function __construct( $sek_model = array(), $is_global_stylesheet = false ) { $this->sek_model = $sek_model; // June 2020 : this property is set when saving the customizer // and used to determine if we need to generate css for a given location // typically useful when a local header is populated with sections but not used on the page. While still present in the collection of location, we don't want to generate css for it. $raw_customizer_active_locations = ( isset($_POST['active_locations']) && is_array($_POST['active_locations']) ) ? $_POST['active_locations'] : '_not_set_'; // sanitize now and cache if ( !is_array($raw_customizer_active_locations) ) { $this->customizer_active_locations = sanitize_text_field($raw_customizer_active_locations); } else { $_active_locations = []; foreach($raw_customizer_active_locations as $loc ) { $_active_locations[] = sanitize_text_field($loc); } $this->customizer_active_locations = $_active_locations; } $this->is_global_stylesheet = $is_global_stylesheet; // set the css rules for columns /* ------------------------------------------------------------------------- * * SCHEDULE CSS RULES FILTERING /* ------------------------------------------------------------------------- */ // filter fired in sek_css_rules_sniffer_walker() add_filter( 'sek_add_css_rules_for_level_options', array( $this, 'sek_add_rules_for_column_width' ), 10, 2 ); $this->sek_css_rules_sniffer_walker(); } // Fired in the constructor // Walk the level tree and build rules when needed // The rules are filtered when some conditions are met. // This allows us to schedule the css rules addition remotely : // - from the module registration php file // - from the generic input types ( @see sek_add_css_rules_for_generic_css_input_types() ) public function sek_css_rules_sniffer_walker( $level = null, $parent_level = array() ) { $level = is_null( $level ) ? $this->sek_model : $level; $level = is_array( $level ) ? $level : array(); // The parent level is set when the function is invoked recursively, from a level where we actually have a 'level' property if ( !empty( $parent_level ) ) { $this->parent_level_model = $parent_level; } foreach ( $level as $key => $entry ) { // Let's cache the currently sniffed location if ( is_array($entry) && isset($entry['level']) && 'location' === $entry['level'] ) { $this->current_sniffed_location = $entry['id']; $this->sniffed_locations[ $this->current_sniffed_location ] = []; } // When saving in the customizer, the active locations are passed in $_POST // so we can determine if a location is currently active or not, and if not, we don't need to generate CSS for it. // Oct 2020 : case of the global stylesheet : // The global stylesheet may be inactive on a given customization, which means that the customizer_active_locations won't include any global locations. // But this does not mean that the global stylesheet is inactive on other pages. // That's why we only verify the active location condition when !$this->is_global_stylesheet if ( !$this->is_global_stylesheet && '_not_set_' !== $this->customizer_active_locations && is_array($this->customizer_active_locations) && '_not_set_' !== $this->current_sniffed_location && !in_array($this->current_sniffed_location, $this->customizer_active_locations ) ) { continue; } $rules = array(); // INPUT CSS RULES <= used in front modules only // When we are inside the associative arrays of // - the module 'value' // - or the level 'options' entries <= NOT ANYMORE // the keys are not integer. // We want to filter each input // which makes it possible to target for example the font-family. Either in module values or in level options if ( is_string( $key ) && 1 < strlen( $key ) ) { // we need to have a level model set if ( !empty( $parent_level ) && is_array( $parent_level ) && !empty( $parent_level['module_type'] ) ) { // Populates the sniffed module collection for later use $current_location_modules = $this->sniffed_locations[ $this->current_sniffed_location ]; $current_location_modules = is_array($current_location_modules) ? $current_location_modules : []; if ( !in_array( $parent_level['module_type'], $current_location_modules ) ) { $this->sniffed_modules[] = $parent_level['module_type']; $this->sniffed_locations[ $this->current_sniffed_location ][] = $parent_level['module_type']; } // If the current level is a module, check if the module has generic css ( *_css suffixed ) selectors specified on registration // $module_level_css_selectors = null; // $registered_input_list = null; $module_level_css_selectors = sek_get_registered_module_type_property( $parent_level['module_type'], 'css_selectors' ); $registered_input_list = sek_get_registered_module_input_list( $parent_level['module_type'] ); if ( 'value' === $key && is_array( $entry ) ) { $is_father = sek_get_registered_module_type_property( $parent_level['module_type'], 'is_father' ); $father_mod_type = $parent_level['module_type']; // If the module has children ( the module is_father ), let's loop on each option group if ( $is_father ) { $children = sek_get_registered_module_type_property( $father_mod_type, 'children' ); // Loop on the children foreach ( $entry as $opt_group_type => $input_candidates ) { if ( !is_array( $children ) ) { sek_error_log( 'Father module ' . $father_mod_type . ' has invalid children'); continue; } if ( empty( $children[$opt_group_type] ) ) { sek_error_log( 'Father module ' . $father_mod_type . ' has a invalid child for option group : '. $opt_group_type); continue; } // The module type of the currently looped child $child_mod_type = $children[ $opt_group_type ]; // If the child module has no css_selectors set, we fallback on the father css_selector $child_css_selector = sek_get_registered_module_type_property( $child_mod_type, 'css_selectors' ); $child_css_selector = empty( $child_css_selector ) ? $module_level_css_selectors : $child_css_selector; // Is is a multi-item module ? $is_multi_items_module = true === sek_get_registered_module_type_property( $child_mod_type, 'is_crud' ); if ( $is_multi_items_module ) { foreach ( $input_candidates as $item_input_list ) { $rules = $this->sek_loop_on_input_candidates_and_maybe_generate_css_rules( $rules, array( 'input_list' => $item_input_list, 'registered_input_list' => $registered_input_list[ $opt_group_type ],// <= the full list of input for the module 'parent_module_level' => $parent_level,// <= the parent module level. can be one of those array( 'location', 'section', 'column', 'module' ) 'module_css_selector' => $child_css_selector, //a default set of css_se 'is_multi_items' => true ) ); $rules = apply_filters( "sek_add_css_rules_for_single_item_in_module_type___{$child_mod_type}", $rules, array( 'input_list' => wp_parse_args( $item_input_list, sek_get_default_module_model( $child_mod_type ) ), 'parent_module_type' => $child_mod_type,// 'registered_input_list' => $registered_input_list[ $opt_group_type ],// <= the full list of input for the module 'parent_module_id' => $parent_level['id'],// <= the parent module level id, used to increase the CSS specificity 'module_css_selector' => $child_css_selector //a default set of css_se ) ); } } else { $rules = $this->sek_loop_on_input_candidates_and_maybe_generate_css_rules( $rules, array( 'input_list' => $input_candidates, 'registered_input_list' => $registered_input_list[ $opt_group_type ],// <= the full list of input for the module 'parent_module_level' => $parent_level,// <= the parent module level. can be one of those array( 'location', 'section', 'column', 'module' ) 'module_css_selector' => $child_css_selector //a default set of css_selectors might have been specified on module registration )); } }//foreach } //if ( $is_father ) else { // Is is a multi-item module ? $is_multi_items_module = true === sek_get_registered_module_type_property( $father_mod_type, 'is_crud' ); if ( $is_multi_items_module ) { foreach ( $entry as $item_input_list ) { $rules = $this->sek_loop_on_input_candidates_and_maybe_generate_css_rules( $rules, array( 'input_list' => $item_input_list, 'registered_input_list' => $registered_input_list,// <= the full list of input for the module 'parent_module_level' => $parent_level,// <= the parent module level. can be one of those array( 'location', 'section', 'column', 'module' ) 'module_css_selector' => $module_level_css_selectors, //a default set of css_se 'is_multi_items' => true ) ); $rules = apply_filters( "sek_add_css_rules_for_multi_item_module_type___{$father_mod_type}", $rules, array( 'input_list' => wp_parse_args( $item_input_list, sek_get_default_module_model( $father_mod_type ) ), 'parent_module_type' => $father_mod_type,// <= the full list of input for the module 'parent_module_id' => $parent_level['id'],// <= the parent module level id, used to increase the CSS specificity 'module_css_selector' => $module_level_css_selectors, //a default set of css_se ) ); } } else { $rules = $this->sek_loop_on_input_candidates_and_maybe_generate_css_rules( $rules, array( 'input_list' => $entry, 'registered_input_list' => $registered_input_list,// <= the full list of input for the module 'parent_module_level' => $parent_level,// <= the parent module level. can be one of those array( 'location', 'section', 'column', 'module' ) 'module_css_selector' => $module_level_css_selectors //a default set of css_selectors might have been specified on module registration )); } }//if is_father }//if }//if }//if // INPUT TEXT LEVEL CSS RULES // @added in sept 2019 for https://github.com/presscustomizr/nimble-builder/issues/499 // When we are inside the associative arrays of the level 'options' // the keys are not integer. // We want to filter each input // which makes it possible to target for example the font-family. Either in module values or in level options if ( is_string( $key ) && 1 < strlen( $key ) && 'options' === $key ) { // we need to have a level model set if ( !empty( $parent_level ) && is_array( $parent_level ) ) { if ( is_array( $entry ) ) { // Level options are structured as an associative array of option groups // $entry = array( // 'text' => array( // font_size_css => ... // color_css => ... // ), // 'bg' => array(), // ... // ) foreach ( $entry as $opt_group_type => $input_candidates ) { if ( 'level_text' !== $opt_group_type ) continue; $level_text_registered_input_list = sek_get_registered_module_input_list( 'sek_level_text_module' ); $level_text_css_selectors = sek_get_registered_module_type_property( 'sek_level_text_module', 'css_selectors' ); $rules = $this->sek_loop_on_input_candidates_and_maybe_generate_css_rules( $rules, array( 'input_list' => $input_candidates, 'registered_input_list' => $level_text_registered_input_list,// <= the full list of input for the module 'parent_module_level' => $parent_level,// <= the parent module level. can be one of those array( 'location', 'section', 'column', 'module' ) 'module_css_selector' => $level_text_css_selectors //a default set of css_selectors might have been specified on module registration )); } }//if }//if }//if // LEVEL CSS RULES if ( is_array( $entry ) ) { // Populate rules for sections / columns / modules // Location level are excluded if ( !empty( $entry[ 'level' ] ) && 'location' != $entry[ 'level' ] ) { $level_type = $entry[ 'level' ]; $rules = apply_filters( "sek_add_css_rules_for__{$level_type}__options", $rules, $entry ); // build rules for level options => section / column / module // param is_global_stylesheet says that we're building the global stylesheet // introduced for the custom CSS, to know if we're building CSS for a local or a global section // @see https://github.com/presscustomizr/nimble-builder-pro/issues/67 $rules = apply_filters( 'sek_add_css_rules_for_level_options', $rules, $entry, $this->is_global_stylesheet ); } // populate rules for modules values if ( !empty( $entry[ 'level' ] ) && 'module' === $entry['level'] ) { if ( !empty( $entry['module_type'] ) ) { $module_type = $entry['module_type']; // populate module types list so we can add their stylesheet afterward $this->module_types[] = $module_type; // build rules for modules // applying sek_normalize_module_value_with_defaults() allows us to access all the value properties of the module without needing to check their existence $rules = apply_filters( "sek_add_css_rules_for_module_type___{$module_type}", $rules, sek_normalize_module_value_with_defaults( $entry ) ); } } } // if ( is_array( $entry ) ) { // POPULATE THE CSS RULES COLLECTION if ( !empty( $rules ) ) { //@TODO: MAKE SURE RULE ARE NORMALIZED foreach( $rules as $rule ) { if ( !is_array( $rule ) ) { sek_error_log( __CLASS__ . '::' . __FUNCTION__ . ' => a css rule should be represented by an array', $rule ); continue; } if ( empty( $rule['selector']) ) { sek_error_log( __CLASS__ . '::' . __FUNCTION__ . '=> a css rule is missing the selector param', $rule ); continue; } $this->sek_populate( $rule[ 'selector' ], $rule[ 'css_rules' ], $rule[ 'mq' ] ); }//foreach } // keep walking if the current $entry is an array // make sure that the parent_level_model is set right before jumping down to the next level if ( is_array( $entry ) ) { // Can we set a parent level ? if ( !empty( $entry['level'] ) && in_array( $entry['level'], array( 'location', 'section', 'column', 'module' ) ) ) { $parent_level = $entry; } // Let's go recursive $this->sek_css_rules_sniffer_walker( $entry, $parent_level ); } // Reset the parent level model because it might have been modified after walking the sublevels if ( !empty( $parent_level ) ) { $this->parent_level_model = $parent_level; } }//foreach }//sek_css_rules_sniffer_walker() // @param $rules // <= the in-progress global array of css rules to be populated // @param $params= array() // @return array of css rules* // The input ids prefixed with '_css' are eligible for automaric CSS rules generation. // @see add_filter( "sek_add_css_rules_for_input_id", '\Nimble\sek_add_css_rules_for_css_sniffed_input_id', 10, 1 ); function sek_loop_on_input_candidates_and_maybe_generate_css_rules( $rules, $params ) { // normalize params $default_params = array( 'input_list' => array(), 'registered_input_list' => array(),// <= the full list of input for the module 'parent_module_level' => array(),// <= the parent module level. can be one of those array( 'location', 'section', 'column', 'module' ) 'module_css_selector' => '',//a default set of css_selectors might have been specified on module registration 'is_multi_items' => false ); $params = wp_parse_args( $params, $default_params ); // FOR MULTI-ITEM MODULES=> add the item-id // a multi-item module has a unique id for each item // An item looks like : // Array // ( // [id] => 34913f6eef98 // [icon] => fab fa-accusoft // [color_css] => #dd9933 // ) $item_id = null; if ( $params['is_multi_items'] ) { if ( !is_array( $params['input_list'] ) || !isset($params['input_list']['id']) ) { sek_error_log( __FUNCTION__ . ' => Error => each item of a multi-item module must have an id', $params ); } else { $item_id = $params['input_list']['id']; } } foreach( $params['input_list'] as $input_id_candidate => $_input_val ) { if ( false !== strpos( $input_id_candidate, '_css') ) { $rules = apply_filters( 'sek_add_css_rules_for_input_id', $rules, array( 'css_val' => $_input_val,//string or array(), //<= the css property value 'input_id' => $input_id_candidate,//string// <= the unique input_id as it as been declared on module registration 'registered_input_list' => $params['registered_input_list'],// <= the full list of input for the module 'parent_module_level' => $params['parent_module_level'],// <= the parent module level. can be one of those array( 'location', 'section', 'column', 'module' ) 'module_css_selector' => $params['module_css_selector'],// <= a default set of css_selectors might have been specified on module registration 'is_multi_items' => $params['is_multi_items'],// <= for multi-item modules, the input selectors will be made specific for each item-id. In module templates, we'll use data-sek-item-id="%5$s" // implemented to allow CSS rules to be generated on a per-item basis // for https://github.com/presscustomizr/nimble-builder/issues/78 'item_id' => $item_id // <= a multi-item module has a unique id for each item )); } else { // added April 2021 for site templates // sniff if we need to add the comments css => looking for {{the_comments}} if ( !$this->is_global_stylesheet && is_string($_input_val) ) { preg_replace_callback( '!\{\{\s?(.*?)\s?\}\}!', array( $this, "sek_sniff_wp_comment_template_tag" ), $_input_val); } } } return $rules; } // added Arpil 2021 for site templates public function sek_sniff_wp_comment_template_tag($matches) { //sek_error_log('$matches ??', $matches ); if ( !is_array($matches) || empty($matches[1]) ) return; if ( !$this->needs_wp_comments_stylesheet && is_string($matches[1] ) ) { $this->needs_wp_comments_stylesheet = false !== strpos($matches[1], 'the_comments'); } } // @return void() // populates the css rules ::collection property, organized by media queries public function sek_populate( $selector, $css_rules, $mq = '' ) { if ( !is_string( $selector ) ) return; if ( !is_string( $css_rules ) ) return; // Assign a default media device //TODO: allowed media query? $mq_device = 'all_devices'; // If a media query is requested, build it if ( !empty( $mq ) ) { if ( false === strpos($mq, 'max') && false === strpos($mq, 'min')) { error_log( __FUNCTION__ . ' ' . __CLASS__ . ' => the media queries only accept max-width and min-width rules'); } else { $mq_device = $mq; } } // if the media query for this device is not yet added, add it if ( !isset( $this->collection[ $mq_device ] ) ) { $this->collection[ $mq_device ] = array(); } if ( !isset( $this->collection[ $mq_device ][ $selector ] ) ) { $this->collection[ $mq_device ][ $selector ] = array(); } $this->collection[ $mq_device ][ $selector ][] = $css_rules; }//sek_populate // @return string public static function sek_maybe_wrap_in_media_query( $css, $mq_device = 'all_devices' ) { if ( 'all_devices' === $mq_device ) { return $css; } if ( false === strpos($mq_device, '(') || false === strpos($mq_device, ')') ) { sek_error_log( __CLASS__ . '::' . __FUNCTION__ . ' => missing parenthesis in the media queries', $mq_device ); return $css; } return sprintf( '@media%1$s{%2$s}', $mq_device, $css); } // sorts the media queries from all_devices to the smallest width // This doesn't make the difference between max-width and min-width // @return integer public static function user_defined_array_key_sort_fn($a, $b) { if ( 'all_devices' === $a ) { return -1; } if ( 'all_devices' === $b ) { return 1; } $a_int = (int)preg_replace('/[^0-9]/', '', $a) * 1; $b_int = (int)preg_replace('/[^0-9]/', '', $b) * 1; return $b_int - $a_int; } //@returns a stringified stylesheet, ready to be printed on the page or in a file public function get_stylesheet() { $css = ''; // CONCATENATE MODULE STYLESHEETS // Oct 2020 => https://github.com/presscustomizr/nimble-builder/issues/749 $this->module_types = array_unique($this->module_types); $modules_css = ''; $base_uri = NIMBLE_BASE_PATH . '/assets/front/css/modules/'; global $wp_filesystem; $reading_issue = false; $read_attempt = false; $concatenated_module_stylesheets = Nimble_Manager()->concatenated_module_stylesheets; foreach (Nimble_Manager()->big_module_stylesheet_map as $module_type => $stylesheet_name ) { if ( $reading_issue ) continue; if ( !in_array($module_type , $this->module_types ) ) continue; // abort if the module stylesheet has already been concatenated in another stylesheet if ( in_array( $module_type, $concatenated_module_stylesheets ) ) continue; $uri = sprintf( '%1$s%2$s%3$s', $base_uri , $stylesheet_name, sek_is_dev_mode() ? '.css' : '.min.css' ); $uri = wp_normalize_path($uri); $read_attempt = true; //sek_error_log('$uri ??' . $module_type . $stylesheet_name, $uri ); if ( $wp_filesystem->exists($uri) && $wp_filesystem->is_readable($uri) ) { $modules_css .= $wp_filesystem->get_contents($uri); // add this stylesheet to the already concatenated list $concatenated_module_stylesheets[] = $module_type; } else { $reading_issue = true; } }//foreach // COMMENTS CSS $comments_css = ''; //if ( apply_filters('include_comments_css', true ) ) { if ( !$this->is_global_stylesheet && $this->needs_wp_comments_stylesheet ) { $read_attempt = true; $uri = sprintf( '%1$s%2$s%3$s', NIMBLE_BASE_PATH . '/assets/front/css/', 'sek-wp-comments', sek_is_dev_mode() ? '.css' : '.min.css' ); $uri = wp_normalize_path($uri); //sek_error_log('$uri ??' . $module_type . $stylesheet_name, $uri ); if ( $wp_filesystem->exists($uri) && $wp_filesystem->is_readable($uri) ) { $comments_css = $wp_filesystem->get_contents($uri); } else { $reading_issue = true; } } // update the list of concatenated module stylesheets so that NB doesn't concatenate a module stylesheet twice for the local css and for the global css Nimble_Manager()->concatenated_module_stylesheets = array_unique($concatenated_module_stylesheets); if ( $read_attempt ) { if ( $reading_issue ) { update_option( NIMBLE_OPT_FOR_MODULE_CSS_READING_STATUS, 'failed'); sek_error_log( __CLASS__ . '::' . __FUNCTION__ . ' => reading issue => impossible to concatenate module stylesheets'); } else { update_option( NIMBLE_OPT_FOR_MODULE_CSS_READING_STATUS, 'OK'); } } //sek_error_log('$modules_css ??', $modules_css ); // ORGANIZE CSS RULES BY MEDIA QUERIES $collection = apply_filters( 'nimble_css_rules_collection_before_printing_stylesheet', $this->collection ); if ( is_array( $collection ) && !empty( $collection ) ) { // Sort the collection by media queries uksort( $collection, array( get_called_class(), 'user_defined_array_key_sort_fn' ) ); // process foreach ( $collection as $mq_device => $selectors ) { $_css = ''; foreach ( $selectors as $selector => $css_rules ) { $css_rules = is_array( $css_rules ) ? implode( ';', $css_rules ) : $css_rules; $_css .= $selector . '{' . $css_rules . '}'; $_css = str_replace(';;', ';', $_css);//@fixes https://github.com/presscustomizr/nimble-builder/issues/137 } $_css = self::sek_maybe_wrap_in_media_query( $_css, $mq_device ); $css .= $_css; } } // CONCATENATE MODULE CSS + GENERATED CSS return apply_filters( 'nimble_get_dynamic_stylesheet', $modules_css . $comments_css . $css, $this->is_global_stylesheet, $this->sniffed_locations, $this->sniffed_modules ); } // Helper // @return css string including media queries // @used for example when generating the rules for used defined section widths locally and globally public static function sek_generate_css_stylesheet_for_a_set_of_rules( $rules ) { $rules_collection = array(); $css = ''; if ( empty( $rules ) || !is_array( $rules ) ) return $css; // POPULATE THE CSS RULES COLLECTION foreach( $rules as $rule ) { if ( !is_array( $rule ) ) { sek_error_log( __CLASS__ . '::' . __FUNCTION__ . ' => a css rule should be represented by an array', $rule ); continue; } if ( empty($rule['selector']) || !is_string( $rule['selector'] ) ) { sek_error_log( __CLASS__ . '::' . __FUNCTION__ . '=> a css rule is missing the selector param', $rule ); continue; } $selector = $rule[ 'selector' ]; $css_rules = $rule[ 'css_rules' ]; $mq = $rule[ 'mq' ]; if ( !is_string( $css_rules ) ) continue; // Assign a default media device //TODO: allowed media query? $mq_device = 'all_devices'; // If a media query is requested, build it if ( !empty( $mq ) ) { if ( false === strpos($mq, 'max') && false === strpos($mq, 'min')) { error_log( __FUNCTION__ . ' ' . __CLASS__ . ' => the media queries only accept max-width and min-width rules'); } else { $mq_device = $mq; } } // if the media query for this device is not yet added, add it if ( !isset( $rules_collection[ $mq_device ] ) ) { $rules_collection[ $mq_device ] = array(); } if ( !isset( $rules_collection[ $mq_device ][ $selector ] ) ) { $rules_collection[ $mq_device ][ $selector ] = array(); } $rules_collection[ $mq_device ][ $selector ][] = $css_rules; }//foreach // GENERATE CSS if ( is_array( $rules_collection ) && !empty( $rules_collection ) ) { // Sort the collection by media queries // get_called_class() is supported by php >= 5.3.0. Nimble needs 5.4 // @see https://developer.wordpress.org/reference/functions/add_action/ uksort( $rules_collection, array( get_called_class(), 'user_defined_array_key_sort_fn' ) ); // process foreach ( $rules_collection as $mq_device => $selectors ) { $_css = ''; foreach ( $selectors as $selector => $css_rules ) { $css_rules = is_array( $css_rules ) ? implode( ';', $css_rules ) : $css_rules; $_css .= $selector . '{' . $css_rules . '}'; $_css = str_replace(';;', ';', $_css);//@fixes https://github.com/presscustomizr/nimble-builder/issues/137 } $_css = self::sek_maybe_wrap_in_media_query( $_css, $mq_device ); $css .= $_css; } } return $css; }//sek_generate_css_stylesheet_for_a_set_of_rules() // hook : sek_add_css_rules_for_level_options // fired this class constructor public function sek_add_rules_for_column_width( $rules, $column ) { if ( !is_array( $column ) ) return $rules; if ( empty( $column['level'] ) || 'column' !== $column['level'] || empty( $column['id'] ) ) return $rules; $width = null; // First try to find a width value in options, then look in the previous width property for backward compatibility // After implementing https://github.com/presscustomizr/nimble-builder/issues/279 $column_options = isset( $column['options'] ) ? $column['options'] : array(); //sek_error_log( 'COLUMN MODEL WHEN ADDING RULES ?', $column_options ); if ( !empty( $column_options['width'] ) && !empty( $column_options['width']['custom-width'] ) ) { $width_candidate = (float)$column_options['width']['custom-width']; if ( $width_candidate < 0 || $width_candidate > 100 ) { sek_error_log( __FUNCTION__ . ' => invalid width value for column id : ' . $column['id'] ); } else { $width = $width_candidate; } } else { // Backward compat since June 2019 // After implementing https://github.com/presscustomizr/nimble-builder/issues/279 $width = empty( $column[ 'width' ] ) || !is_numeric( $column[ 'width' ] ) ? '' : $column['width']; } // width if ( empty( $width ) ) return $rules; // define a default breakpoint : 768 $breakpoint = self::$breakpoints[ self::COLS_MOBILE_BREAKPOINT ]; // Does the parent section have a custom breakpoint set ? $parent_section = sek_get_parent_level_model( $column['id'] ); if ( 'no_match' === $parent_section ) { sek_error_log( __FUNCTION__ . ' => $parent_section not found for column id : ' . $column['id'] ); return $rules; } $section_custom_breakpoint = intval( sek_get_section_custom_breakpoint( array( 'section_model' => $parent_section, 'for_responsive_columns' => true ) ) ); if ( $section_custom_breakpoint >= 1 ) { $breakpoint = $section_custom_breakpoint; } else { // Is there a global custom breakpoint set ? $global_custom_breakpoint = intval( sek_get_global_custom_breakpoint() ); if ( $global_custom_breakpoint >= 1 ) { $breakpoint = $global_custom_breakpoint; } } // Note : the css selector must be specific enough to override the possible parent section ( or global ) custom breakpoint one. // @see sek_add_css_rules_for_level_breakpoint() $rules[] = array( 'selector' => sprintf( '[data-sek-id="%1$s"] .sek-sektion-inner > .sek-column[data-sek-id="%2$s"]', $parent_section['id'], $column['id'] ), 'css_rules' => sprintf( '-ms-flex: 0 0 %1$s%%;flex: 0 0 %1$s%%;max-width: %1$s%%', $width ), 'mq' => "(min-width:{$breakpoint}px)" ); return $rules; } }//end class ?> 'sek-'.rand(), 'skope_id' => '', // property "is_global_stylesheet" has been added when fixing https://github.com/presscustomizr/nimble-builder/issues/273 'is_global_stylesheet' => false, 'mode' => self::MODE_FILE, 'css_string_to_enqueue_or_print' => $this->css_string_to_enqueue_or_print, 'dep' => $this->dep, 'hook' => '', 'priority' => $this->priority, 'customizer_save' => false,//<= used when saving the customizer settins => we want to write the css file on Nimble_Collection_Setting::update() 'force_write' => $this->force_write, 'force_rewrite' => $this->force_rewrite ); $args = wp_parse_args( $args, $defaults ); //normalize some parameters $args[ 'dep' ] = is_array( $args[ 'dep' ] ) ? $args[ 'dep' ] : array(); $args[ 'priority'] = is_numeric( $args[ 'priority' ] ) ? $args[ 'priority' ] : $this->priority; //turn $args into object properties foreach ( $args as $key => $value ) { if ( property_exists( $this, $key ) && array_key_exists( $key, $defaults) ) { $this->$key = $value; } } if ( empty( $this->skope_id ) ) { sek_error_log( __CLASS__ . '::' . __FUNCTION__ .' => __construct => skope_id not provided' ); return; } //build no parameterized properties $this->_sek_dyn_css_set_properties(); // Introduced March 2021 for #478 if ( 'delete' !== $this->mode ) { // Possible scenarii : // 1) customizing : // the css is always printed inline. If there's already an existing css file for this skope_id, it's not enqueued. // 2) saving in the customizer : // the css file is written in a "force_rewrite" mode, meaning that any existing css file gets re-written. // There's no enqueing scheduled, 'customizer_save' mode. // 3) front, user logged in + 'customize' capabilities : // the css file is re-written on each page load + enqueued. If writing a css file is not possible, we fallback on inline printing. // 4) front, user not logged in : // the default behavior is that the css file is enqueued. // It should have been written when saving in the customizer. If no file available, we try to write it. If writing a css file is not possible, we fallback on inline printing. if ( is_customize_preview() || !$this->_sek_dyn_css_file_exists_is_readable_and_has_content() || $this->force_rewrite || $this->customizer_save ) { $this->sek_model = sek_get_skoped_seks( $this->skope_id ); // on front, when no stylesheet is available, the fallback hook must be set to wp_head, because the hook property might be empty // fixes https://github.com/presscustomizr/nimble-builder/issues/328 if ( !is_customize_preview() && !$this->_sek_dyn_css_file_exists_is_readable_and_has_content() ) { $this->hook = 'wp_head'; } //build stylesheet $this->builder = new Sek_Dyn_CSS_Builder( $this->sek_model, $this->is_global_stylesheet ); // now that the stylesheet is ready let's cache it // Note that esc_html() cannot be used because `div > span` is not interpreted properly. $this->css_string_to_enqueue_or_print = (string)strip_tags($this->builder->get_stylesheet()); } // Do we have any rules to print / enqueue ? // If yes, print in the dom or enqueue depending on the current context ( customization or front ) // If not, delete any previouly created stylesheet //hook setup for printing or enqueuing //bail if "customizer_save" == true, typically when saving the customizer settings @see Nimble_Collection_Setting::update() if ( !$this->customizer_save ) { // when not customizing, we write and enqueue : // - if the file already exists, // - or if we just have generated the CSS because the file had been deleted if ( !empty($this->css_string_to_enqueue_or_print) || $this->_sek_dyn_css_file_exists_is_readable_and_has_content() ) { $this->_schedule_css_and_fonts_enqueuing_or_printing_maybe_on_custom_hook(); } else { $this->sek_dyn_css_delete_file_if_empty(); } } else { //sek_error_log( __CLASS__ . '::' . __FUNCTION__ .' ?? => $this->css_string_to_enqueue_or_print => ', $this->css_string_to_enqueue_or_print ); if ( !empty($this->css_string_to_enqueue_or_print) ) { $this->sek_dyn_css_maybe_write_css_file(); } else { // When customizing, the stylesheet is always generated. // So if it is empty, it means we have to delete it $this->sek_dyn_css_delete_file(); } } // Maybe update global inline style now with a filter // This CSS is the one generated by global options like global text, global width, global breakpoint // It is printed @wp_head inline // for better performances on front, NB only wants to re-generate this style when customizing, and we user is logged in ( force_rewrite ) // see https://github.com/presscustomizr/nimble-builder/issues/750 if ( $this->is_global_stylesheet ) { if ( is_customize_preview() || $this->force_rewrite || $this->customizer_save ) { $global_style = Nimble_Manager()->sek_build_global_options_inline_css(); //sek_error_log('SOO GLOBAL INLINE CSS?', $global_style ); update_option( NIMBLE_OPT_FOR_GLOBAL_CSS, $global_style, 'no' ); } } }//if 'delete' !== $this->mode }//__construct /* * Private methods */ /** * * Build these instance properties based on the params passed on instantiation * called in the constructor * * @access private * */ private function _sek_dyn_css_set_properties() { $this->_sek_dyn_css_require_wp_filesystem(); $this->relative_base_path = $this->_sek_dyn_css_build_relative_base_path(); $this->base_uri = $this->_sek_dyn_css_build_base_uri(); $this->base_url = $this->_sek_dyn_css_build_base_url(); $this->uri = $this->_sek_dyn_css_build_uri(); $this->url = $this->_ssl_maybe_fix_url( $this->_sek_dyn_css_build_url() ); $this->file_exists = $this->_sek_dyn_css_file_exists_is_readable_and_has_content(); if ( self::MODE_FILE == $this->mode ) { if ( !$this->_sek_dyn_css_write_file_is_possible() ) { $this->mode = self::MODE_INLINE; } } // July 2020 remove previous folder // see https://github.com/presscustomizr/nimble-builder/issues/727 // if ( 'done' != get_transient( 'nimble_update_css_folder_name_0720' ) ) { // set_transient( 'nimble_update_css_folder_name_0720', 'done', 30 * YEAR_IN_SECONDS ); // } $upload_dir = wp_get_upload_dir(); $prev_folder_path = $this->_sek_dyn_css_build_relative_base_path( NIMBLE_DEPREC_ONE_CSS_FOLDER_NAME ); $previous_folder_one = wp_normalize_path( trailingslashit( $upload_dir['basedir'] ) . $prev_folder_path ); global $wp_filesystem; if ( $wp_filesystem->exists( $previous_folder_one ) ) { $wp_filesystem->rmdir( $previous_folder_one, true ); } // October 2020 remove previous folder when implementing dynamic module stylesheet concatenation $prev_folder_path = $this->_sek_dyn_css_build_relative_base_path( NIMBLE_DEPREC_TWO_CSS_FOLDER_NAME ); $previous_folder_two = wp_normalize_path( trailingslashit( $upload_dir['basedir'] ) . $prev_folder_path ); global $wp_filesystem; if ( $wp_filesystem->exists( $previous_folder_two ) ) { $wp_filesystem->rmdir( $previous_folder_two, true ); } } /** * replace http: URL with https: URL * @fix https://github.com/presscustomizr/nimble-builder/issues/188 * @param string $url * @return string */ private function _ssl_maybe_fix_url($url) { // only fix if source URL starts with http:// if ( is_ssl() && is_string($url) && stripos($url, 'http://') === 0 ) { $url = 'https' . substr($url, 4); } return $url; } /** * * Maybe setup hooks * called in the constructor * * @access private * */ private function _schedule_css_and_fonts_enqueuing_or_printing_maybe_on_custom_hook() { if ( $this->hook ) { add_action( $this->hook, array( $this, 'sek_dyn_css_enqueue_or_print_and_google_fonts_print' ), $this->priority ); } else { //enqueue or print $this->sek_dyn_css_enqueue_or_print_and_google_fonts_print(); } } /** * Enqueue CSS. * * Either enqueue the CSS file or add inline style, depending on the object mode property. * The inline enqueuing is also the fall-back if anything goes wrong while trying to enqueuing the file. * * This method can also write the file under some circumstances (see when the object force_write || force_rewrite are enabled) * * @access public * @return void() */ public function sek_dyn_css_enqueue_or_print_and_google_fonts_print() { // CSS FILE //case enqueue file : front end + user with customize caps not logged in if ( self::MODE_FILE == $this->mode ) { //in case we need to write the file before enqueuing //1) $this->css_string_to_enqueue_or_print must exists //2) we might need to force the rewrite even if the file exists or to write it if the file doesn't exist if ( $this->css_string_to_enqueue_or_print ) { if ( $this->force_rewrite || ( !$this->file_exists && $this->force_write ) ) { $this->file_exists = $this->sek_dyn_css_maybe_write_css_file(); } } //if the file exists if ( $this->file_exists ) { //this resource version is built upon the file last modification time wp_enqueue_style( "sek-dyn-{$this->id}", $this->url, $this->dep, filemtime($this->uri) ); $this->enqueued_or_printed = true; } }// if ( self::MODE_FILE ) //if $this->mode != 'file' or the file enqueuing didn't go through (fall back) //print inline style if ( $this->css_string_to_enqueue_or_print && !$this->enqueued_or_printed ) { $dep = array_pop( $this->dep ); if ( !$dep || wp_style_is( $dep, 'done' ) || !wp_style_is( $dep, 'done' ) && ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) { // only fired when doing ajax during customization in order to return a refreshed partial stylesheet printf( '', esc_attr($this->id), // Note that esc_html() cannot be used because `div > span` is not interpreted properly. strip_tags($this->css_string_to_enqueue_or_print) ); } else { wp_add_inline_style( $dep , $this->css_string_to_enqueue_or_print ); } $this->mode = self::MODE_INLINE; $this->enqueued_or_printed = true; } } /* * Public 'actions' */ /** * * Write the CSS to the disk, if we can * * @access public * * @return bool TRUE if the CSS file has been written, FALSE otherwise */ public function sek_dyn_css_maybe_write_css_file() { global $wp_filesystem; $error = false; $base_uri = $this->base_uri; // Can we create the folder? if ( !$wp_filesystem->is_dir( $base_uri ) ) { $error = !wp_mkdir_p( $base_uri ); } if ( $error ) { return false; } if ( !file_exists( $index_path = wp_normalize_path( trailingslashit( $base_uri ) . 'index.php' ) ) ) { // predefined mode settings for WP files // Make sure NB uses the proper FS_CHMOD_DIR value // fixes https://github.com/presscustomizr/nimble-builder/issues/862 // doc : https://wordpress.org/support/article/editing-wp-config-php/#override-of-default-file-permissions // doc : https://wordpress.stackexchange.com/questions/253274/use-of-undefined-constant-fs-chmod-dir-assumed-fs-chmod-dir $chmod_dir = ( 0755 & ~ umask() ); if ( defined( 'FS_CHMOD_DIR' ) ) { $chmod_dir = FS_CHMOD_DIR; } $wp_filesystem->put_contents( $index_path, "file_exists = $wp_filesystem->put_contents( $this->uri, $this->css_string_to_enqueue_or_print,//secured earlier with strip_tags() // predefined mode settings for WP files FS_CHMOD_FILE ); //return whether or not the writing succeeded return $this->file_exists; } /** * * Maybe remove the CSS file from the disk, if it exists and if empty * Note : July 2020 => function updated for https://github.com/presscustomizr/nimble-builder/issues/727 * * @return bool TRUE if the CSS file has been deleted (or didn't exist already), FALSE otherwise */ public function sek_dyn_css_delete_file_if_empty() { global $wp_filesystem; if ( $this->_sek_dyn_css_file_exists_and_is_empty() ) { $this->file_exists != $wp_filesystem->delete( $this->uri ); return !$this->file_exists; } return !$this->file_exists; } /** * * Remove the CSS file from the disk, if it exists, and even if not empty * Note : July 2020 => function updated for https://github.com/presscustomizr/nimble-builder/issues/727 * * @return bool TRUE if the CSS file has been deleted (or didn't exist already), FALSE otherwise */ public function sek_dyn_css_delete_file() { global $wp_filesystem; if ( $this->file_exists ) { $this->file_exists != $wp_filesystem->delete( $this->uri ); //sek_error_log('CSS HANDLER => REMOVE FILE => ' . $this->uri); return !$this->file_exists; } return !$this->file_exists; } /* * Private helpers */ /** * * Retrieve the actual CSS file existence on the file system * * @access private * * @return bool TRUE if the CSS file exists, FALSE otherwise */ private function _sek_dyn_css_file_exists_is_readable_and_has_content() { global $wp_filesystem; if ( $wp_filesystem->exists( $this->uri ) ) { $file_content = $wp_filesystem->get_contents( $this->uri ); return $wp_filesystem->is_readable( $this->uri ) && !empty( $file_content ); } else { return false; } } // Note : July 2020 => function introduced for https://github.com/presscustomizr/nimble-builder/issues/727 private function _sek_dyn_css_file_exists_and_is_empty() { global $wp_filesystem; if ( $wp_filesystem->exists( $this->uri ) ) { $file_content = $wp_filesystem->get_contents( $this->uri ); return empty( $file_content ); } else { return false; } } /** * * Build normalized URI of the CSS file * * @access private * * @return string The absolute CSS file URI */ private function _sek_dyn_css_build_uri() { if ( !isset( $this->base_uri ) ) { $this->_sek_dyn_css_build_base_uri(); } //sek_error_log('///////////////////ALORS CSS FILE NAME ??', $this->id ); return wp_normalize_path( trailingslashit( $this->base_uri ) . "{$this->id}.css" ); } /** * * Build the URL of the CSS file * * @access private * * @return string The absolute CSS file URL */ private function _sek_dyn_css_build_url() { if ( !isset( $this->base_url ) ) { $this->_sek_dyn_css_build_base_url(); } return trailingslashit( $this->base_url ) . "{$this->id}.css"; } /** * * Build the URI of the CSS base directory * * @access private * * @return string The absolute CSS base directory URI */ private function _sek_dyn_css_build_base_uri() { //since 4.5.0 $upload_dir = wp_get_upload_dir(); $relative_base_path = isset( $this->relative_base_path ) ? $this->relative_base_path : $this->_sek_dyn_css_build_relative_base_path(); return wp_normalize_path( trailingslashit( $upload_dir['basedir'] ) . $relative_base_path ); } /** * * Build the URL of the CSS base directory * * @access private * * @return string The absolute CSS base directory URL */ private function _sek_dyn_css_build_base_url() { //since 4.5.0 $upload_dir = wp_get_upload_dir(); $relative_base_path = isset( $this->relative_base_path ) ? $this->relative_base_path : $this->_sek_dyn_css_build_relative_base_path(); return set_url_scheme( trailingslashit( $upload_dir['baseurl'] ) . $relative_base_path ); } /** * * Retrieve the relative path (to the 'uploads' dir ) of the CSS base directory * July 2020 => added a $base_dir param for #727 * * @access private * * @return string The relative path (to the 'uploads' dir) of the CSS base directory */ private function _sek_dyn_css_build_relative_base_path( $base_dir = null ) { $css_base_dir = is_null($base_dir) ? self::CSS_BASE_DIR : $base_dir; if ( is_multisite() ) { $site = get_site(); $network_id = $site->site_id; $site_id = $site->blog_id; $css_dir = trailingslashit( $css_base_dir ) . trailingslashit( $network_id ) . $site_id; } return $css_base_dir; } /** * * Checks whether or not we can write to the disk * * @access private * * @return bool Whether or not we have filesystem credentials */ //TODO: try to extend this to other methods e.g. FTP when FTP credentials are already defined private function _sek_dyn_css_write_file_is_possible() { $upload_dir = wp_get_upload_dir(); //Note: if the 'uploads' dir has not been created, this check will not pass, hence no file will never be created //unless something else creates the 'uploads' dir if ( 'direct' === get_filesystem_method( array(), $upload_dir['basedir'] ) ) { $creds = request_filesystem_credentials( '', '', false, false, array() ); /* initialize the API */ if ( !WP_Filesystem($creds) ) { /* any problems and we exit */ return false; } return true; } return false; } /** * * Simple helper to require the WordPress filesystem relevant file * * @access private */ private function _sek_dyn_css_require_wp_filesystem() { global $wp_filesystem; // Initialize the WordPress filesystem. if ( empty( $wp_filesystem ) ) { require_once( ABSPATH . '/wp-admin/includes/file.php' ); WP_Filesystem(); } } } ?>parent_level ); // the rules are filtered if ( false !== strpos( $input_id_candidate, '_css') ) // Example of input id candidate filtered : 'h_alignment_css' // @see function sek_loop_on_input_candidates_and_maybe_generate_css_rules( $params ) {} add_filter( "sek_add_css_rules_for_input_id", '\Nimble\sek_add_css_rules_for_css_sniffed_input_id', 10, 2 ); //@param $params = array() //@param $rules <= the in-progress global array of css rules to be populated function sek_add_css_rules_for_css_sniffed_input_id( $rules, $params ) { // normalize params $default_params = array( 'css_val' => '',//string or array(), //<= the css property value 'input_id' => '',//string// <= the unique input_id as it as been declared on module registration 'registered_input_list' => array(),// <= the full list of input for the module 'parent_module_level' => array(),// <= the parent level. name is misleading because can be module but also other levels array( 'location', 'section', 'column', 'module' ) 'module_css_selector' => '',//<= a default set of css_selectors might have been specified on module registration 'is_multi_items' => false,// <= for multi-item modules, the input selectors will be made specific for each item-id. In module templates, we'll use data-sek-item-id="%5$s" 'item_id' => '' // <= a multi-item module has a unique id for each item ); $params = wp_parse_args( $params, $default_params ); // map variables $value = $params['css_val']; $input_id = $params['input_id']; $registered_input_list = $params['registered_input_list']; $parent_level = $params['parent_module_level']; $module_level_css_selectors = $params['module_css_selector']; $is_multi_items = $params['is_multi_items']; $item_id = $params['item_id']; if ( !is_string( $input_id ) || empty( $input_id ) ) { sek_error_log( __FUNCTION__ . ' => missing input_id', $parent_level); return $rules; } if ( !is_array( $registered_input_list ) || empty( $registered_input_list ) ) { sek_error_log( __FUNCTION__ . ' => missing input_list', $parent_level); return $rules; } if ( empty( $registered_input_list[ $input_id ] ) ) { sek_error_log( __FUNCTION__ . ' => missing input_id : ' . $input_id, $parent_level); return $rules; } $input_registration_params = $registered_input_list[ $input_id ]; if ( !is_string( $input_registration_params['css_identifier'] ) || empty( $input_registration_params['css_identifier'] ) ) { sek_error_log( __FUNCTION__ . ' => missing css_identifier for parent level', $parent_level ); sek_error_log('$registered_input_list', $registered_input_list ); return $rules; } // Make sure we have the right specificity depending on the level type // When styling a section, the specificity has to be > to the global option one // This is important in particular to make sure that text CSS rules follow the rule : Global < Section < Module if ( 'section' === $parent_level['level'] ) { $selector = sprintf( '.nb-loc [data-sek-id="%1$s"] [data-sek-level]', $parent_level['id'] ); } else { // for modules and columns $selector = sprintf( '.nb-loc .sek-row [data-sek-id="%1$s"]', $parent_level['id'] ); } // for multi-items module, each item has a unique id allowing us to identify it // implemented to allow CSS rules to be generated on a per-item basis // for https://github.com/presscustomizr/nimble-builder/issues/78 if ( $is_multi_items ) { $selector = sprintf( '.nb-loc [data-sek-id="%1$s"] [data-sek-item-id="%2$s"]', $parent_level['id'], $item_id ); } $css_identifier = $input_registration_params['css_identifier']; // SPECIFIC CSS SELECTOR AT MODULE LEVEL // are there more specific css selectors specified on module registration ? if ( !is_null( $module_level_css_selectors ) && !empty( $module_level_css_selectors ) ) { if ( is_array( $module_level_css_selectors ) ) { $new_selectors = array(); foreach ( $module_level_css_selectors as $spec_selector ) { $new_selectors[] = $selector . ' ' . $spec_selector; } $new_selectors = implode(',', $new_selectors ); $selector = $new_selectors; } else if ( is_string( $module_level_css_selectors ) ) { $selector .= ' ' . $module_level_css_selectors; } } // for a module level, increase the default specifity to the .sek-module-inner container by default // @fixes https://github.com/presscustomizr/nimble-builder/issues/85 else if ( 'module' === $parent_level['level'] ) { $selector .= ' .sek-module-inner'; } // SPECIFIC CSS SELECTOR AT INPUT LEVEL // => Overrides the module level specific selector, if it was set. if ( 'module' === $parent_level['level'] ) { //$start = microtime(true) * 1000; if ( !is_array( $registered_input_list ) || empty( $registered_input_list ) ) { sek_error_log( __FUNCTION__ . ' => missing input list' ); } else if ( is_array( $registered_input_list ) && empty( $registered_input_list[ $input_id ] ) ) { sek_error_log( __FUNCTION__ . ' => missing input id ' . $input_id . ' in input list for module type ' . $parent_level['module_type'] ); } if ( is_array( $registered_input_list ) && !empty( $registered_input_list[ $input_id ] ) && !empty( $registered_input_list[ $input_id ]['css_selectors'] ) ) { // reset the selector to the level id selector, in case it was previously set specifically at the module level $selector = '.nb-loc .sek-row [data-sek-id="'.$parent_level['id'].'"]'; if ( $is_multi_items ) { $selector = sprintf( '[data-sek-id="%1$s"] [data-sek-item-id="%2$s"]', $parent_level['id'], $item_id ); } $input_level_css_selectors = $registered_input_list[ $input_id ]['css_selectors']; $new_selectors = array(); if ( is_array( $input_level_css_selectors ) ) { foreach ( $input_level_css_selectors as $spec_selector ) { $new_selectors[] = $selector . ' ' . $spec_selector; } } else if ( is_string( $input_level_css_selectors ) ) { $new_selectors[] = $selector . ' ' . $input_level_css_selectors; } $new_selectors = implode(',', $new_selectors ); $selector = $new_selectors; //sek_error_log( '$input_level_css_selectors', $selector ); } // sek_error_log( 'input_id', $input_id ); // sek_error_log( '$registered_input_list', $registered_input_list ); // $end = microtime(true) * 1000; // $time_elapsed_secs = $end - $start; // sek_error_log('$time_elapsed_secs to get module params', $time_elapsed_secs ); } $mq = null; $properties_to_render = array(); switch ( $css_identifier ) { // 2 cases for the font_size // 1) we save it as a string : '16px' // 2) we save it as an array of strings by devices : [ 'desktop' => '55px', 'tablet' => '40px', 'mobile' => '36px'] case 'font_size' : if ( is_string( $value ) ) { // <= simple $numeric = sek_extract_numeric_value($value); if ( !empty( $numeric ) ) { $properties_to_render['font-size'] = $value; } } else if ( is_array( $value ) ) { // <= by device $important = false; if ( 'module' === $parent_level['level'] && !empty( $parent_level['value'] ) ) { $important = sek_is_flagged_important( $input_id, $parent_level['value'], $registered_input_list ); } $value = wp_parse_args( $value, array( 'desktop' => '16px', 'tablet' => '', 'mobile' => '' )); $rules = sek_set_mq_css_rules( array( 'value' => $value, 'css_property' => 'font-size', 'selector' => $selector, 'is_important' => $important, 'level_id' => $parent_level['id'] ), $rules ); } break; case 'line_height' : $properties_to_render['line-height'] = $value; break; case 'font_weight' : $properties_to_render['font-weight'] = $value; break; case 'font_style' : $properties_to_render['font-style'] = $value; break; case 'text_decoration' : $properties_to_render['text-decoration'] = $value; break; case 'text_transform' : $properties_to_render['text-transform'] = $value; break; case 'letter_spacing' : $properties_to_render['letter-spacing'] = $value . 'px'; break; case 'color' : $properties_to_render['color'] = $value; break; case 'color_hover' : //$selector = '[data-sek-id="'.$parent_level['id'].'"]:hover'; // Add ':hover to each selectors' $new_selectors = array(); $exploded = explode(',', $selector); foreach ( $exploded as $sel ) { $new_selectors[] = $sel.':hover'; $new_selectors[] = $sel.':focus'; } $selector = implode(',', $new_selectors); $properties_to_render['color'] = $value; break; case 'background_color' : $properties_to_render['background-color'] = $value; break; case 'h_flex_alignment' : $important = false; if ( 'module' === $parent_level['level'] && !empty( $parent_level['value'] ) ) { $important = sek_is_flagged_important( $input_id, $parent_level['value'], $registered_input_list ); } // convert to flex $flex_ready_value = array(); foreach( $value as $device => $val ) { switch ( $val ) { case 'left' : $h_align_value = sprintf('justify-content:flex-start%1$s;-webkit-box-pack:start%1$s;-ms-flex-pack:start%1$s;', $important ? '!important' : '' ); break; case 'center' : $h_align_value = sprintf('justify-content:center%1$s;-webkit-box-pack:center%1$s;-ms-flex-pack:center%1$s;', $important ? '!important' : '' ); break; case 'right' : $h_align_value = sprintf('justify-content:flex-end%1$s;-webkit-box-pack:end%1$s;-ms-flex-pack:end%1$s;', $important ? '!important' : '' ); break; default : $h_align_value = sprintf('justify-content:center%1$s;-webkit-box-pack:center%1$s;-ms-flex-pack:center%1$s;', $important ? '!important' : '' ); break; } $flex_ready_value[$device] = $h_align_value; } $flex_ready_value = wp_parse_args( $flex_ready_value, array( 'desktop' => '', 'tablet' => '', 'mobile' => '' )); $rules = sek_set_mq_css_rules_supporting_vendor_prefixes( array( 'css_rules_by_device' => $flex_ready_value, 'selector' => $selector, 'level_id' => $parent_level['id'] ), $rules ); break; // handles simple or by device option case 'h_alignment' : if ( is_string( $value ) ) {// <= simple $properties_to_render['text-align'] = $value; } else if ( is_array( $value ) ) {// <= by device $important = false; if ( 'module' === $parent_level['level'] && !empty( $parent_level['value'] ) ) { $important = sek_is_flagged_important( $input_id, $parent_level['value'], $registered_input_list ); } $value = wp_parse_args( $value, array( 'desktop' => '', 'tablet' => '', 'mobile' => '' )); $rules = sek_set_mq_css_rules( array( 'value' => $value, 'css_property' => 'text-align', 'selector' => $selector, 'is_important' => $important, 'level_id' => $parent_level['id'], ), $rules ); } break; // -webkit-box-align:end; // -ms-flex-align:end; // align-items:flex-end; case 'v_alignment' : switch ( $value ) { case 'top' : $v_align_value = "flex-start"; $v_vendor_value = "start"; break; case 'center' : $v_align_value = "center"; $v_vendor_value = "center"; break; case 'bottom' : $v_align_value = "flex-end"; $v_vendor_value = "end"; break; default : $v_align_value = "center"; $v_vendor_value = "center"; break; } $properties_to_render['align-items'] = $v_align_value; $properties_to_render['-webkit-box-align'] = $v_vendor_value; $properties_to_render['-ms-flex-align'] = $v_vendor_value; break; case 'font_family' : $ffamily = sek_extract_css_font_family_from_customizer_option( $value ); if ( !empty( $ffamily ) ) { $properties_to_render['font-family'] = $ffamily; } break; /* Spacer */ // The unit should be included in the $value case 'height' : if ( is_string( $value ) ) { // <= simple $numeric = sek_extract_numeric_value($value); if ( !empty( $numeric ) ) { $unit = sek_extract_unit( $value ); $unit = '%' === $unit ? 'vh' : $unit; $properties_to_render['height'] = $numeric . $unit; } } else if ( is_array( $value ) ) { // <= by device $important = false; if ( 'module' === $parent_level['level'] && !empty( $parent_level['value'] ) ) { $important = sek_is_flagged_important( $input_id, $parent_level['value'], $registered_input_list ); } $value = wp_parse_args( $value, array( 'desktop' => '20px', 'tablet' => '', 'mobile' => '' )); // replace % by vh when needed $ready_value = $value; foreach ($value as $device => $num_unit ) { $numeric = sek_extract_numeric_value( $num_unit ); if ( !empty( $numeric ) ) { $unit = sek_extract_unit( $num_unit ); $unit = '%' === $unit ? 'vh' : $unit; $ready_value[$device] = $numeric . $unit; } } $rules = sek_set_mq_css_rules(array( 'value' => $ready_value, 'css_property' => 'height', 'selector' => $selector, 'is_important' => $important, 'level_id' => $parent_level['id'] ), $rules ); } break; /* Quote border */ case 'border_width' : $numeric = sek_extract_numeric_value( $value ); if ( 0 === intval($numeric) || !empty( $numeric ) ) { $unit = sek_extract_unit( $value ); $properties_to_render['border-width'] = $numeric . $unit; } break; case 'border_color' : $properties_to_render['border-color'] = $value ? $value : ''; break; /* Divider */ case 'border_top_width' : $numeric = sek_extract_numeric_value( $value ); if ( !empty( $numeric ) ) { $unit = sek_extract_unit( $value ); $unit = '%' === $unit ? 'vh' : $unit; $properties_to_render['border-top-width'] = $numeric . $unit; //$properties_to_render['border-top-width'] = $value > 0 ? $value . 'px' : '1px'; } break; case 'border_top_style' : $properties_to_render['border-top-style'] = $value ? $value : 'solid'; break; case 'border_top_color' : $properties_to_render['border-top-color'] = $value ? $value : '#5a5a5a'; break; case 'border_radius' : if ( is_string( $value ) ) { $numeric = sek_extract_numeric_value( $value ); if ( !empty( $numeric ) ) { $unit = sek_extract_unit( $value ); $properties_to_render['border-radius'] = $numeric . $unit; } } else if ( is_array( $value ) ) { $rules = sek_generate_css_rules_for_border_radius_options( $rules, $value, $selector ); } break; case 'width' : if ( is_string( $value ) ) { // <= simple $numeric = sek_extract_numeric_value($value); if ( !empty( $numeric ) ) { $unit = sek_extract_unit( $value ); $properties_to_render['width'] = $numeric . $unit; } } else if ( is_array( $value ) ) { // <= by device $important = false; if ( 'module' === $parent_level['level'] && !empty( $parent_level['value'] ) ) { $important = sek_is_flagged_important( $input_id, $parent_level['value'], $registered_input_list ); } $value = wp_parse_args( $value, array( 'desktop' => '100%', 'tablet' => '', 'mobile' => '' )); // replace % by vh when needed $ready_value = $value; foreach ($value as $device => $num_unit ) { $numeric = sek_extract_numeric_value( $num_unit ); if ( !empty( $numeric ) ) { $unit = sek_extract_unit( $num_unit ); $ready_value[$device] = $numeric . $unit; } } $rules = sek_set_mq_css_rules(array( 'value' => $ready_value, 'css_property' => 'width', 'selector' => $selector, 'is_important' => $important, 'level_id' => $parent_level['id'] ), $rules ); } break; case 'v_spacing' : if ( is_string( $value ) ) { // <= simple $numeric = sek_extract_numeric_value($value); if ( !empty( $numeric ) ) { $unit = sek_extract_unit( $value ); $unit = '%' === $unit ? 'vh' : $unit; $properties_to_render = array( 'margin-top' => $numeric . $unit, 'margin-bottom' => $numeric . $unit ); } } else if ( is_array( $value ) ) { // <= by device $important = false; if ( 'module' === $parent_level['level'] && !empty( $parent_level['value'] ) ) { $important = sek_is_flagged_important( $input_id, $parent_level['value'], $registered_input_list ); } $value = wp_parse_args( $value, array( 'desktop' => '15px', 'tablet' => '', 'mobile' => '' )); // replace % by vh when needed $ready_value = $value; foreach ($value as $device => $num_unit ) { $numeric = sek_extract_numeric_value( $num_unit ); if ( !empty( $numeric ) ) { $unit = sek_extract_unit( $num_unit ); $unit = '%' === $unit ? 'vh' : $unit; $ready_value[$device] = $numeric . $unit; } } $rules = sek_set_mq_css_rules(array( 'value' => $ready_value, 'css_property' => 'margin-top', 'selector' => $selector, 'is_important' => $important, 'level_id' => $parent_level['id'] ), $rules ); $rules = sek_set_mq_css_rules(array( 'value' => $ready_value, 'css_property' => 'margin-bottom', 'selector' => $selector, 'is_important' => $important, 'level_id' => $parent_level['id'] ), $rules ); } break; //not used at the moment, but it might if we want to display the divider as block (e.g. a div instead of a span) case 'h_alignment_block' : switch ( $value ) { case 'right' : $properties_to_render = array( 'margin-right' => '0' ); break; case 'left' : $properties_to_render = array( 'margin-left' => '0' ); break; default : $properties_to_render = array( 'margin-left' => 'auto', 'margin-right' => 'auto' ); } break; case 'padding_margin_spacing' : $default_unit = 'px'; $rules_candidates = $value; //add unit and sanitize padding (cannot have negative padding) $unit = !empty( $rules_candidates['unit'] ) ? $rules_candidates['unit'] : $default_unit; $unit = 'percent' == $unit ? '%' : $unit; $new_filtered_rules = array(); foreach ( $rules_candidates as $k => $v) { if ( 'unit' !== $k ) { $new_filtered_rules[ $k ] = $v; } } $properties_to_render = $new_filtered_rules; array_walk( $properties_to_render, function( &$val, $key, $unit ) { //make sure paddings are positive values if ( FALSE !== strpos( 'padding', $key ) ) { $val = abs( $val ); } $val .= $unit; }, $unit ); break; // May 2020 => @todo those media query css rules doesn't take into account the custom breakpoint if set case 'spacing_with_device_switcher' : if ( !empty( $value ) && is_array( $value ) ) { $rules = sek_generate_css_rules_for_spacing_with_device_switcher( $rules, $value, $selector ); } break; // The default is simply there to let us know if a css_identifier is missing default : sek_error_log( __FUNCTION__ . ' => the css_identifier : ' . $css_identifier . ' has no css rules defined for input id ' . $input_id ); break; }//switch // when the module has an '*_flag_important' input, // => check if the input_id belongs to the list of "important_input_list" // => and maybe flag the css rules with !important if ( !empty( $properties_to_render ) ) { $important = false; if ( 'module' === $parent_level['level'] && !empty( $parent_level['value'] ) ) { $important = sek_is_flagged_important( $input_id, $parent_level['value'], $registered_input_list ); } $css_rules = ''; foreach ( $properties_to_render as $prop => $prop_val ) { $css_rules .= sprintf( '%1$s:%2$s%3$s;', $prop, $prop_val, $important ? '!important' : '' ); }//end foreach $rules[] = array( 'selector' => $selector, 'css_rules' => $css_rules, 'mq' => $mq ); } return $rules; }//sek_add_css_rules_for_css_sniffed_input_id // @return boolean // Recursive // Check if a *_flag_important input id is part of the registered input list of the module // then verify is the provided input_id is part of the list of input that should be set to important => 'important_input_list' // Example of a *_flag_important input: // 'quote___flag_important' => array( // 'input_type' => 'nimblecheck', // 'title' => __( 'Make those style options win if other rules are applied.', 'text_doma' ), // 'default' => 0, // 'refresh_markup' => false, // 'refresh_stylesheet' => true, // 'title_width' => 'width-80', // 'input_width' => 'width-20', // // declare the list of input_id that will be flagged with !important when the option is checked // // @see sek_add_css_rules_for_css_sniffed_input_id // // @see sek_is_flagged_important // 'important_input_list' => array( // 'quote_font_family_css', // 'quote_font_size_css', // 'quote_line_height_css', // 'quote_font_weight_css', // 'quote_font_style_css', // 'quote_text_decoration_css', // 'quote_text_transform_css', // 'quote_letter_spacing_css', // 'quote_color_css', // 'quote_color_hover_css' // ) // ) function sek_is_flagged_important( $input_id, $module_value, $registered_input_list ) { $important = false; if ( !is_array( $registered_input_list ) || empty( $registered_input_list ) ) { sek_error_log( __FUNCTION__ . ' => error => the $registered_input_list param should be an array not empty'); return $important; } // loop on the registered input list and try to find a *_flag_important input id foreach ( $registered_input_list as $_id => $input_data ) { if ( is_string( $_id ) && false !== strpos( $_id, '_flag_important' ) ) { if ( empty( $input_data[ 'important_input_list' ] ) ) { sek_error_log( __FUNCTION__ . ' => error => missing important_input_list for input id ' . $_id ); } else { $important_list_candidate = $input_data[ 'important_input_list' ]; if ( in_array( $input_id, $important_list_candidate ) ) { $important = sek_booleanize_checkbox_val( sek_get_input_value_in_module_model( $_id, $module_value ) ); } } } } return $important; } ?> array( 'priority' => 10 ), 'before_content' => array(), 'after_content' => array(), 'loop_end' => array( 'priority' => 10 ), ]; public $registered_locations = []; // the model used to register a location public $default_registered_location_model = [ 'priority' => 10, 'is_global_location' => false, 'is_header_location' => false, 'is_footer_location' => false ]; // the model used when saving a location in db public $default_location_model = [ 'id' => '', 'level' => 'location', 'collection' => [], 'options' => [], 'ver_ini' => NIMBLE_VERSION ]; public $rendered_levels = [];//<= stores the ids of the level rendered with ::render() public static function get_instance( $params ) { if ( !isset( self::$instance ) && !( self::$instance instanceof Sek_Nimble_Manager ) ) { self::$instance = new Sek_Nimble_Manager( $params ); // this hook is used to add_action( 'nimble_front_classes_ready', array( $this, 'sek_register_nimble_global_locations') ); do_action( 'nimble_front_classes_ready', self::$instance ); } return self::$instance; } // store the local and global options public $local_options = '_not_cached_yet_'; public $local_options_without_tmpl_inheritance = '_not_cached_yet_';//Introduced for site templates, when using function sek_is_inheritance_locally_disabled() public $global_nimble_options = '_not_cached_yet_'; public $img_smartload_enabled = 'not_cached'; public $video_bg_lazyload_enabled = 'not_cached';//<= for https://github.com/presscustomizr/nimble-builder/issues/287 public $has_local_header_footer = '_not_cached_yet_';//used in sek_maybe_set_local_nimble_header() and sek_maybe_set_local_nimble_footer() public $has_global_header_footer = '_not_cached_yet_';//used in sek_maybe_set_local_nimble_header() and sek_maybe_set_local_nimble_footer() public $recaptcha_enabled = '_not_cached_yet_';//enabled in the global options public $recaptcha_badge_displayed = '_not_cached_yet_';//enabled in the global options // option key as saved in db => module_type // is used in _1_6_5_sektions_generate_UI_global_options.js and when normalizing the global option in sek_normalize_global_options_with_defaults() public static $global_options_map = [ 'global_header_footer' => 'sek_global_header_footer', 'global_text' => 'sek_global_text', 'site_templates' => 'sek_site_tmpl_pickers', 'widths' => 'sek_global_widths', 'breakpoint' => 'sek_global_breakpoint', 'performances' => 'sek_global_performances', 'recaptcha' => 'sek_global_recaptcha', 'global_revisions' => 'sek_global_revisions', 'global_reset' => 'sek_global_reset', 'global_imp_exp' => 'sek_global_imp_exp', 'beta_features' => 'sek_global_beta_features'// may 2021 not rendered anymore in ::controls customizer ]; // option key as saved in db => module_type // is used in _1_6_4_sektions_generate_UI_local_skope_options.js and when normalizing the global option in sek_normalize_local_options_with_defaults() public static $local_options_map = [ 'template' => 'sek_local_template', 'local_header_footer' => 'sek_local_header_footer', 'widths' => 'sek_local_widths', 'custom_css' => 'sek_local_custom_css', 'local_performances' => 'sek_local_performances', 'local_reset' => 'sek_local_reset', 'import_export' => 'sek_local_imp_exp', 'local_revisions' => 'sek_local_revisions' ]; // introduced when implementing import/export feature // @see https://github.com/presscustomizr/nimble-builder/issues/411 public $img_import_errors = []; // stores the active module collection // @see populated in sek_populate_collection_of_contextually_active_modules() // list of modules displayed on local + global sektions for a givent page. // populated 'wp'@PHP_INT_MAX and used to // 1) determine which module should be registered when not customizing or ajaxing. See sek_register_modules_when_not_customizing_and_not_ajaxing() // 2) determine which assets ( css / js ) is needed for this context. see ::sek_enqueue_front_assets // // updated for https://github.com/presscustomizr/nimble-builder/issues/612 public $contextually_active_modules = 'not_set'; public static $ui_picker_modules = [ // UI CONTENT PICKER 'sek_content_type_switcher_module', 'sek_module_picker_module' ]; // JUNE 2020 // PREBUILT AND USER SECTION MODULES ARE REGISTERED IN add_action( 'after_setup_theme', '\Nimble\sek_schedule_module_registration', 50 ); // with sek_register_prebuilt_section_modules(); and sek_register_user_sections_module(); public static $ui_level_modules = [ // UI LEVEL MODULES 'sek_mod_option_switcher_module', 'sek_level_bg_module', 'sek_level_text_module', 'sek_level_border_module', //'sek_level_section_layout_module', 'quote-module', 'czr_icon_module' => 'icon-module', 'czr_img_slider_module' => 'img-slider-module', 'czr_accordion_module' => 'accordion-module', 'czr_menu_module' => 'menu-module', 'czr_post_grid_module' => 'post-grid-module', 'czr_simple_form_module' => 'simple-form-module', 'czr_image_module' => 'image-module', 'czr_special_img_module' => 'special-image-module', 'czr_advanced_list_module' => 'advanced-list-module', 'czr_social_icons_module' => 'social-icons-module', 'czr_button_module' => 'button-module', 'czr_heading_module' => 'heading-module', 'czr_gallery_module' => 'gallery-module', ]; // March 2020, for https://github.com/presscustomizr/nimble-builder/issues/629 public $google_fonts_print_candidates = 'not_set';// will cache the google font candidates to print in ::_setup_hook_for_front_css_printing_or_enqueuing() public $css_loader_html = '
'; // March 2020, for https://github.com/presscustomizr/nimble-builder/issues/649 public $nimble_customizing_or_content_is_printed_on_this_page = false;//<= tells if any Nimble Content has been printed. // October 2020 public $page_has_local_or_global_sections = 'not_set';//<= set @wp_enqueue_script, used to determine if we should load css, js and fonts assets or not. // feb 2021, introduced for #478 public $page_has_local_sections = 'not_set'; public $page_has_global_sections = 'not_set'; // April 2020 for https://github.com/presscustomizr/nimble-builder/issues/679 public $is_content_restricted = false; //<= set at 'wp' // May 2020 // those location properties are set when walking Nimble content on rendering // @see #705 prevent lazyloading images when in header section. public $current_location_is_header = false; public $current_location_is_footer = false; // September 2020 for https://github.com/presscustomizr/nimble-builder-pro/issues/67 public $local_levels_custom_css = ''; public $global_levels_custom_css = ''; // October 2020 public $rendering = false;//<= set to true when rendering NB content // October 2020 public $emitted_js_event = [];//<= collection of unique js event emitted with a script like nb_.emit('nb-needs-parallax') // October 2020, for https://github.com/presscustomizr/nimble-builder/issues/751 public $partial_front_scripts = [ 'slider-module' => 'nb-needs-swiper', 'menu-module' => 'nb-needs-menu-js', 'front-parallax' => 'nb-needs-parallax', 'accordion-module' => 'nb-needs-accordion' ]; // janv 2021 => will populate the modules stylesheets already concatenated, so that NB doesn't concatenate a module stylesheet twice for the local css and for the global css (if any) // see in inc\sektions\_front_dev_php\dyn_css_builder_and_google_fonts_printer\5_0_1_class-sek-dyn-css-builder.php public $concatenated_module_stylesheets = []; // April 2021 => added some properties when implementing late escape for attributes // @see ::render() and base-tmpl PHP files public $level_css_classes; public $level_custom_anchor; public $level_custom_attr; ///////////////////////////////////////////////////////////////// // function __construct( $params = array() ) { if ( did_action('nimble_manager_ready') ) return; // INITIALIZE THE REGISTERED LOCATIONS WITH THE DEFAULT LOCATIONS $this->registered_locations = $this->default_locations; // AJAX $this->_schedule_front_ajax_actions(); // FRONT ASSETS $this->_schedule_front_assets_printing(); // CUSTOOMIZER PREVIEW ASSETS $this->_schedule_preview_assets_printing(); // RENDERING $this->_schedule_front_rendering(); // RENDERING $this->_setup_hook_for_front_css_printing_or_enqueuing(); // LOADS SIMPLE FORM $this->_setup_simple_forms(); // REGISTER NIMBLE WIDGET ZONES add_action( 'widgets_init', array( $this, 'sek_nimble_widgets_init' ) ); do_action('nimble_manager_ready'); // MAYBE REGISTER PRO UPSELL MODUlES add_filter('nb_level_module_collection', function( $module_collection ) { if ( is_array($module_collection) && ( sek_is_pro() || sek_is_upsell_enabled() ) ) { array_push($module_collection, 'sek_level_cust_css_level' ); array_push($module_collection, 'sek_level_animation_module' ); } return $module_collection; }); // see #838 // prevents using persistent cache object systems like Memcached which override the default WP class WP_Object_Cache () which is normally refreshed on each page load ) add_action('init', array( $this, 'sek_clear_cached_objects_when_customizing') ); // FLUSH CACHE OBJECT ON POST SAVE / UPDATE // for https://github.com/presscustomizr/nimble-builder/issues/867 add_action( 'save_post', array( $this, 'sek_flush_object_cache_on_post_update') ); }//__construct // @init public function sek_clear_cached_objects_when_customizing() { if ( skp_is_customizing() ) { // Make sure cached objects are cleaned wp_cache_flush(); } } // @save_post function sek_flush_object_cache_on_post_update() { wp_cache_flush(); } // @fired @hook 'widgets_init' // Creates 10 widget zones public function sek_nimble_widgets_init() { if ( sek_is_widget_module_disabled() ) return; $number_of_widgets = apply_filters( 'nimble_number_of_wp_widgets', 10 ); // Header/footer, widgets module, menu module have been beta tested during 5 months and released in June 2019, in version 1.8.0 $defaults = array( 'name' => '', 'id' => '', 'description' => '', 'class' => '', 'before_widget' => '', 'before_title' => '

', 'after_title' => '

', ); for ( $i=1; $i < ( intval( $number_of_widgets) + 1 ); $i++ ) { $args['id'] = NIMBLE_WIDGET_PREFIX . $i;//'nimble-widget-area-' $args['name'] = sprintf( __('Nimble widget area #%1$s', 'text_domain_to_replace' ), $i ); $args['description'] = $args['name']; $args = wp_parse_args( $args, $defaults ); register_sidebar( $args ); } } // Invoked @'after_setup_theme' static function sek_get_front_module_collection() { $front_module_collection = [ // FRONT MODULES 'czr_simple_html_module', 'czr_tiny_mce_editor_module' => array( 'czr_tiny_mce_editor_module', 'czr_tinymce_child', 'czr_font_child' ), 'czr_image_module' => array( 'czr_image_module', 'czr_image_main_settings_child', 'czr_image_borders_corners_child' ), 'czr_heading_module' => array( 'czr_heading_module', 'czr_heading_child', 'czr_heading_spacing_child', 'czr_font_child' ), 'czr_spacer_module', 'czr_divider_module', 'czr_icon_module' => array( 'czr_icon_module', 'czr_icon_settings_child', 'czr_icon_spacing_border_child', ), 'czr_map_module', 'czr_quote_module' => array( 'czr_quote_module', 'czr_quote_quote_child', 'czr_quote_cite_child', 'czr_quote_design_child', ), 'czr_button_module' => array( 'czr_button_module', 'czr_btn_content_child', 'czr_btn_design_child', 'czr_font_child' ), // simple form father + children 'czr_simple_form_module' => array( 'czr_simple_form_module', 'czr_simple_form_fields_child', 'czr_simple_form_button_child', 'czr_simple_form_design_child', 'czr_simple_form_fonts_child', 'czr_simple_form_submission_child' ), 'czr_post_grid_module' => array( 'czr_post_grid_module', 'czr_post_grid_main_child', 'czr_post_grid_thumb_child', 'czr_post_grid_metas_child', 'czr_post_grid_fonts_child' ), // widgets module, menu module have been beta tested during 5 months and released in June 2019, in version 1.8.0 'czr_menu_module' => array( 'czr_menu_module', 'czr_menu_content_child', 'czr_menu_mobile_options', 'czr_font_child' ), //'czr_menu_design_child', 'czr_social_icons_module' => array( 'czr_social_icons_module', 'czr_social_icons_settings_child', 'czr_social_icons_style_child' ), 'czr_img_slider_module' => array( 'czr_img_slider_module', 'czr_img_slider_collection_child', 'czr_img_slider_opts_child' ), 'czr_accordion_module' => array( 'czr_accordion_module', 'czr_accordion_collection_child', 'czr_accordion_opts_child' ), 'czr_gallery_module' => array( 'czr_gallery_module', 'czr_gallery_collection_child', 'czr_gallery_opts_child' ), 'czr_shortcode_module', ]; if ( !sek_is_widget_module_disabled() ) { $front_module_collection[] = 'czr_widget_area_module'; } return apply_filters( 'sek_get_front_module_collection', $front_module_collection ); } }//class endif; ?>ajax_action_map = array( 'sek-add-section', 'sek-remove-section', 'sek-duplicate-section', // fired when dropping a module or a preset_section 'sek-add-content-in-new-nested-sektion', 'sek-add-content-in-new-sektion', // add, duplicate, remove column is a re-rendering of the parent sektion collection 'sek-add-column', 'sek-remove-column', 'sek-duplicate-column', 'sek-resize-columns', 'sek-refresh-columns-in-sektion', 'sek-add-module', 'sek-remove-module', 'sek-duplicate-module', 'sek-refresh-modules-in-column', 'sek-refresh-stylesheet', 'sek-refresh-level' ); } //////////////////////////////////////////////////////////////// // GENERIC HELPER FIRED IN ALL AJAX CALLBACKS // @param $params = array('check_nonce' => true ) function sek_do_ajax_pre_checks( $params = array() ) { $params = wp_parse_args( $params, array( 'check_nonce' => true ) ); if ( $params['check_nonce'] ) { $action = 'save-customize_' . get_stylesheet(); if ( !check_ajax_referer( $action, 'nonce', false ) ) { wp_send_json_error( array( 'code' => 'invalid_nonce', 'message' => __( __CLASS__ . '::' . __FUNCTION__ . ' => check_ajax_referer() failed.' ), ) ); } } if ( !is_user_logged_in() ) { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' => unauthenticated' ); } if ( !current_user_can( 'customize' ) ) { status_header( 403 ); wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' => customize_not_allowed' ); } else if ( !isset( $_SERVER['REQUEST_METHOD'] ) || 'POST' !== $_SERVER['REQUEST_METHOD'] ) { status_header( 405 ); wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' => bad_method' ); } }//sek_do_ajax_pre_checks() // hook : 'wp_ajax_sek_get_html_for_injection' function sek_get_level_content_for_injection( $params ) { $this->sek_do_ajax_pre_checks( array( 'check_nonce' => false ) ); if ( !isset( $_POST['location_skope_id'] ) || empty( $_POST['location_skope_id'] ) ) { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' => missing skope_id' ); } // introduced for https://github.com/presscustomizr/nimble-builder/issues/494 // september 2019 // this guid is used to differentiate dynamically rendered content from static content that may include a Nimble generated HTML structure // an attribute "data-sek-preview-level-guid" is added to each rendered level when customizing or ajaxing // otherwise the preview UI can be broken if ( !isset( $_POST['preview-level-guid'] ) || empty( $_POST['preview-level-guid'] ) ) { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' => missing preview-level-guid' ); } if ( !isset( $_POST['sek_action'] ) || empty( $_POST['sek_action'] ) ) { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' => missing sek_action' ); } $sek_action = sanitize_text_field($_POST['sek_action']); $exported_setting_validities = array(); // CHECK THE SETTING VALIDITIES BEFORE RENDERING // When a module has been registered with a sanitize_callback, we can collect the possible problems here before sending the response. // Then, on ajax.done(), in SekPreviewPrototype::schedulePanelMsgReactions, we will send the setting validities object to the panel if ( is_customize_preview() ) { global $wp_customize; // prepare the setting validities so we can pass them when sending the ajax response $setting_validities = $wp_customize->validate_setting_values( $wp_customize->unsanitized_post_values() ); $raw_exported_setting_validities = array_map( array( $wp_customize, 'prepare_setting_validity_for_js' ), $setting_validities ); // filter the setting validity to only keep the __nimble__ prefixed ui settings $exported_setting_validities = array(); foreach( $raw_exported_setting_validities as $setting_id => $validity ) { // don't consider the not Nimble UI settings, not starting with __nimble__ if ( false === strpos( $setting_id , NIMBLE_PREFIX_FOR_SETTING_NOT_SAVED ) ) continue; $exported_setting_validities[ $setting_id ] = $validity; } } $html = ''; // is this action possible ? if ( in_array( $sek_action, $this->ajax_action_map ) ) { $content_type = null; if ( array_key_exists( 'content_type', $_POST ) && is_string( $_POST['content_type'] ) ) { $content_type = sanitize_text_field($_POST['content_type']); } // This 'preset_section' === $content_type statement has been introduced when implementing support for multi-section pre-build sections // @see https://github.com/presscustomizr/nimble-builder/issues/489 if ( 'preset_section' === $content_type ) { switch ( $sek_action ) { // when 'sek-add-content-in-new-sektion' is fired, the section has already been populated with a column and a module case 'sek-add-content-in-new-sektion' : case 'sek-add-content-in-new-nested-sektion' : if ( 'preset_section' === $content_type ) { if ( !array_key_exists( 'collection_of_preset_section_id', $_POST ) || !is_array( $_POST['collection_of_preset_section_id'] ) || empty( $_POST['collection_of_preset_section_id'] ) ) { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' ' . $sek_action .' => missing param collection_of_preset_section_id when injecting a preset section' ); break; } foreach ( $_POST['collection_of_preset_section_id'] as $preset_section_id ) { $html .= $this->sek_ajax_fetch_content( $sek_action, sanitize_text_field($preset_section_id )); } // 'module' === $content_type } else { $html = $this->sek_ajax_fetch_content( $sek_action ); } break; default : $html = $this->sek_ajax_fetch_content( $sek_action ); break; } } else { $html = $this->sek_ajax_fetch_content( $sek_action ); } //sek_error_log(__CLASS__ . '::' . __FUNCTION__ , $html ); if ( is_wp_error( $html ) ) { wp_send_json_error( $html ); } else { $response = array( 'contents' => $html, 'setting_validities' => $exported_setting_validities ); wp_send_json_success( apply_filters( 'sek_content_results', $response, $sek_action ) ); } } else { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' => this ajax action ( ' . $sek_action . ' ) is not listed in the map ' ); } }//sek_get_content_for_injection() // hook : add_filter( "sek_set_ajax_content___{$action}", array( $this, 'sek_ajax_fetch_content' ) ); // $_POST looks like Array // ( // [action] => sek_get_content // [withNonce] => false // [id] => __nimble__0b7c85561448ab4eb8adb978 // [skope_id] => skp__post_page_home // [sek_action] => sek-add-section // [SEKFrontNonce] => 3713b8ac5c // [customized] => {\"nimble___loop_start[skp__post_page_home]\":{...}} // ) // @return string // @param $sek_action is $_POST['sek_action'] // @param $maybe_preset_section_id is used when injecting a collection of preset sections private function sek_ajax_fetch_content( $sek_action = '', $maybe_preset_section_id = '' ) { //sek_error_log( __CLASS__ . '::' . __FUNCTION__ . ' POST ?', $_POST ); // Important Notes : // 1) at this stage => the $_POST['customized'] has already been updated // so invoking sek_get_skoped_seks() will ensure that we get the latest data // How $_POST['customized'] is getting populated without a full refresh of the preview ? // a) Each time the main collection setting id is updated ( @see CZRSeksPrototype::mayBeUpdateSektionsSetting() ), api.Setting.prototype.preview sends a 'setting' event to the preview // ( note that api.Setting.prototype.preview is overriden by NB to send other events ) // b) when the core customize-preview receives the event, it updates the customized dirties // c) then when ajaxing, the $_POST['customized'] param is added by WP core with $.ajaxPrefilter() in customize-preview.js // // 2) since 'wp' hook has not been fired yet, we need to use the posted skope_id param. $sektionSettingValue = sek_get_skoped_seks( sanitize_text_field($_POST['location_skope_id']) ); if ( !is_array( $sektionSettingValue ) ) { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' => invalid sektionSettingValue => it should be an array().' ); return; } if ( empty( $sek_action ) ) { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' => invalid sek_action param' ); return; } $sektion_collection = array_key_exists('collection', $sektionSettingValue) ? $sektionSettingValue['collection'] : array(); if ( !is_array( $sektion_collection ) ) { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' => invalid sektion_collection => it should be an array().' ); return; } $candidate_id = ''; $collection = array(); $level_model = array(); $is_stylesheet = false; switch ( $sek_action ) { case 'sek-add-section' : case 'sek-duplicate-section' : if ( array_key_exists( 'is_nested', $_POST ) && true === json_decode( sanitize_text_field($_POST['is_nested']) ) ) { // we need to set the parent_mode here to access it later in the ::render method to calculate the column width. $this->parent_model = sek_get_level_model( sanitize_text_field($_POST[ 'in_sektion' ]), $sektion_collection ); $level_model = sek_get_level_model( sanitize_text_field($_POST[ 'in_column' ]), $sektion_collection ); } else { //$level_model = sek_get_level_model( $_POST[ 'id' ], $sektion_collection ); $level_model = sek_get_level_model( sanitize_text_field($_POST[ 'id' ]), $sektion_collection ); } break; // This $content_type var has been introduced when implementing support for multi-section pre-build sections // @see https://github.com/presscustomizr/nimble-builder/issues/489 // when 'sek-add-content-in-new-sektion' is fired, the section has already been populated with a column and a module case 'sek-add-content-in-new-sektion' : case 'sek-add-content-in-new-nested-sektion' : $content_type = null; if ( array_key_exists( 'content_type', $_POST ) && is_string( $_POST['content_type'] ) ) { $content_type = sanitize_text_field($_POST['content_type']); } if ( 'preset_section' === $content_type ) { if ( !array_key_exists( 'collection_of_preset_section_id', $_POST ) || !is_array( $_POST['collection_of_preset_section_id'] ) ) { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' ' . $sek_action .' => missing param collection_of_preset_section_id when injecting a preset section' ); break; } if ( !is_string( $maybe_preset_section_id ) || empty( $maybe_preset_section_id ) ) { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' ' . $sek_action .' => invalid preset section id' ); break; } $level_id = $maybe_preset_section_id; // module content type case. // the level id has been passed the regular way } else { $level_id = sanitize_text_field($_POST[ 'id' ]); } if ( array_key_exists( 'is_nested', $_POST ) && true === json_decode( sanitize_text_field($_POST['is_nested']) ) ) { // we need to set the parent_mode here to access it later in the ::render method to calculate the column width. $this->parent_model = sek_get_level_model( sanitize_text_field($_POST[ 'in_sektion' ]), $sektion_collection ); $level_model = sek_get_level_model( sanitize_text_field($_POST[ 'in_column' ]), $sektion_collection ); } else { $level_model = sek_get_level_model( $level_id, $sektion_collection ); } break; //only used for nested section case 'sek-remove-section' : if ( !array_key_exists( 'is_nested', $_POST ) || true !== json_decode( sanitize_text_field($_POST['is_nested'] )) ) { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' ' . $sek_action .' => the section must be nested in this ajax action' ); break; } else { // we need to set the parent_model here to access it later in the ::render method to calculate the column width. $this->parent_model = sek_get_parent_level_model( sanitize_text_field($_POST[ 'in_column' ]), $sektion_collection ); $level_model = sek_get_level_model( sanitize_text_field($_POST[ 'in_column' ]), $sektion_collection ); } break; // We re-render the entire parent sektion collection in all cases case 'sek-add-column' : case 'sek-remove-column' : case 'sek-duplicate-column' : case 'sek-refresh-columns-in-sektion' : if ( !array_key_exists( 'in_sektion', $_POST ) || empty( $_POST['in_sektion'] ) ) { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' ' . $sek_action .' => missing in_sektion param' ); break; } // sek_error_log('sektion_collection', $sektion_collection ); $level_model = sek_get_level_model( sanitize_text_field($_POST[ 'in_sektion' ]), $sektion_collection ); break; // We re-render the entire parent column collection case 'sek-add-module' : case 'sek-remove-module' : case 'sek-refresh-modules-in-column' : case 'sek-duplicate-module' : if ( !array_key_exists( 'in_column', $_POST ) || empty( $_POST['in_column'] ) ) { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' ' . $sek_action .' => missing in_column param' ); break; } if ( !array_key_exists( 'in_sektion', $_POST ) || empty( $_POST[ 'in_sektion' ] ) ) { $this->parent_model = sek_get_parent_level_model( sanitize_text_field($_POST[ 'in_column' ]), $sektion_collection ); } else { $this->parent_model = sek_get_level_model( sanitize_text_field($_POST[ 'in_sektion' ]), $sektion_collection ); } $level_model = sek_get_level_model( sanitize_text_field($_POST[ 'in_column' ]), $sektion_collection ); break; case 'sek-resize-columns' : if ( !array_key_exists( 'resized_column', $_POST ) || empty( $_POST['resized_column'] ) ) { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' ' . $sek_action .' => missing resized_column' ); break; } $is_stylesheet = true; break; case 'sek-refresh-stylesheet' : $is_stylesheet = true; break; case 'sek-refresh-level' : if ( !array_key_exists( 'id', $_POST ) || empty( $_POST['id'] ) ) { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' ' . $sek_action .' => missing level id' ); break; } if ( !empty( $_POST['level'] ) && 'column' === sanitize_text_field($_POST['level']) ) { // we need to set the parent_mode here to access it later in the ::render method to calculate the column width. $this->parent_model = sek_get_parent_level_model( sanitize_text_field($_POST['id']), $sektion_collection ); } $level_model = sek_get_level_model( sanitize_text_field($_POST[ 'id' ]), $sektion_collection ); break; }//Switch sek_action // sek_error_log('LEVEL MODEL WHEN AJAXING', $level_model ); ob_start(); if ( $is_stylesheet ) { $r = $this->print_or_enqueue_seks_style( sanitize_text_field($_POST['location_skope_id']) ); } else { if ( 'no_match' == $level_model ) { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' ' . $sek_action . ' => missing level model' ); ob_end_clean(); return; } if ( empty( $level_model ) || !is_array( $level_model ) ) { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' => empty or invalid $level_model' ); ob_end_clean(); return; } // note that in the case of a sektion nested inside a column, the parent_model has been set in the switch{ case : ... } above ,so we can access it in the ::render method to calculate the column width. $r = $this->render( $level_model ); } $html = ob_get_clean(); if ( is_wp_error( $r ) ) { return $r; } else { // the $html content should not be empty when ajaxing a template // it can be empty when ajaxing a stylesheet if ( !$is_stylesheet && empty( $html ) ) { // return a new WP_Error that will be intercepted in sek_get_level_content_for_injection $html = new \WP_Error( 'ajax_fetch_content_error', __CLASS__ . '::' . __FUNCTION__ . ' => no content returned for sek_action : ' . $sek_action ); } return apply_filters( "sek_set_ajax_content", $html, $sek_action );// this is sent with wp_send_json_success( apply_filters( 'sek_content_results', $html, $sek_action ) ); } } //////////////////////////////////////////////////////////////// // USED TO PRINT THE BUTTON EDIT WITH NIMBLE ON POSTS AND PAGES // when using Gutenberg editor // implemented for https://github.com/presscustomizr/nimble-builder/issues/449 function sek_get_customize_url_for_nimble_edit_button() { $this->sek_do_ajax_pre_checks( array( 'check_nonce' => false ) ); if ( !isset( $_POST['nimble_edit_post_id'] ) || empty( $_POST['nimble_edit_post_id'] ) ) { wp_send_json_error( __CLASS__ . '::' . __FUNCTION__ . ' => missing post_id' ); } $post_id = sanitize_text_field($_POST['nimble_edit_post_id']); // Build customize_url // @see function sek_get_customize_url_when_is_admin() $return_url_after_customization = '';//"/wp-admin/post.php?post={$post_id}&action=edit"; $customize_url = sek_get_customize_url_for_post_id( $post_id, $return_url_after_customization ); wp_send_json_success( $customize_url ); } }//class endif; ?>page_has_local_or_global_sections ) { Nimble_Manager()->page_has_local_or_global_sections = sek_local_skope_has_nimble_sections( skp_get_skope_id() ) || sek_has_global_sections(); } } // hook : 'wp_enqueue_scripts' // Contextual stylesheets for local and global sections are loaded with ::print_or_enqueue_seks_style() // see inc\sektions\_front_dev_php\8_4_1_sektions_front_class_render_css.php function sek_maybe_enqueue_front_css_assets() { /* ------------------------------------------------------------------------- * * MAIN STYLESHEET /* ------------------------------------------------------------------------- */ // Oct 2020 => use sek-base ( which includes all module stylesheets ) if Nimble could not concatenate module stylesheets when generating the dynamic stylesheet // for https://github.com/presscustomizr/nimble-builder/issues/749 if ( 'failed' === get_option(NIMBLE_OPT_FOR_MODULE_CSS_READING_STATUS) ) { $main_stylesheet_name = 'sek-base'; } else { // the light split stylesheet is never used when customizing $main_stylesheet_name = !skp_is_customizing() ? 'sek-base-light' : 'sek-base'; } // Always load the base Nimble style when user logged in so we can display properly the button in the top admin bar. if ( is_user_logged_in() || false != Nimble_Manager()->page_has_local_or_global_sections ) { $rtl_suffix = is_rtl() ? '-rtl' : ''; //wp_enqueue_style( 'google-material-icons', '//fonts.googleapis.com/icon?family=Material+Icons', array(), null, 'all' ); //base custom CSS bootstrap inspired wp_enqueue_style( $main_stylesheet_name, sprintf( '%1$s/assets/front/css/%2$s' , NIMBLE_BASE_URL, sek_is_dev_mode() ? "{$main_stylesheet_name}{$rtl_suffix}.css" : "{$main_stylesheet_name}{$rtl_suffix}.min.css" ), array(), NIMBLE_ASSETS_VERSION, 'all' ); } /* ------------------------------------------------------------------------- * * STOP HERE IF NOT CUSTOMIZING AND THERE IS NOTHING TO PRINT /* ------------------------------------------------------------------------- */ // We don't need Nimble Builder assets when no local or global sections have been created // see https://github.com/presscustomizr/nimble-builder/issues/586 if ( !skp_is_customizing() && !Nimble_Manager()->page_has_local_or_global_sections ) return; /* ------------------------------------------------------------------------- * * MODULE PARTIAL STYLESHEETS /* ------------------------------------------------------------------------- */ // populate the collection of module displayed in current context : local + global // introduced march 2020 for https://github.com/presscustomizr/nimble-builder/issues/612 // formed like : // [czr_heading_module] => Array // ( // [0] => __nimble__9a02775e86ec // [1] => __nimble__01f1e8d56415 // [2] => __nimble__8fc8dac22299 // [3] => __nimble__b71c69fd674d // [4] => __nimble__b74a63e1dc57 // [5] => __nimble__ca13a73ca586 // [6] => __nimble__e66b407f0f2b // [7] => __nimble__7d6526ab1812 // ) // [czr_img_slider_module] => Array // ( // [0] => __nimble__3a38fe3587b2 // ) // [czr_accordion_module] => Array // ( // [0] => __nimble__ec3d7956fe17 // ) // [czr_social_icons_module] => Array // ( // [0] => __nimble__c1526193134e // ) $contextually_active_modules = sek_get_collection_of_contextually_active_modules(); //sek_error_log('$contextually_active_modules ?', $contextually_active_modules ); // public $big_module_stylesheet_map = [ // 'czr_quote_module' => 'quote-module', // 'czr_icon_module' => 'icon-module', // 'czr_img_slider_module' => 'img-slider-module', // 'czr_accordion_module' => 'accordion-module', // 'czr_menu_module' => 'menu-module', // 'czr_post_grid_module' => 'post-grid-module', // 'czr_simple_form_module' => 'simple-form-module' // ]; // SPLIT STYLESHEETS // introduced march 2020 for https://github.com/presscustomizr/nimble-builder/issues/612 // if the module stylesheets are inline, see wp_head action // October 2020 => modules stylesheets are now concatenated in the dynamically generated stylesheet // if ( !skp_is_customizing() && $is_stylesheet_split_for_performance ) { // // loop on the map module type (candidates for split) => stylesheet file name // foreach (Nimble_Manager()->big_module_stylesheet_map as $module_type => $stylesheet_name ) { // if ( !array_key_exists($module_type , $contextually_active_modules ) ) // continue; // wp_enqueue_style( // $module_type, // sprintf( '%1$s%2$s%3$s', // NIMBLE_BASE_URL . '/assets/front/css/modules/', // $stylesheet_name, // sek_is_dev_mode() ? '.css' : '.min.css' // ), // array( $main_stylesheet_name ), // NIMBLE_ASSETS_VERSION, // $media = 'all' // ); // } // } /* ------------------------------------------------------------------------- * * LIGHT BOX WITH MAGNIFIC POPUP /* ------------------------------------------------------------------------- */ // the stylesheet is always preloaded on front if ( skp_is_customizing() ) { wp_enqueue_style( 'nb-swipebox', NIMBLE_BASE_URL . '/assets/front/css/libs/swipebox.min.css', array(), NIMBLE_ASSETS_VERSION, $media = 'all' ); } /* ------------------------------------------------------------------------- * * SWIPER FOR SLIDERS /* ------------------------------------------------------------------------- */ if ( array_key_exists('czr_img_slider_module' , $contextually_active_modules) || skp_is_customizing() ) { // march 2020 : // when loading assets in ajax, swiper stylesheet is loaded dynamically // so we don't need to enqueue it // added for https://github.com/presscustomizr/nimble-builder/issues/612 // added for https://github.com/presscustomizr/nimble-builder/issues/635 if ( skp_is_customizing() || !sek_load_front_assets_dynamically() ) { wp_enqueue_style( 'nb-swiper', sek_is_dev_mode() ? NIMBLE_BASE_URL . '/assets/front/css/libs/swiper-bundle.css' : NIMBLE_BASE_URL . '/assets/front/css/libs/swiper-bundle.min.css', array(), NIMBLE_ASSETS_VERSION, $media = 'all' ); } } /* ------------------------------------------------------------------------- * * FONT AWESOME STYLESHEET /* ------------------------------------------------------------------------- */ if ( skp_is_customizing() ) { wp_enqueue_style( 'nb-font-awesome', NIMBLE_BASE_URL . '/assets/front/fonts/css/fontawesome-all.min.css', array(), NIMBLE_ASSETS_VERSION, $media = 'all' ); } }//sek_enqueue_front_assets // @wp_head 0 function sek_inline_init_js() { // Nimble-init if ( skp_is_customizing() || Nimble_Manager()->page_has_local_or_global_sections ) { // Minified version of assets/front/nimble-init.js ob_start(); ?> window.nb_={},function(e,t){if(window.nb_={isArray:function(e){return Array.isArray(e)||"[object Array]"===toString.call(e)},inArray:function(e,t){return!(!nb_.isArray(e)||nb_.isUndefined(t))&&e.indexOf(t)>-1},isUndefined:function(e){return void 0===e},isObject:function(e){var t=typeof e;return"function"===t||"object"===t&&!!e},errorLog:function(){nb_.isUndefined(console)||"function"!=typeof window.console.log||console.log.apply(console,arguments)},hasPreloadSupport:function(e){var t=document.createElement("link").relList;return!(!t||!t.supports)&&t.supports("preload")},listenTo:function(e,t){nb_.eventsListenedTo.push(e);var n={"nb-jquery-loaded":function(){return"undefined"!=typeof jQuery},"nb-app-ready":function(){return void 0!==window.nb_&&nb_.wasListenedTo("nb-jquery-loaded")},"nb-swipebox-parsed":function(){return"undefined"!=typeof jQuery&&void 0!==jQuery.fn.swipebox},"nb-main-swiper-parsed":function(){return void 0!==window.Swiper}},o=function(o){nb_.isUndefined(n[e])||!1!==n[e]()?t():nb_.errorLog("Nimble error => an event callback could not be fired because conditions not met => ",e,nb_.eventsListenedTo,t)};"function"==typeof t?nb_.wasEmitted(e)?o():document.addEventListener(e,o):nb_.errorLog("Nimble error => listenTo func param is not a function for event => ",e)},eventsEmitted:[],eventsListenedTo:[],emit:function(e,t){if(!(nb_.isUndefined(t)||t.fire_once)||!nb_.wasEmitted(e)){var n=document.createEvent("Event");n.initEvent(e,!0,!0),document.dispatchEvent(n),nb_.eventsEmitted.push(e)}},wasListenedTo:function(e){return"string"==typeof e&&nb_.inArray(nb_.eventsListenedTo,e)},wasEmitted:function(e){return"string"==typeof e&&nb_.inArray(nb_.eventsEmitted,e)},isInScreen:function(e){if(!nb_.isObject(e))return!1;var t=e.getBoundingClientRect(),n=Math.max(document.documentElement.clientHeight,window.innerHeight);return!(t.bottom<0||t.top-n>=0)},isCustomizing:function(){return!1},isLazyLoadEnabled:function(){return!nb_.isCustomizing()&&!1},preloadOrDeferAsset:function(e){if(e=e||{},nb_.preloadedAssets=nb_.preloadedAssets||[],!nb_.inArray(nb_.preloadedAssets,e.id)){var t,n=document.getElementsByTagName("head")[0],o=function(){if("style"===e.as)this.setAttribute("rel","stylesheet"),this.setAttribute("type","text/css"),this.setAttribute("media","all");else{var t=document.createElement("script");t.setAttribute("src",e.href),t.setAttribute("id",e.id),"script"===e.as&&t.setAttribute("defer","defer"),n.appendChild(t),i.call(this)}e.eventOnLoad&&nb_.emit(e.eventOnLoad)},i=function(){if(this&&this.parentNode&&this.parentNode.contains(this))try{this.parentNode.removeChild(this)}catch(e){nb_.errorLog("NB error when removing a script el",el)}};("font"!==e.as||nb_.hasPreloadSupport())&&(t=document.createElement("link"),"script"===e.as?e.onEvent?nb_.listenTo(e.onEvent,function(){o.call(t)}):o.call(t):(t.setAttribute("href",e.href),"style"===e.as?t.setAttribute("rel",nb_.hasPreloadSupport()?"preload":"stylesheet"):"font"===e.as&&nb_.hasPreloadSupport()&&t.setAttribute("rel","preload"),t.setAttribute("id",e.id),t.setAttribute("as",e.as),"font"===e.as&&(t.setAttribute("type",e.type),t.setAttribute("crossorigin","anonymous")),t.onload=function(){this.onload=null,"font"!==e.as?e.onEvent?nb_.listenTo(e.onEvent,function(){o.call(t)}):o.call(t):e.eventOnLoad&&nb_.emit(e.eventOnLoad)},t.onerror=function(t){nb_.errorLog("Nimble preloadOrDeferAsset error",t,e)}),n.appendChild(t),nb_.preloadedAssets.push(e.id),i.call(e.scriptEl))}},mayBeRevealBG:function(){this.getAttribute("data-sek-src")&&(this.setAttribute("style",'background-image:url("'+this.getAttribute("data-sek-src")+'")'),this.className+=" sek-lazy-loaded",this.querySelectorAll(".sek-css-loader").forEach(function(e){nb_.isObject(e)&&e.parentNode.removeChild(e)}))}},window.NodeList&&!NodeList.prototype.forEach&&(NodeList.prototype.forEach=function(e,t){t=t||window;for(var n=0;n "+e.path)}).fail(function(){nb_.errorLog("ajaxLoadScript failed for => "+e.path)}))})})}),nb_.listenTo("nb-jquery-loaded",function(){jQuery(function(e){sekFrontLocalized.load_front_assets_on_dynamically&&(nb_.ajaxLoadScript({path:sekFrontLocalized.isDevMode?"js/ccat-nimble-front.js":"js/ccat-nimble-front.min.js"}),e.each(sekFrontLocalized.partialFrontScripts,function(e,t){nb_.listenTo(t,function(){nb_.ajaxLoadScript({path:sekFrontLocalized.isDevMode?"js/partials/"+e+".js":"js/partials/"+e+".min.js"})})}))})}); >> when not customizing, all js assets but nb-js-init are injected with javascript <<=== // Loading sequence : // 1) window.nb_ utils starts being populated // 2) 'nb-jquery-loaded' => fired in footer when jQuery is defined <= window.nb_ utils is completed with jQuery dependant helper properties and methods // 3) 'nb-app-ready' => fired in footer on 'nb-jquery-loaded' <= all module scripts are fired on this event // 4) 'nb-{js-library}-parsed', ... are emitted in each script files function sek_enqueue_front_js_assets() { if ( !skp_is_customizing() ) { wp_enqueue_script('jquery'); } if ( skp_is_customizing() || Nimble_Manager()->page_has_local_or_global_sections ) { // Google reCAPTCHA $global_recaptcha_opts = sek_get_global_option_value('recaptcha'); $global_recaptcha_opts = is_array( $global_recaptcha_opts ) ? $global_recaptcha_opts : array(); $l10n = array( 'isDevMode' => sek_is_dev_mode(), 'isCustomizing' => skp_is_customizing(), //'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'frontNonce' => array( 'id' => 'SEKFrontNonce', 'handle' => wp_create_nonce( 'sek-front-nonce' ) ), // 'localSeks' => sek_is_debug_mode() ? wp_json_encode( sek_get_skoped_seks() ) : '', // 'globalSeks' => sek_is_debug_mode() ? wp_json_encode( sek_get_skoped_seks( NIMBLE_GLOBAL_SKOPE_ID ) ) : '', 'skope_id' => skp_get_skope_id(), //added for debugging purposes 'recaptcha_public_key' => !empty( $global_recaptcha_opts['public_key'] ) ? $global_recaptcha_opts['public_key'] : '', 'lazyload_enabled' => sek_is_img_smartload_enabled(), 'video_bg_lazyload_enabled' => sek_is_video_bg_lazyload_enabled(), 'load_front_assets_on_dynamically' => sek_load_front_assets_dynamically(), 'assetVersion' => NIMBLE_ASSETS_VERSION, 'frontAssetsPath' => NIMBLE_BASE_URL . '/assets/front/', 'contextuallyActiveModules' => sek_get_collection_of_contextually_active_modules(), 'fontAwesomeAlreadyEnqueued' => wp_style_is('customizr-fa', 'enqueued') || wp_style_is('hueman-font-awesome', 'enqueued'), 'partialFrontScripts' => Nimble_Manager()->partial_front_scripts, // Debug for https://github.com/presscustomizr/nimble-builder/issues/795 // 'debug' => [ // 'nb_debug_save' => get_transient('nb_debug_save'), // 'nb_debug_get' => get_transient('nb_debug_get') // ] ); $l10n = apply_filters( 'nimble-localized-js-front', $l10n ); wp_localize_script( 'nb-js-app', 'sekFrontLocalized',$l10n); } // When not customizing, the main and partial front scripts are loaded only when needed on front, with nb_.listenTo('{event_name}') // When customizing, we need to enqueue them the regular way if ( !skp_is_customizing() ) return; /* ------------------------------------------------------------------------- * * FRONT MAIN SCRIPT /* ------------------------------------------------------------------------- */ wp_enqueue_script( 'nb-main-js', sek_is_dev_mode() ? NIMBLE_BASE_URL . '/assets/front/js/ccat-nimble-front.js' : NIMBLE_BASE_URL . '/assets/front/js/ccat-nimble-front.min.js', array( 'jquery'), NIMBLE_ASSETS_VERSION, false ); // added for https://github.com/presscustomizr/nimble-builder/issues/583 sek_defer_script('nb-main-js'); /* ------------------------------------------------------------------------- * * FRONT PARTIAL SCRIPTS /* ------------------------------------------------------------------------- */ // public $partial_front_scripts = [ // 'slider-module' => 'nb-needs-swiper', // 'menu-module' => 'nb-needs-menu-js', // 'front-parallax' => 'nb-needs-parallax', // 'accordion-module' => 'nb-needs-accordion' // ]; foreach (Nimble_Manager()->partial_front_scripts as $name => $event) { $handle = "nb-{$name}"; wp_enqueue_script( $handle, sprintf('%1$s/assets/front/js/partials/%2$s.%3$s', NIMBLE_BASE_URL, $name, sek_is_dev_mode() ? 'js' : 'min.js'), array(), NIMBLE_ASSETS_VERSION, false ); sek_defer_script($handle); } /* ------------------------------------------------------------------------- * * LIGHT BOX WITH MAGNIFIC POPUP /* ------------------------------------------------------------------------- */ wp_enqueue_script( 'nb-swipebox', sek_is_dev_mode() ? NIMBLE_BASE_URL . '/assets/front/js/libs/jquery-swipebox.js' : NIMBLE_BASE_URL . '/assets/front/js/libs/jquery-swipebox.min.js', array(), NIMBLE_ASSETS_VERSION, false ); sek_defer_script('nb-swipebox'); /* ------------------------------------------------------------------------- * * SWIPER FOR SLIDERS /* ------------------------------------------------------------------------- */ // SWIPER JS LIB + MODULE SCRIPT // Swiper js is needed for the czr_img_slider_module // front : Load if js not loaded dynamically + we detect the need for the script // customizing : load if not loaded dynamically wp_enqueue_script( 'nb-swiper', sek_is_dev_mode() ? NIMBLE_BASE_URL . '/assets/front/js/libs/swiper-bundle.js' : NIMBLE_BASE_URL . '/assets/front/js/libs/swiper-bundle.min.js', array(), NIMBLE_ASSETS_VERSION, false ); // not added when customizing sek_defer_script('nb-swiper'); /* ------------------------------------------------------------------------- * * VIDEO BG /* ------------------------------------------------------------------------- */ // front : Load if js not loaded dynamically + we detect the need for the script // customizing : load if not loaded dynamically wp_enqueue_script( 'nb-video-bg-plugin', sek_is_dev_mode() ? NIMBLE_BASE_URL . '/assets/front/js/libs/nimble-video-bg.js' : NIMBLE_BASE_URL . '/assets/front/js/libs/nimble-video-bg.min.js', array(), NIMBLE_ASSETS_VERSION, false ); // not added when customizing sek_defer_script('nb-video-bg-plugin'); }//sek_enqueue_front_js_assets //@wp_head // ==>>> when customizing, all assets are enqueued the wp way <<=== function sek_main_front_js_preloading_when_not_customizing() { if ( skp_is_customizing() ) return; if ( !Nimble_Manager()->page_has_local_or_global_sections ) return; if ( sek_load_front_assets_dynamically() ) return; // Load main script on nb-docready event $script_url = sprintf('%1$s/assets/front/js/ccat-nimble-front.%2$s?v=%3$s', NIMBLE_BASE_URL, sek_is_dev_mode() ? 'js' : 'min.js', NIMBLE_ASSETS_VERSION); ob_start(); ?> nb_.listenTo('nb-docready', function() { nb_.preloadOrDeferAsset( { id : 'nb-main-js', as : 'script', href : "", scriptEl : document.getElementById('') }); }); partial_front_scripts; foreach ($partial_front_scripts as $name => $event) { $url = sprintf('%1$s/assets/front/js/partials/%2$s.%3$s?v=%4$s', NIMBLE_BASE_URL, $name, sek_is_dev_mode() ? 'js' : 'min.js', NIMBLE_ASSETS_VERSION); ?> nb_.listenTo('', function() { nb_.preloadOrDeferAsset( { id : "", as : 'script', href : "", scriptEl : document.getElementById('') }); }); >> when customizing, all assets are enqueued the wp way <<=== function sek_maybe_preload_front_assets_when_not_customizing() { if ( skp_is_customizing() ) return; // Check that current page has Nimble content before printing anything // For https://github.com/presscustomizr/nimble-builder/issues/649 // When customizing, all assets are enqueued the WP way if ( !Nimble_Manager()->page_has_local_or_global_sections ) return; // When we load assets in ajax, we stop here if ( sek_load_front_assets_dynamically() ) return; /* ------------------------------------------------------------------------- * * PRELOAD FRONT SCRIPT /* ------------------------------------------------------------------------- */ $assets_urls = [ 'nb-swipebox' => sek_is_dev_mode() ? '/assets/front/js/libs/jquery-swipebox.js' : '/assets/front/js/libs/jquery-swipebox.min.js', 'nb-swiper' => sek_is_dev_mode() ? '/assets/front/js/libs/swiper-bundle.js' : '/assets/front/js/libs/swiper-bundle.min.js', 'nb-video-bg-plugin' => sek_is_dev_mode() ? '/assets/front/js/libs/nimble-video-bg.js' : '/assets/front/js/libs/nimble-video-bg.min.js', 'nb-swipebox-style' => '/assets/front/css/libs/swipebox.min.css', ]; // add version foreach( $assets_urls as $k => $path ) { $assets_urls[$k] = NIMBLE_BASE_URL .$path .'?'.NIMBLE_ASSETS_VERSION; } ob_start(); ?> nb_.listenTo('nb-needs-swipebox', function() { nb_.preloadOrDeferAsset( { id : 'nb-swipebox', as : 'script', href : "", onEvent : 'nb-docready', // scriptEl : document.currentScript }); nb_.preloadOrDeferAsset( { id : 'nb-swipebox-style', as : 'style', href : "", onEvent : 'nb-docready', // scriptEl : document.currentScript }); }); nb_.listenTo('nb-needs-swiper', function() { nb_.preloadOrDeferAsset( { id : 'nb-swiper', as : 'script', href : "", onEvent : 'nb-docready', // scriptEl : document.currentScript }); }); nb_.listenTo('nb-needs-videobg-js', function() { nb_.preloadOrDeferAsset( { id : 'nb-video-bg-plugin', as : 'script', href : "", onEvent : 'nb-docready', // scriptEl : document.currentScript }); }); 'customizr-fa' // asset handle for Hueman => 'hueman-font-awesome' if ( !wp_style_is('customizr-fa', 'enqueued') && !wp_style_is('hueman-font-awesome', 'enqueued') ) { // Font awesome is always loaded when customizing ?> nb_.listenTo('nb-needs-fa', function() { nb_.preloadOrDeferAsset( { id : 'nb-font-awesome', as : 'style', href : "", onEvent : 'nb-docready', scriptEl : document.currentScript }); }); this removes Google Speed tests message "preload key requests" // important => the url of the font must be exactly the same as in font awesome stylesheet, including the query param at the end fa-brands-400.woff2?5.15.2 // note that we could preload all other types available ( eot, woff, ttf, svg ) // but NB focus on preloading woff2 which is the type used by most recent browsers // see https://css-tricks.com/snippets/css/using-font-face/ function sek_maybe_preload_fa_fonts() { if ( !skp_is_customizing() && !Nimble_Manager()->page_has_local_or_global_sections ) return; $fonts = [ 'fa-brands' => 'fa-brands-400.woff2?v=5.15.2', 'fa-regular' => 'fa-regular-400.woff2?v=5.15.2', 'fa-solid' => 'fa-solid-900.woff2?v=5.15.2' ]; ob_start(); ?> $name ) : ?> nb_.listenTo('nb-needs-fa', function() { nb_.preloadOrDeferAsset( { id : "", as : 'font', href : "", type : 'font/woff2', //onEvent : 'nb-docready', //eventOnLoad : 'nb-font-awesome-preloaded', scriptEl : document.getElementById('') }); }); $tag = str_replace('src=', 'id="nb-jquery" src=', $tag); } // if ( skp_is_customizing() ) // return $tag; foreach ( [ 'async', 'defer' ] as $attr ) { if ( !wp_scripts()->get_data( $handle, $attr ) ) { continue; } // Prevent adding attribute when already added in #12009. if ( !preg_match( ":\s$attr(=|>|\s):", $tag ) ) { $tag = preg_replace( ':(?=>):', " $attr", $tag, 1 ); } // Only allow async or defer, not both. break; } return $tag; } }//class endif; ?> (function(w, d){ nb_.listenTo( 'nb-app-ready', function() { //PREVIEWED DEVICE ? //Listen to the customizer previewed device var _setPreviewedDevice = function() { wp.customize.preview.bind( 'previewed-device', function( device ) { nb_.previewedDevice = device;// desktop, tablet, mobile }); }; if ( wp.customize.preview ) { _setPreviewedDevice(); } else { wp.customize.bind( 'preview-ready', function() { _setPreviewedDevice(); }); } // REVEAL BG IMAGE ON CHANGE ? jQuery( function($) { $('body').on( 'sek-level-refreshed', '[data-sek-level="location"]', function( evt, params ) { var matches = document.querySelectorAll('div.sek-has-bg'); if ( !nb_.isObject( matches ) || matches.length < 1 ) return; var imgSrc; matches.forEach( function(el) { if ( !nb_.isObject(el) ) return; // Maybe reveal BG if lazyload is on if ( nb_.isCustomizing() ) { nb_.mayBeRevealBG.call(el); } }); }); }); }); }(window, document)); array( "You've reached the maximum number of columns allowed in this section." => __( "You've reached the maximum number of columns allowed in this section.", 'text_doma'), "Moving elements between global and local sections is not allowed." => __( "Moving elements between global and local sections is not allowed.", 'text_doma'), 'Something went wrong, please refresh this page.' => __('Something went wrong, please refresh this page.', 'text_doma'), 'Insert here' => __('Insert here', 'text_doma'), 'This content has been created with the WordPress editor.' => __('This content has been created with the WordPress editor.', 'text_domain' ), 'Insert a new section' => __('Insert a new section', 'text_doma' ), '@location' => __('@location', 'text_domain_to_be'), 'Insert a new global section' => __('Insert a new global section', 'text_doma' ), 'section' => __('section', 'text_doma'), 'header section' => __('header section', 'text_doma'), 'footer section' => __('footer section', 'text_doma'), '(global)' => __('(global)', 'text_doma'), 'nested section' => __('nested section', 'text_doma'), 'Shift-click to visit the link' => __('Shift-click to visit the link', 'text_doma'), 'External links are disabled when customizing' => __('External links are disabled when customizing', 'text_doma'), 'Link deactivated while previewing' => __('Link deactivated while previewing', 'text_doma') ), 'isDevMode' => sek_is_dev_mode(), 'isPreviewUIDebugMode' => isset( $_GET['preview_ui_debug'] ) || NIMBLE_IS_PREVIEW_UI_DEBUG_MODE, 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'frontNonce' => array( 'id' => 'SEKFrontNonce', 'handle' => wp_create_nonce( 'sek-front-nonce' ) ), 'registeredModules' => CZR_Fmk_Base()->registered_modules, // introduced for https://github.com/presscustomizr/nimble-builder/issues/494 // september 2019 // this guid is used to differentiate dynamically rendered content from static content that may include a Nimble generated HTML structure // an attribute "data-sek-preview-level-guid" is added to each rendered level when customizing or ajaxing // when generating the ui, we check if the localized guid matches the one rendered server side // otherwise the preview UI can be broken 'previewLevelGuid' => $this->sek_get_preview_level_guid(), // Assets id 'googleFontsStyleId' => NIMBLE_GOOGLE_FONTS_STYLESHEET_ID, 'globalOptionsStyleId' => NIMBLE_GLOBAL_OPTIONS_STYLESHEET_ID ) ); wp_enqueue_script( 'jquery-ui-sortable' ); wp_enqueue_style( 'ui-sortable', '//code.jquery.com/ui/1.12.1/themes/smoothness/jquery-ui.css', array(), null,//time(), $media = 'all' ); wp_enqueue_script( 'jquery-ui-resizable' ); } //'wp_footer' in the preview frame function sek_print_ui_tmpl() { ?> sek_setup_tiny_mce_content_filters(); // REGISTER HEADER AND FOOTER GLOBAL LOCATIONS add_action( 'nimble_front_classes_ready', array( $this, 'sek_register_nimble_global_locations') ); // CONTENT : USE THE DEFAULT WP TEMPLATE OR A CUSTOM NIMBLE ONE add_filter( 'template_include', array( $this, 'sek_maybe_set_local_nimble_template' ) ); // HEADER FOOTER // Header/footer, widgets module, menu module have been beta tested during 5 months and released in June 2019, in version 1.8.0 add_action( 'template_redirect', array( $this, 'sek_maybe_set_nimble_header_footer' ) ); // HEADER : USE THE DEFAULT WP TEMPLATE OR A CUSTOM NIMBLE ONE add_filter( 'get_header', array( $this, 'sek_maybe_set_local_nimble_header') ); // FOOTER : USE THE DEFAULT WP TEMPLATE OR A CUSTOM NIMBLE ONE add_filter( 'get_footer', array( $this, 'sek_maybe_set_local_nimble_footer') ); // INCLUDE NIMBLE CONTENT IN SEARCH RESULTS add_action( 'wp_head', array( $this, 'sek_maybe_include_nimble_content_in_search_results' ) ); add_filter( 'body_class', array( $this, 'sek_add_front_body_class') ); // PASSWORD FORM AND CONTENT RESTRICTION ( PLUGINS ) $this->sek_schedule_content_restriction_actions(); }//_schedule_front_rendering() // @'body_class' function sek_add_front_body_class( $classes ) { $classes = is_array($classes) ? $classes : array(); // Check whether we're in the customizer preview. if ( is_customize_preview() ) { $classes[] = 'customizer-preview'; } if ( !is_customize_preview() ) { $skope_id = skp_get_skope_id(); $group_skope = sek_get_group_skope_for_site_tmpl(); if ( sek_is_inheritance_locally_disabled() ) { array_unshift( $classes, 'nimble-site-tmpl-inheritance-disabled' ); } if ( sek_has_group_site_template_data() ) { // Site template params are structured as follow : // [ // 'site_tmpl_id' : '_no_site_tmpl_', // 'site_tmpl_source' : 'user_tmpl', // 'site_tmpl_title' : '' //]; $tmpl_params = sek_get_site_tmpl_params_for_skope( $group_skope ); array_unshift( $classes, 'nimble-site-tmpl__' . $tmpl_params['site_tmpl_source'] . '__' . $tmpl_params['site_tmpl_id'] ); array_unshift( $classes, 'nimble-has-group-site-tmpl-' . $group_skope ); } else { array_unshift( $classes, 'nimble-no-group-site-tmpl-' . $group_skope ); } array_unshift( $classes, !sek_local_skope_has_been_customized() ? 'nimble-no-local-data-' . $skope_id : 'nimble-has-local-data-' . $skope_id ); } if ( sek_is_pro() ) { array_unshift( $classes, 'nb-pro-' . str_replace('.', '-', NB_PRO_VERSION ) ); } array_unshift( $classes, 'nb-' . str_replace('.', '-', NIMBLE_VERSION ) ); return $classes; } // Encapsulate the singular post / page content so we can generate a dynamic ui around it when customizing // @filter the_content::NIMBLE_WP_CONTENT_WRAP_FILTER_PRIORITY function sek_wrap_wp_content( $html ) { if ( !skp_is_customizing() || ( defined('DOING_AJAX') && DOING_AJAX ) ) return $html; if ( is_singular() && in_the_loop() && is_main_query() ) { global $post; // note : the edit url is printed as a data attribute to prevent being automatically parsed by wp when customizing and turned into a changeset url $html = sprintf( '
%4$s
', $post->ID, // we can't rely on the get_edit_post_link() function when customizing because emptied by wp core $this->get_unfiltered_edit_post_link( $post->ID ), __( 'WordPress content', 'text_domain'), wpautop( $html ) ); } return $html; } // Fired in the constructor function sek_register_nimble_global_locations() { register_location('nimble_local_header', array( 'is_header_location' => true ) ); register_location('nimble_local_footer', array( 'is_footer_location' => true ) ); register_location('nimble_global_header', array( 'is_global_location' => true, 'is_header_location' => true ) ); register_location('nimble_global_footer', array( 'is_global_location' => true, 'is_footer_location' => true ) ); } // @template_redirect // When using the default theme template, let's schedule the default hooks rendering // When using the Nimble template, this is done with render_content_sections_for_nimble_template(); function sek_schedule_rendering_hooks() { $locale_template = sek_get_locale_template(); // cache all locations now $all_locations = sek_get_locations(); // $default_locations = [ // 'loop_start' => array( 'priority' => 10 ), // 'before_content' => array(), // 'after_content' => array(), // 'loop_end' => array( 'priority' => 10 ), // ] // SCHEDULE THE ACTIONS ON HOOKS AND CONTENT FILTERS foreach( $all_locations as $location_id => $params ) { $params = is_array( $params ) ? $params : array(); $params = wp_parse_args( $params, array( 'priority' => 10 ) ); // When a local template is used, the default locations are rendered with : // render_nimble_locations( // array_keys( Nimble_Manager()->default_locations ),//array( 'loop_start', 'before_content', 'after_content', 'loop_end'), // ); // @see nimble tmpl/ template files // That's why we don't need to add the rendering actions for the default locations. We only need to add action for the possible locations registered on the theme hooks if ( !empty( $locale_template ) && !array_key_exists( $location_id, Nimble_Manager()->default_locations ) ) { add_action( $location_id, array( $this, 'sek_schedule_sektions_rendering' ), $params['priority'] ); } else { switch ( $location_id ) { case 'loop_start' : case 'loop_end' : // Do not add loop_start, loop_end action hooks when in a jetpack's like "infinite scroll" query // see: https://github.com/presscustomizr/nimble-builder/issues/228 // the filter 'infinite_scroll_got_infinity' is documented both in jetpack's infinite module // and in Customizr-Pro/Hueman-Pro infinite scroll code. They both use the same $_GET var too. // Actually this is not needed anymore for our themes, see: // https://github.com/presscustomizr/nimble-builder/issues/228#issuecomment-449362111 if ( !( apply_filters( 'infinite_scroll_got_infinity', isset( $_GET[ 'infinity' ] ) ) ) ) { add_action( $location_id, array( $this, 'sek_schedule_sektions_rendering' ), $params['priority'] ); } break; case 'before_content' : add_filter('the_content', array( $this, 'sek_schedule_sektion_rendering_before_content' ), NIMBLE_BEFORE_CONTENT_FILTER_PRIORITY ); break; case 'after_content' : add_filter('the_content', array( $this, 'sek_schedule_sektion_rendering_after_content' ), NIMBLE_AFTER_CONTENT_FILTER_PRIORITY ); break; // Default is typically used for custom locations default : add_action( $location_id, array( $this, 'sek_schedule_sektions_rendering' ), $params['priority'] ); break; } } } } // hook : loop_start, loop_end, and all custom locations like __before_main_wrapper, __after_header or __before_footer in the Customizr theme. // @return void() function sek_schedule_sektions_rendering( $query = null ) { // Check if the passed query is the main_query, bail if not // fixes: https://github.com/presscustomizr/nimble-builder/issues/154 2. // Note: a check using $query instanceof WP_Query would return false here, probably because the // query object is passed by reference // accidentally this would also fix the same point 1. of the same issue if the 'sek_schedule_rendering_hooks' method will be fired // with an early hook (earlier than wp_head). if ( is_object( $query ) && is_a( $query, 'WP_Query' ) && !$query->is_main_query() ) { return; } $location_id = current_filter(); $this->_render_seks_for_location( $location_id ); } // hook : 'the_content'::-9999 function sek_schedule_sektion_rendering_before_content( $html ) { return $this->_filter_the_content( $html, 'before_content' ); } // hook : 'the_content'::9999 function sek_schedule_sektion_rendering_after_content( $html ) { return $this->_filter_the_content( $html, 'after_content' ); } private function _filter_the_content( $html, $where ) { if ( is_singular() && in_the_loop() && is_main_query() ) { ob_start(); $this->_render_seks_for_location( $where ); $html = 'before_content' == $where ? ob_get_clean() . $html : $html . ob_get_clean(); // Collapse line breaks before and after
elements so they don't get autop'd. // @see function wpautop() in wp-includes\formatting.php // @fixes https://github.com/presscustomizr/nimble-builder/issues/32 if ( strpos( $html, '\s*|', '
', $html ); } } return $html; } // the $location_data can be provided. Typically when using the function render_content_sections_for_nimble_template in the Nimble page template. // @param $skope_id added april 2020 for https://github.com/presscustomizr/nimble-builder/issues/657 public function _render_seks_for_location( $location_id = '', $location_data = array(), $skope_id = '' ) { // why check if did_action( ... ) ? // => A location can be rendered only once // => for loop_start and loop_end, checking with is_main_query() is not enough because the main loop might be used 2 times in the same page // => for a custom location, it can be rendered by do_action() somewhere, and be rendered also with render_nimble_locations() // @see issue with Twenty Seventeen here : https://github.com/presscustomizr/nimble-builder/issues/14 if ( is_string( $location_id) && did_action( "sek_before_location_{$location_id}" ) ) return; $all_locations = sek_get_locations(); if ( !array_key_exists( $location_id, $all_locations ) ) { sek_error_log( __CLASS__ . '::' . __FUNCTION__ . ' Error => the location ' . $location_id . ' is not registered in sek_get_locations()'); return; } do_action( "sek_before_location_{$location_id}" ); $locationSettingValue = array(); $is_global_location = sek_is_global_location( $location_id ); if ( empty( $location_data ) ) { // APRIL 2020 added for for https://github.com/presscustomizr/nimble-builder/issues/657 if ( empty($skope_id) ) { $skope_id = $is_global_location ? NIMBLE_GLOBAL_SKOPE_ID : skp_build_skope_id(); } $locationSettingValue = sek_get_skoped_seks( $skope_id, $location_id ); } else { $locationSettingValue = $location_data; } if ( is_array( $locationSettingValue ) ) { remove_filter('the_content', array( $this, 'sek_wrap_wp_content' ), NIMBLE_WP_CONTENT_WRAP_FILTER_PRIORITY ); // sek_error_log( 'LEVEL MODEL IN ::sek_schedule_sektions_rendering()', $locationSettingValue); remove_filter('the_content', array( $this, 'sek_schedule_sektion_rendering_before_content' ), NIMBLE_BEFORE_CONTENT_FILTER_PRIORITY ); remove_filter('the_content', array( $this, 'sek_schedule_sektion_rendering_after_content' ), NIMBLE_AFTER_CONTENT_FILTER_PRIORITY ); // rendering property allows us to determine if we're rendering NB content while filtering WP core functions, like the one of the lazy load attributes Nimble_Manager()->rendering = true; $this->render( $locationSettingValue, $location_id ); Nimble_Manager()->rendering = false; add_filter('the_content', array( $this, 'sek_schedule_sektion_rendering_before_content' ),NIMBLE_BEFORE_CONTENT_FILTER_PRIORITY ); add_filter('the_content', array( $this, 'sek_schedule_sektion_rendering_after_content' ), NIMBLE_AFTER_CONTENT_FILTER_PRIORITY ); add_filter('the_content', array( $this, 'sek_wrap_wp_content' ), NIMBLE_WP_CONTENT_WRAP_FILTER_PRIORITY ); // inform Nimble Builder that a global section has been rendered // introduced for https://github.com/presscustomizr/nimble-builder/issues/456 if ( $is_global_location ) { Nimble_Manager()->global_sections_rendered = true; } } else { error_log( __CLASS__ . ' :: ' . __FUNCTION__ .' => sek_get_skoped_seks() should always return an array().'); } do_action( "sek_after_location_{$location_id}" ); }//_render_seks_for_location( /* ------------------------------------------------------------------------- * * RENDERING UTILITIES USED IN NIMBLE TEMPLATES /* ------------------------------------------------------------------------- */ // @return void() // @param $locations. mixed type // @param $options (array) // Note that a location can be rendered only once in a given page. // That's why we need to check if did_action(''), like in ::sek_schedule_sektions_rendering function render_nimble_locations( $locations, $options = array() ) { if ( is_string( $locations ) && !empty( $locations ) ) { $locations = array( $locations ); } if ( !is_array( $locations ) ) { sek_error_log( __FUNCTION__ . ' error => missing or invalid locations provided'); return; } // Normalize the $options $options = !is_array( $options ) ? array() : $options; $options = wp_parse_args( $options, array( // fallback_location => the location rendered even if empty. // This way, the user starts customizing with only one location for the content instead of four // But if the other locations were already customized, they will be printed. 'fallback_location' => null, // Typically set as 'loop_start' in the nimble templates )); //$is_global = sek_is_global_location( $location_id ); // $skopeLocationCollection = array(); // $skopeSettingValue = sek_get_skoped_seks( $skope_id ); // if ( is_array( ) && array_key_exists('collection', search) ) { // $skopeLocationCollection = $skopeSettingValue['collection']; // } //sek_error_log( __FUNCTION__ . ' sek_get_skoped_seks( ', sek_get_skoped_seks() ); foreach( $locations as $location_id ) { if ( !is_string( $location_id ) || empty( $location_id ) ) { sek_error_log( __FUNCTION__ . ' => error => a location_id is not valid in the provided locations', $locations ); continue; } // why check if did_action( ... ) ? // => A location can be rendered only once // => for loop_start and loop_end, checking with is_main_query() is not enough because the main loop might be used 2 times in the same page // => for a custom location, it can be rendered by do_action() somewhere, and be rendered also with render_nimble_locations() // @see issue with Twenty Seventeen here : https://github.com/presscustomizr/nimble-builder/issues/14 if ( did_action( "sek_before_location_{$location_id}" ) ) continue; $is_global = sek_is_global_location( $location_id ); $skope_id = $is_global ? NIMBLE_GLOBAL_SKOPE_ID : skp_get_skope_id(); $locationSettingValue = sek_get_skoped_seks( $skope_id, $location_id ); if ( !is_null( $options[ 'fallback_location' ]) ) { // We don't need to render the locations with no sections // But we need at least one location : let's always render loop_start. // => so if the user switches from the nimble_template to the default theme one, the loop_start section will always be rendered. if ( $options[ 'fallback_location' ] === $location_id || ( is_array( $locationSettingValue ) && !empty( $locationSettingValue['collection'] ) ) ) { Nimble_Manager()->_render_seks_for_location( $location_id, $locationSettingValue ); } } else { Nimble_Manager()->_render_seks_for_location( $location_id, $locationSettingValue ); } }//render_nimble_locations() } /* ------------------------------------------------------------------------- * * MAIN RENDERING METHOD /* ------------------------------------------------------------------------- */ // Walk a model tree recursively and render each level with a specific template function render( $model = array(), $location = 'loop_start' ) { //sek_error_log('LOCATIONS IN ::render()', sek_get_locations() ); //sek_error_log('LEVEL MODEL IN ::RENDER()', $model ); // Is it the root level ? // The root level has no id and no level entry if ( !is_array( $model ) ) { sek_error_log( __CLASS__ . '::' . __FUNCTION__ . ' => a model must be an array', $model ); return; } if ( !array_key_exists( 'level', $model ) || !array_key_exists( 'id', $model ) ) { error_log( '::render() => a level model is missing the level or the id property' ); return; } // The level "id" is a string not empty $id = $model['id']; if ( !is_string( $id ) || empty( $id ) ) { sek_error_log( __CLASS__ . '::' . __FUNCTION__ . ' Error => a level id must be a string not empty', $model ); return; } // The level "level" can take 4 values : location, section, column, module $level_type = $model['level']; if ( !is_string( $level_type ) || empty( $level_type ) ) { sek_error_log( __CLASS__ . '::' . __FUNCTION__ . ' Error => a level type must be a string not empty', $model ); return; } // A level id can be rendered only once by the recursive ::render method if ( in_array( $id, Nimble_Manager()->rendered_levels ) ) { sek_error_log( __CLASS__ . '::' . __FUNCTION__ . ' Error => a ' . $level_type . ' level id has already been rendered : ' . $id ); return; } // Record the rendered id now Nimble_Manager()->rendered_levels[] = $id; // Cache the parent model // => used when calculating the width of the column to be added $parent_model = $this->parent_model; $this->model = $model; $collection = array_key_exists( 'collection', $model ) ? $model['collection'] : array(); //sek_error_log( __FUNCTION__ . ' WHAT ARE WE RENDERING? ' . $id, current_filter() . ' | ' . current_action() ); $level_custom_anchor = null; if ( !empty( $model[ 'options' ] ) && !empty( $model[ 'options' ][ 'anchor' ] ) && !empty( $model[ 'options' ][ 'anchor' ]['custom_anchor'] ) ) { if ( is_string( $model[ 'options' ][ 'anchor' ]['custom_anchor'] ) ) { $level_custom_anchor = esc_attr( $model[ 'options' ][ 'anchor' ]['custom_anchor'] ); } } $level_css_classes = ''; if ( !empty( $model[ 'options' ] ) && !empty( $model[ 'options' ][ 'anchor' ] ) && !empty( $model[ 'options' ][ 'anchor' ]['custom_css_classes'] ) ) { if ( is_string( $model[ 'options' ][ 'anchor' ]['custom_css_classes'] ) ) { $level_css_classes = esc_attr( $model[ 'options' ][ 'anchor' ]['custom_css_classes'] ); //clean commas $level_css_classes = preg_replace("/(? Box shadow CSS class if ( !empty( $model[ 'options' ] ) && !empty( $model[ 'options' ][ 'border' ] ) && !empty( $model[ 'options' ][ 'border' ]['shadow'] ) ) { if ( sek_is_checked( $model[ 'options' ][ 'border' ]['shadow'] ) ) { $level_css_classes .= 'sek-level-has-shadow'; } } Nimble_Manager()->level_css_classes = apply_filters( 'nimble_level_css_classes', $level_css_classes, $model ); Nimble_Manager()->level_custom_anchor = $level_custom_anchor; Nimble_Manager()->level_custom_attr = apply_filters( 'nimble_level_custom_data_attributes', '', $model ); do_action('nimble_before_rendering_level', $model, Nimble_Manager()->level_css_classes, Nimble_Manager()->level_custom_attr ); switch ( $level_type ) { case 'location' : load_template( sek_get_templates_dir() . "/base-tmpl/location.php", false ); break; case 'section' : load_template( sek_get_templates_dir() . "/base-tmpl/section.php", false ); break; case 'column' : load_template( sek_get_templates_dir() . "/base-tmpl/column.php", false ); break; case 'module' : load_template( sek_get_templates_dir() . "/base-tmpl/module.php", false ); break; default : sek_error_log( __CLASS__ . '::' . __FUNCTION__ . ' error => a level is invalid : ' . $level_type ); break; } $this->parent_model = $parent_model; }//render /* ------------------------------------------------------------------------- * * VARIOUS HELPERS /* ------------------------------------------------------------------------- */ /* HELPER TO PRINT THE VISIBILITY CSS CLASS IN THE LEVEL CONTAINER */ // Dec 2019 : since issue https://github.com/presscustomizr/nimble-builder/issues/555, we use a dynamic CSS rule generation instead of static CSS // The CSS class are kept only for information when inspecting the markup // @see sek_add_css_rules_for_level_visibility() // @return string public function get_level_visibility_css_class( $model ) { if ( !is_array( $model ) ) { error_log( __FUNCTION__ . ' => $model param should be an array' ); return; } $visibility_class = ''; //when boxed use proper container class if ( !empty( $model[ 'options' ] ) && !empty( $model[ 'options' ][ 'visibility' ] ) ) { if ( is_array( $model[ 'options' ][ 'visibility' ] ) ) { foreach ( $model[ 'options' ][ 'visibility' ] as $device_type => $device_visibility_bool ) { if ( true !== sek_booleanize_checkbox_val( $device_visibility_bool ) ) { $visibility_class .= " sek-hidden-on-{$device_type}"; } } } } return $visibility_class; } /* MODULE AND PLACEHOLDER */ // module templates can be overriden from a child theme when located in nimble_templates/modules/{template_name}.php // for example /wp-content/themes/twenty-nineteen-child/nimble_templates/modules/image_module_tmpl.php // added for #532, october 2019 public function sek_maybe_get_overriden_template_path_for_module( $template_name = '') { if ( empty( $template_name ) ) return; $overriden_template_path = ''; // try locating this template file by looping through the template paths // inspîred from /wp-content/plugins/easy-digital-downloads/includes/template-functions.php foreach( sek_get_theme_template_base_paths() as $path_candidate ) { if( file_exists( $path_candidate . 'modules/' . $template_name ) ) { $overriden_template_path = $path_candidate . 'modules/' . $template_name; break; } } return $overriden_template_path; } // march 2020 : not used anymore function sek_get_input_placeholder_content( $input_type = '', $input_id = '' ) { $ph = 'pan_tool'; switch( $input_type ) { case 'detached_tinymce_editor' : case 'nimble_tinymce_editor' : case 'text' : $ph = skp_is_customizing() ? '
' . __('Click to edit', 'here') .'
' : ''; break; case 'upload' : $ph = 'image'; break; } switch( $input_id ) { case 'html_content' : $ph = skp_is_customizing() ? sprintf('
%1$s
%2$s
', __('Html code goes here', 'text-domain'), __('Click to edit', 'here') ) : ''; break; } if ( skp_is_customizing() ) { return sprintf('
%3$s
', $input_type, $input_id, $ph, __('Click to edit', 'here') ); } else { return $ph; } } /** * unfiltered version of get_edit_post_link() located in wp-includes/link-template.php * ( filtered by wp core when invoked in customize-preview ) */ function get_unfiltered_edit_post_link( $id = 0, $context = 'display' ) { if ( !$post = get_post( $id ) ) return; if ( 'revision' === $post->post_type ) $action = ''; elseif ( 'display' == $context ) $action = '&action=edit'; else $action = '&action=edit'; $post_type_object = get_post_type_object( $post->post_type ); if ( !$post_type_object ) return; if ( !current_user_can( 'edit_post', $post->ID ) ) return; if ( $post_type_object->_edit_link ) { $link = admin_url( sprintf( $post_type_object->_edit_link . $action, $post->ID ) ); } else { $link = ''; } return $link; } // @hook wp_enqueue_scripts function sek_enqueue_the_printed_module_assets() { $skope_id = skp_get_skope_id(); $skoped_seks = sek_get_skoped_seks( $skope_id ); if ( !is_array( $skoped_seks ) || empty( $skoped_seks['collection'] ) ) return; $enqueueing_candidates = $this->sek_sniff_assets_to_enqueue( $skoped_seks['collection'] ); foreach ( $enqueueing_candidates as $handle => $asset_params ) { if ( empty( $asset_params['type'] ) ) { sek_error_log( __FUNCTION__ . ' => missing asset type', $asset_params ); continue; } switch ( $asset_params['type'] ) { case 'css' : wp_enqueue_style( $handle, array_key_exists( 'src', $asset_params ) ? $asset_params['src'] : null, array_key_exists( 'deps', $asset_params ) ? $asset_params['deps'] : array(), NIMBLE_ASSETS_VERSION, 'all' ); break; case 'js' : wp_enqueue_script( $handle, array_key_exists( 'src', $asset_params ) ? $asset_params['src'] : null, array_key_exists( 'deps', $asset_params ) ? $asset_params['deps'] : null, array_key_exists( 'ver', $asset_params ) ? $asset_params['ver'] : null, array_key_exists( 'in_footer', $asset_params ) ? $asset_params['in_footer'] : false ); break; } } }//sek_enqueue_the_printed_module_assets() // @hook sek_sniff_assets_to_enqueue function sek_sniff_assets_to_enqueue( $collection, $enqueuing_candidates = array() ) { foreach ( $collection as $level_data ) { if ( array_key_exists( 'level', $level_data ) && 'module' === $level_data['level'] && !empty( $level_data['module_type'] ) ) { $front_assets = sek_get_registered_module_type_property( $level_data['module_type'], 'front_assets' ); if ( is_array( $front_assets ) ) { foreach ( $front_assets as $handle => $asset_params ) { if ( is_string( $handle ) && !array_key_exists( $handle, $enqueuing_candidates ) ) { $enqueuing_candidates[ $handle ] = $asset_params; } } } } else { if ( array_key_exists( 'collection', $level_data ) && is_array( $level_data['collection'] ) ) { $enqueuing_candidates = $this->sek_sniff_assets_to_enqueue( $level_data['collection'], $enqueuing_candidates ); } } }//foreach return $enqueuing_candidates; } /* ------------------------------------------------------------------------- * * SMART LOAD. /* ------------------------------------------------------------------------- */ // @return array of bg attributes // adds the lazy load data attributes when sek_is_img_smartload_enabled() // adds the parallax attributes // img smartload can be set globally with 'global-img-smart-load' and locally with 'local-img-smart-load' // the local option wins // deactivated when customizing @see function sek_is_img_smartload_enabled() function sek_maybe_add_bg_attributes( $model ) { $new_attributes = []; $bg_img_url = ''; $parallax_enabled = false; $fixed_bg_enabled = false; $width = ''; $height = ''; $level_type = array_key_exists( 'level', $model ) ? $model['level'] : 'section'; // will be used for sections (not columns and modules ) that have a video background // implemented for video bg https://github.com/presscustomizr/nimble-builder/issues/287 $video_bg_url = ''; $video_bg_loop = true; $video_bg_delay_before_start = null; $video_bg_on_mobile = false; $video_bg_start_time = null; $video_bg_end_time = null; if ( !empty( $model[ 'options' ] ) && is_array( $model['options'] ) ) { $bg_options = ( !empty( $model[ 'options' ][ 'bg' ] ) && is_array( $model[ 'options' ][ 'bg' ] ) ) ? $model[ 'options' ][ 'bg' ] : array(); $use_post_thumbnail_bg = !empty( $bg_options['bg-use-post-thumb'] ) && sek_is_checked( $bg_options['bg-use-post-thumb'] ); if ( !empty( $bg_options[ 'bg-image'] ) || $use_post_thumbnail_bg ) { $bg_image_id_or_url = ''; // Feb 2021 // First check if user wants to use the contextual post thumbnail // Fallback on the regular image background if not if ( $use_post_thumbnail_bg ) { $current_post_id = sek_get_post_id_on_front_and_when_customizing(); $bg_image_id_or_url = ( has_post_thumbnail( $current_post_id ) ) ? get_post_thumbnail_id( $current_post_id ) : $bg_image_id_or_url; } if ( empty($bg_image_id_or_url) ) { $bg_image_id_or_url = $bg_options[ 'bg-image']; } // April 2020 : // on import, user can decide to use the image url instead of importing // we need to check if the image is set as an attachement id or starts with 'http' // introduced for https://github.com/presscustomizr/nimble-builder/issues/663 if ( is_numeric( $bg_image_id_or_url ) ) { $bg_img_url = wp_get_attachment_url( $bg_image_id_or_url ); } else if ( "http" === substr( $bg_image_id_or_url, 0, 4 ) ) { $bg_img_url = $bg_image_id_or_url; } // At this point we may not have a valid $bg_img_url // let's check if ( !empty( $bg_img_url ) ) { $new_attributes['data-sek-has-bg'] = 'true'; if ( defined('DOING_AJAX') && DOING_AJAX ) { $new_attributes['style'] = sprintf('background-image:url(\'%1$s\');', esc_url( $bg_img_url )); } else { $new_attributes['data-sek-src'] = sprintf( '%1$s', esc_url($bg_img_url) ); if ( sek_is_img_smartload_enabled() ) { $new_attributes['data-sek-lazy-bg'] = 'true'; } } // When the fixed background is ckecked, it wins against parallax $fixed_bg_enabled = !empty( $bg_options['bg-attachment'] ) && sek_booleanize_checkbox_val( $bg_options['bg-attachment'] ); $parallax_enabled = !$fixed_bg_enabled && !empty( $bg_options['bg-parallax'] ) && sek_booleanize_checkbox_val( $bg_options['bg-parallax'] ); if ( $parallax_enabled && is_numeric( $bg_image_id_or_url ) ) { $image = wp_get_attachment_image_src( $bg_image_id_or_url, 'full' ); if ( $image ) { list( $src, $width, $height ) = $image; } } } } // Nov 2019, for video background https://github.com/presscustomizr/nimble-builder/issues/287 // should be added for sections and columns only if ( in_array( $level_type, array( 'section', 'column') ) && !empty( $bg_options[ 'bg-use-video'] ) && sek_booleanize_checkbox_val( $bg_options[ 'bg-use-video'] ) ) { if ( !empty( $bg_options[ 'bg-video' ] ) ) { $video_bg_url = $bg_options[ 'bg-video' ]; // replace http by https if needed for mp4 video url // fixes https://github.com/presscustomizr/nimble-builder/issues/550 if ( is_ssl() && is_string($video_bg_url) && stripos($video_bg_url, 'http://') === 0 ) { $video_bg_url = 'https' . substr($video_bg_url, 4); } } if ( array_key_exists( 'bg-video-loop', $bg_options ) ) { $video_bg_loop = sek_booleanize_checkbox_val( $bg_options[ 'bg-video-loop' ] ); } if ( !empty( $bg_options[ 'bg-video-delay-start' ] ) ) { $video_bg_delay_before_start = abs( (int)$bg_options[ 'bg-video-delay-start' ] ); } if ( array_key_exists( 'bg-video-on-mobile', $bg_options ) ) { $video_bg_on_mobile = sek_booleanize_checkbox_val( $bg_options[ 'bg-video-on-mobile' ] ); } if ( !empty( $bg_options[ 'bg-video-start-time' ] ) ) { $video_bg_start_time = abs( (int)$bg_options[ 'bg-video-start-time' ] ); } if ( !empty( $bg_options[ 'bg-video-end-time' ] ) ) { $video_bg_end_time = abs( (int)$bg_options[ 'bg-video-end-time' ] ); } } } // data-sek-bg-fixed attribute has been added for https://github.com/presscustomizr/nimble-builder/issues/414 // @see css rules related // we can't have both fixed and parallax option together // when the fixed background is ckecked, it wins against parallax if ( $fixed_bg_enabled ) { $new_attributes['data-sek-bg-fixed'] = 'true'; } else if ( $parallax_enabled ) { $new_attributes['data-sek-bg-parallax'] = 'true'; $new_attributes['data-bg-width'] = $width; $new_attributes['data-bg-height'] = $height; $new_attributes['data-sek-parallax-force'] = array_key_exists('bg-parallax-force', $bg_options) ? $bg_options['bg-parallax-force'] : '40'; } // video background insertion can only be done for sections and columns if ( in_array( $level_type, array( 'section', 'column') ) ) { if ( !empty( $video_bg_url ) && is_string( $video_bg_url ) ) { $new_attributes['data-sek-video-bg-src'] = esc_url( $video_bg_url ); $new_attributes['data-sek-video-bg-loop'] = $video_bg_loop ? 'true' : 'false'; if ( !is_null( $video_bg_delay_before_start ) && $video_bg_delay_before_start >= 0 ) { $new_attributes['data-sek-video-delay-before'] = $video_bg_delay_before_start; } $new_attributes['data-sek-video-bg-on-mobile'] = $video_bg_on_mobile ? 'true' : 'false'; if ( !is_null( $video_bg_start_time ) && $video_bg_start_time >= 0 ) { $new_attributes['data-sek-video-start-at'] = $video_bg_start_time; } if ( !is_null( $video_bg_end_time ) && $video_bg_end_time >= 0 ) { $new_attributes['data-sek-video-end-at'] = $video_bg_end_time; } } } return $new_attributes; } // @filter nimble_parse_for_smart_load // this filter is used in several modules : tiny_mce_editor, image module, post grid // img smartload can be set globally with 'global-img-smart-load' and locally with 'local-img-smart-load' // deactivated when customizing @see function sek_is_img_smartload_enabled() // @return html string function sek_maybe_process_img_for_js_smart_load( $html ) { // if ( skp_is_customizing() || !sek_is_img_smartload_enabled() ) // return $html; // Disable smart load parsing when building in the customizer if ( defined('DOING_AJAX') && DOING_AJAX ) { return $html; } // prevent lazyloading images when in header section // @see https://github.com/presscustomizr/nimble-builder/issues/705 if ( Nimble_Manager()->current_location_is_header ) return $html; if ( !sek_is_img_smartload_enabled() ) return $html; if ( !is_string( $html ) ) { sek_error_log( __CLASS__ . '::' . __FUNCTION__ . ' Error => provided html is not a string', $html ); return $html; } if ( is_feed() || is_preview() ) return $html; $allowed_image_extensions = apply_filters( 'nimble_smartload_allowed_img_extensions', array( 'bmp', 'gif', 'jpeg', 'jpg', 'jpe', 'tif', 'tiff', 'ico', 'png', 'svg', 'svgz' ) ); if ( empty( $allowed_image_extensions ) || !is_array( $allowed_image_extensions ) ) { return $html; } $img_extensions_pattern = sprintf( "(?:%s)", implode( '|', $allowed_image_extensions ) ); $pattern = '#]+?)src=[\'"]?([^\'"\s>]+\.'.$img_extensions_pattern.'[^\'"\s>]*)[\'"]?([^>]*)>#i'; return preg_replace_callback( $pattern, '\Nimble\nimble_regex_callback', $html); } //////////////////////////////////////////////////////////////// // SETUP CONTENT FILTERS FOR TINYMCE MODULE // Fired in the constructor private function sek_setup_tiny_mce_content_filters() { // @see filters in wp-includes/default-filters.php // always check if 'do_blocks' exists for retrocompatibility with WP < 5.0. @see https://github.com/presscustomizr/nimble-builder/issues/237 if ( function_exists( 'do_blocks' ) ) { add_filter( 'the_nimble_tinymce_module_content', 'do_blocks', 9 ); } add_filter( 'the_nimble_tinymce_module_content', 'wptexturize' ); add_filter( 'the_nimble_tinymce_module_content', 'convert_smilies', 20 ); add_filter( 'the_nimble_tinymce_module_content', 'wpautop' ); add_filter( 'the_nimble_tinymce_module_content', 'shortcode_unautop' ); // april 2021 => 'prepend_attachment' is normally in the list of default-filters for the_content. // it is used to Wrap attachment in paragraph tag before content. ( see wp-includes/post-templates.php ) // NB doesn't need it and it can break {{the_title}} template tag when used in an attachment page. //add_filter( 'the_nimble_tinymce_module_content', 'prepend_attachment' ); // July 2020 : compatibility with WP 5.5 if ( function_exists('wp_filter_content_tags') ) { add_filter( 'the_nimble_tinymce_module_content', 'wp_filter_content_tags' ); } else { add_filter( 'the_nimble_tinymce_module_content', 'wp_make_content_images_responsive' ); } add_filter( 'the_nimble_tinymce_module_content', array( $this, 'sek_do_shortcode' ), 11 ); // AFTER wpautop() add_filter( 'the_nimble_tinymce_module_content', 'capital_P_dangit', 9 ); add_filter( 'the_nimble_tinymce_module_content', '\Nimble\sek_parse_template_tags', 21 ); // Hack to get the [embed] shortcode to run before wpautop() // fixes Video Embed not showing when using Add Media > Insert from Url // @see https://github.com/presscustomizr/nimble-builder/issues/250 // @see wp-includes/class-wp-embed.php add_filter( 'the_nimble_tinymce_module_content', array( $this, 'sek_run_shortcode' ), 8 ); // @see filters in wp-includes/class-wp-embed.php add_filter( 'the_nimble_tinymce_module_content', array( $this, 'sek_parse_content_for_video_embed'), 8 ); } // fired @filter the_nimble_tinymce_module_content // updated May 2020 : prevent doing shortcode when customizing // fixes https://github.com/presscustomizr/nimble-builder/issues/704 function sek_do_shortcode( $content ) { if ( !skp_is_customizing() ) { $content = do_shortcode( $content ); } else { $allow_shortcode_parsing_when_customizing = sek_booleanize_checkbox_val( get_option( NIMBLE_OPT_NAME_FOR_SHORTCODE_PARSING ) ); if ( $allow_shortcode_parsing_when_customizing ) { $content = do_shortcode( $content ); } else { global $shortcode_tags; // Find all registered tag names in $content. preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $content, $matches ); $tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] ); if ( !empty( $tagnames ) ) { $content = sprintf('
 %1$s
%2$s', __('Shortcodes are not parsed by default when customizing. You can change this setting in your WP admin > Settings > Nimble Builder options.', 'text-doma'), $content ); } } } return $content; } // fired @filter the_nimble_tinymce_module_content // updated May 2020 : prevent doing shortcode when customizing // fixes https://github.com/presscustomizr/nimble-builder/issues/704 function sek_run_shortcode( $content ) { // customizing => check if NB can parse the shortcode if ( skp_is_customizing() ) { $allow_shortcode_parsing_when_customizing = sek_booleanize_checkbox_val( get_option( NIMBLE_OPT_NAME_FOR_SHORTCODE_PARSING ) ); if ( !$allow_shortcode_parsing_when_customizing ) { return $content; } } // Not customizing always run if ( array_key_exists( 'wp_embed', $GLOBALS ) && $GLOBALS['wp_embed'] instanceof \WP_Embed ) { $content = $GLOBALS['wp_embed']->run_shortcode( $content ); } return $content; } // fired @filter the_nimble_tinymce_module_content function sek_parse_content_for_video_embed( $content ) { if ( array_key_exists( 'wp_embed', $GLOBALS ) && $GLOBALS['wp_embed'] instanceof \WP_Embed ) { $content = $GLOBALS['wp_embed']->autoembed( $content ); } return $content; } /* ------------------------------------------------------------------------- * * CONTENT, HEADER, FOOTER /* ------------------------------------------------------------------------- */ // fired @hook 'template_include' // @return template path function sek_maybe_set_local_nimble_template( $template ) { $locale_template = sek_get_locale_template(); if ( !empty( $locale_template ) ) { $template = $locale_template; } //sek_error_log( 'TEMPLATE ? => ' . did_action('wp'), $template ); return $template; } // fired @hook 'template_redirect' // fired by sek_maybe_set_local_nimble_footer() @get_footer() // fired by sek_maybe_set_local_nimble_header() @get_header() // @return void() // set the value of the properties // has_local_header_footer // has_global_header_footer function sek_maybe_set_nimble_header_footer() { if ( !did_action('nimble_front_classes_ready') || !did_action('wp') ) { sek_error_log( __FUNCTION__ . ' has been invoked too early at hook ' . current_filter() ); return; } if ( '_not_cached_yet_' === $this->has_local_header_footer || '_not_cached_yet_' === $this->has_global_header_footer ) { $local_header_footer_data = sek_get_local_option_value('local_header_footer'); $global_header_footer_data = sek_get_global_option_value('global_header_footer'); $apply_local_option = !is_null( $local_header_footer_data ) && is_array( $local_header_footer_data ) && !empty( $local_header_footer_data ) && 'inherit' !== $local_header_footer_data['header-footer']; $this->has_global_header_footer = !is_null( $global_header_footer_data ) && is_array( $global_header_footer_data ) && !empty( $global_header_footer_data['header-footer'] ) && 'nimble_global' === $global_header_footer_data['header-footer']; $this->has_local_header_footer = false; if ( $apply_local_option ) { $this->has_local_header_footer = !is_null( $local_header_footer_data ) && is_array( $local_header_footer_data ) && !empty( $local_header_footer_data['header-footer'] ) && 'nimble_local' === $local_header_footer_data['header-footer']; $this->has_global_header_footer = !is_null( $local_header_footer_data ) && is_array( $local_header_footer_data ) && !empty( $local_header_footer_data['header-footer'] ) && 'nimble_global' === $local_header_footer_data['header-footer']; } } } // fired @filter get_header() // Nimble will use an overridable template if a local or global header/footer is used // template located in /tmpl/header/ or /tmpl/footer // developers can override this template from a theme with a file that has this path : 'nimble_templates/header/nimble_header_tmpl.php function sek_maybe_set_local_nimble_header( $header_name ) { // if Nimble_Manager()->has_local_header_footer || Nimble_Manager()->has_global_header_footer if ( sek_page_uses_nimble_header_footer() ) { // load the Nimble template which includes a call to wp_head() $template_file_name_with_php_extension = 'nimble_header_tmpl.php'; $template_path = apply_filters( 'nimble_set_header_template_path', NIMBLE_BASE_PATH . "/tmpl/header/{$template_file_name_with_php_extension}", $template_file_name_with_php_extension ); // dec 2019 : can be overriden from a child theme // see https://github.com/presscustomizr/nimble-builder/issues/568 $overriden_template_path = sek_maybe_get_overriden_local_template_path( array( 'file_name' => $template_file_name_with_php_extension, 'folder' => 'header' ) ); if ( !empty( $overriden_template_path ) ) { $template_path = $overriden_template_path; } load_template( $template_path, false ); // do like in wp core get_header() $templates = array(); $header_name = (string) $header_name; if ( '' !== $header_name ) { $templates[] = "header-{$header_name}.php"; } $templates[] = 'header.php'; // don't run wp_head a second time remove_all_actions( 'wp_head' ); // capture the print and clean it. ob_start(); // won't be re-loaded by the second call performed by WP // see https://developer.wordpress.org/reference/functions/locate_template/ // and https://developer.wordpress.org/reference/functions/load_template/ locate_template( $templates, true ); ob_get_clean(); } } // fired @filter get_footer() // Nimble will use an overridable template if a local or global header/footer is used // template located in /tmpl/header/ or /tmpl/footer // developers can override this template from a theme with a file that has this path : 'nimble_templates/footer/nimble_footer_tmpl.php function sek_maybe_set_local_nimble_footer( $footer_name ) { // if Nimble_Manager()->has_local_header_footer || Nimble_Manager()->has_global_header_footer if ( sek_page_uses_nimble_header_footer() ) { // load the Nimble template which includes a call to wp_footer() $template_file_name_with_php_extension = 'nimble_footer_tmpl.php'; $template_path = apply_filters( 'nimble_set_header_template_path', NIMBLE_BASE_PATH . "/tmpl/footer/{$template_file_name_with_php_extension}", $template_file_name_with_php_extension ); // dec 2019 : can be overriden from a child theme // see https://github.com/presscustomizr/nimble-builder/issues/568 $overriden_template_path = sek_maybe_get_overriden_local_template_path( array( 'file_name' => $template_file_name_with_php_extension, 'folder' => 'footer' ) ); if ( !empty( $overriden_template_path ) ) { $template_path = $overriden_template_path; } load_template( $template_path, false ); // do like in wp core get_footer() $templates = array(); $name = (string) $footer_name; if ( '' !== $footer_name ) { $templates[] = "footer-{$footer_name}.php"; } $templates[] = 'footer.php'; // don't run wp_footer a second time remove_all_actions( 'wp_footer' ); // capture the print and clean it. ob_start(); // won't be re-loaded by the second call performed by WP // see https://developer.wordpress.org/reference/functions/locate_template/ // and https://developer.wordpress.org/reference/functions/load_template/ locate_template( $templates, true ); ob_get_clean(); } }//sek_maybe_set_local_nimble_footer // @hook wp_head // Elements of decisions for this implementation : // The problem to solve here is to add the post ( or pages ) where user has created Nimble sections for which the content matches the search term. // 1) we need a way to find the matches // 2) then to "map" the Nimble post to its related post or page // 3) then include the related post / page to the list of search result. // This can't be done by filtering the WP core query params, because Nimble sections are saved as separate posts, not post metas. // That's why the posts are added to the array of posts of the main query. // // fixes https://github.com/presscustomizr/nimble-builder/issues/439 // // May 2019 => note that this implementation won't include Nimble sections created in other contexts than page or post. // This could be added in the future. // April 2020 : the found_posts number is not correct when search results are paginated. see https://github.com/presscustomizr/nimble-builder/issues/666 // // partially inspired by https://stackoverflow.com/questions/24195818/add-results-into-wordpress-search-results function sek_maybe_include_nimble_content_in_search_results(){ if ( !is_search() ) return; global $wp_query; $query_vars = $wp_query->query_vars; if ( !is_array( $query_vars ) || empty( $query_vars['s'] ) ) return; // Search query on Nimble CPT $sek_post_query_vars = array( 'post_type' => NIMBLE_CPT, 'post_status' => 'publish',//get_post_stati(), 'posts_per_page' => -1, 'no_found_rows' => true, 'cache_results' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, 'lazy_load_term_meta' => false, 's' => $query_vars['s'] ); $query = new \WP_Query( $sek_post_query_vars ); $nimble_post_candidates = array(); // The search string has been found in a set of Nimble posts if ( is_array( $query->posts ) ) { foreach ( $query->posts as $post_object ) { // The related WP object ( == skope ) is written in the title of Nimble CPT // ex : nimble___skp__post_post_114, where 114 is the post_id if ( preg_match('(post_page|post_post)', $post_object->post_title ) ) { $_post_id = preg_replace('/[^0-9]/', '', $post_object->post_title ); $_post_id = intval($_post_id); $post_candidate = get_post( $_post_id ); if ( is_object( $post_candidate ) ) { array_push($nimble_post_candidates, $post_candidate); } } } } // april 2020 : found post for https://github.com/presscustomizr/nimble-builder/issues/666 $nimble_found_posts = (int)count($nimble_post_candidates); // Merge Nimble posts to WP posts but only on the first result page // => this means that the first paginated result page may be > to the user post_per_page setting // fixes https://github.com/presscustomizr/nimble-builder/issues/666 if ( !is_paged() ) { // important : when search results are paginated, $wp_query->posts includes the posts of the result page only, not ALL the search results posts. // => this means that $wp_query->posts is not equal to $wp_query->found_posts when results are paginated. $wp_query->posts = is_array($wp_query->posts) ? $wp_query->posts : array(); // $wp_query->post_count : make sure we remove posts found both by initial query and Nimble search query // => this way we avoid pagination problems by setting a correct value for $wp_query->post_count $maybe_includes_duplicated = array_merge( $wp_query->posts, $nimble_post_candidates ); $without_duplicated = array(); $post_ids = array(); foreach ( $maybe_includes_duplicated as $post_obj ) { if ( in_array( $post_obj->ID, $post_ids ) ) continue; $post_ids[] = $post_obj->ID; $without_duplicated[] = $post_obj; } $wp_query->posts = $without_duplicated; $wp_query->post_count = (int)count($without_duplicated); } // Found post may include duplicated posts because the search result has been found both in the WP search query and in Nimble one. // This should be improved in the future. // The problem to solve here is that when a search query is paginated, $wp_query->posts only includes the posts of the current page, not all the posts of the search results. // If we had the entire set of WP results, we could create an array merging WP results with Nimble results, remove the duplicates and then calculate a real found_posts value. A possible solution would be to get the wp_query->request, remove the limit per page, and re-run a new query to get the entire set of search results. if ( is_numeric($nimble_found_posts) ) { $wp_query->found_posts = $wp_query->found_posts + $nimble_found_posts; } }// return // @return unique guid() // inspired from https://stackoverflow.com/questions/21671179/how-to-generate-a-new-guid#26163679 // introduced for https://github.com/presscustomizr/nimble-builder/issues/494 function sek_get_preview_level_guid() { if ( '_preview_level_guid_not_set_' === $this->preview_level_guid ) { // When ajaxing, typically creating content, we need to make sure that we use the initial guid generated last time the preview was refreshed // @see preview::doAjax() if ( isset( $_POST['preview-level-guid'] ) ) { if ( empty( $_POST['preview-level-guid'] ) ) { sek_error_log( __CLASS__ . '::' . __FUNCTION__ . ' => error, preview-level-guid can not be empty' ); } $this->preview_level_guid = sanitize_text_field($_POST['preview-level-guid']); } else { $this->preview_level_guid = sprintf('%04X%04X%04X', mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535) ); } } return $this->preview_level_guid; } /* ------------------------------------------------------------------------- * * CONTENT RESTRICTION /* ------------------------------------------------------------------------- */ // fired in _schedule_front_rendering() // PASSWORD FORM AND CONTENT RESTRICTION ( PLUGINS ) // - built-in WP password protection => make the wp pwd form is rendered only one time in a singular ( see #673 and #679 ) // - membership and content restriction plugins => https://github.com/presscustomizr/nimble-builder/issues/685 function sek_schedule_content_restriction_actions() { add_action( 'wp', array( $this, 'sek_set_password_protection_status') ); // built-in WP password form add_action( 'the_password_form', array( $this, 'sek_maybe_empty_password_form' ), PHP_INT_MAX ); add_action( 'nimble_content_restriction_for_location', array( $this, 'sek_maybe_print_restriction_stuffs' ), PHP_INT_MAX ); // april 2020 : added for https://github.com/presscustomizr/nimble-builder/issues/685 // Compatibility with Members plugin : https://wordpress.org/plugins/members/ add_filter( 'nimble_is_content_restricted', array( $this, 'sek_is_content_restricted_by_members_plugin') ); // Compatibility with Paid Memberships Pro add_filter( 'nimble_is_content_restricted', array( $this, 'sek_is_content_restricted_by_paidmembershippro_plugin') ); // Compatibility with WP Members add_filter( 'nimble_is_content_restricted', array( $this, 'sek_is_content_restricted_by_wp_members_plugin') ); // Compatibility with Simple Membership plugin add_filter( 'nimble_is_content_restricted', array( $this, 'sek_is_content_restricted_by_simple_membership_plugin') ); // Compatibility with premium Memberpress plugin http://www.memberpress.com/ add_filter( 'nimble_is_content_restricted', array( $this, 'sek_is_content_restricted_by_memberpress_plugin') ); } // hook : 'wp' // april 2020 for #673 and #679 // Never restrict when customizing // @return void function sek_set_password_protection_status() { if ( skp_is_customizing() ) { Nimble_Manager()->is_content_restricted = false; } else { // the default restriction status is the one provided by the built-in WP password protection // the filter allows us to add compatibility with other membership or content restriction plugins Nimble_Manager()->is_content_restricted = apply_filters('nimble_is_content_restricted', is_singular() && post_password_required() ); } } // hook : 'the_password_form'@PHP_INT_MAX documented in wp-includes/post-template.php // Empty the password form if it's been already rendered, either in the WP content or in a Nimble location before the content. // april 2020 for see #673 and #679 // @return html output for the form function sek_maybe_empty_password_form( $output ) { // bail if there's no local Nimble section in the page if ( !sek_local_skope_has_nimble_sections( skp_get_skope_id() ) ) return $output; if ( skp_is_customizing() || !post_password_required() ) return $output; if ( is_singular() && post_password_required() ) { if ( !did_action('nimble_wp_pwd_form_rendered') ) { // fire an action => we know the password form has been rendered so we won't have to render it several times // see ::render() location do_action('nimble_wp_pwd_form_rendered'); return $output; } else { // Empty the form if it's been already rendered, either in the WP content or in a Nimble location before the content. return ''; } } return $output; } // hook : 'nimble_is_content_restricted' // Compatibility with Members plugin : https://wordpress.org/plugins/members/ // for #685 function sek_is_content_restricted_by_members_plugin( $bool ) { if ( !function_exists('members_can_current_user_view_post') || !is_singular() ) { return $bool; } return !members_can_current_user_view_post( get_the_ID() ); } // hook : 'nimble_is_content_restricted' // Compatibility with Paid Membership Pro plugin // for #685 function sek_is_content_restricted_by_paidmembershippro_plugin( $bool ) { if ( !function_exists('pmpro_has_membership_access') ) return $bool; $hasaccess = pmpro_has_membership_access( NULL, NULL, true ); if ( is_array( $hasaccess ) ){ $hasaccess = $hasaccess[0]; } return !$hasaccess; } // hook : 'nimble_is_content_restricted' // Compatibility with Simple WP Membership Protection plugin // for #685 function sek_is_content_restricted_by_simple_membership_plugin( $bool ) { if ( !class_exists('\SwpmAccessControl') || !class_exists('\SwpmUtils') || !is_singular() ) return $bool; $acl = \SwpmAccessControl::get_instance(); global $post; if ( $acl->expired_user_has_access_to_this_page() ) { return false; } $content = ''; if( \SwpmUtils::is_first_click_free($content) ) { return false; } if ( !method_exists($acl, 'can_i_read_post') ) return false; if( $acl->can_i_read_post($post) ) { return false; } // Content is protected return true; } // hook : 'nimble_is_content_restricted' // Compatibility with WP Members plugin : https://wordpress.org/plugins/wp-members/ // for #685 function sek_is_content_restricted_by_wp_members_plugin( $bool ) { if ( !function_exists('wpmem_is_blocked') || !is_singular() ) { return $bool; } return !is_user_logged_in() && wpmem_is_blocked( get_the_ID() ); } // hook : 'nimble_is_content_restricted' // following #685 function sek_is_content_restricted_by_memberpress_plugin( $bool ) { if ( !defined('MEPR_VERSION') ) return $bool; return !current_user_can('mepr-auth'); } // hook : 'nimble_content_restriction_for_location' // april 2020 : added for https://github.com/presscustomizr/nimble-builder/issues/685 function sek_maybe_print_restriction_stuffs( $location_model ) { if ( !Nimble_Manager()->is_content_restricted ) return; if ( post_password_required() ) { echo get_the_password_form();//<= we filter the output of this function to maybe empty and fire the action 'nimble_wp_pwd_form_rendered' } // 1) Compatibility with Members plugin : https://wordpress.org/plugins/members/ if ( function_exists('members_can_current_user_view_post') ) { $post_id = get_the_ID(); if ( !members_can_current_user_view_post( $post_id ) && function_exists('members_get_post_error_message') ) { echo wp_kses_post(members_get_post_error_message( $post_id )); } // 2) for other plugins, if not printed already, print a default fitrable message } else if ( !did_action('nimble_after_restricted_content_html') ) { echo apply_filters('nimble_restricted_content_html', sprintf( '

%1$s

', __('You need to login to view this content.', 'text_doma') ) ); do_action('nimble_after_restricted_content_html'); } } }//class endif; ?> // - we set to hook to wp_head // // when the method is fired in an ajax refresh scenario, like 'sek-refresh-stylesheet' // - the skope_id must be passed as param // - the css rules are printed inline in the // - we set the hook to '' // AJAX REQUESTED STYLESHEET if ( ( !is_null( $skope_id ) && !empty( $skope_id ) ) && ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) { if ( !isset($_POST['local_skope_id']) ) { sek_error_log( __CLASS__ . '::' . __FUNCTION__ . ' => error missing local_skope_id'); return; } $local_skope_id = sanitize_text_field($_POST['local_skope_id']); // Feb 2021 => for site template #478 $local_skope_id = apply_filters( 'nb_set_skope_id_before_generating_local_front_css', $local_skope_id ); $css_handler_instance = $this->_instantiate_css_handler( array( 'skope_id' => $skope_id, 'is_global_stylesheet' => NIMBLE_GLOBAL_SKOPE_ID === $skope_id ) ); $this->sek_get_global_css_for_ajax(); } // in a front normal context, the css is enqueued from the already written file. else { // Feb 2021 => for site template #478 $local_skope_id = apply_filters( 'nb_set_skope_id_before_generating_local_front_css', skp_build_skope_id() ); // LOCAL SECTIONS STYLESHEET $this->_instantiate_css_handler( array( 'skope_id' => $local_skope_id ) ); // GLOBAL SECTIONS STYLESHEET // Can hold rules for global sections and global styling $this->_instantiate_css_handler( array( 'skope_id' => NIMBLE_GLOBAL_SKOPE_ID, 'is_global_stylesheet' => true ) ); } $google_fonts_print_candidates = $this->sek_get_gfont_print_candidates( $local_skope_id ); // GOOGLE FONTS if ( !empty( $google_fonts_print_candidates ) ) { // When customizing we get the google font content if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { $this->sek_get_gfont_in_ajax( $google_fonts_print_candidates ); } else { // preload implemented for https://github.com/presscustomizr/nimble-builder/issues/629 if ( !skp_is_customizing() && sek_preload_google_fonts_on_front() ) { add_action( 'wp_head', array( $this, 'sek_gfont_print_with_preload') ); } else { // March 2020 added param display=swap => Ensure text remains visible during webfont load #572 wp_enqueue_style( NIMBLE_GOOGLE_FONTS_STYLESHEET_ID, sprintf( '//fonts.googleapis.com/css?family=%s&display=swap', $google_fonts_print_candidates ), array(), null, 'all' ); } } } if ( defined( 'DOING_AJAX' ) && DOING_AJAX && empty( $local_skope_id ) ) { sek_error_log( __CLASS__ . '::' . __FUNCTION__ . ' => the skope_id should not be empty' ); } }//print_or_enqueue_seks_style // @param params = array( array( 'skope_id' => NIMBLE_GLOBAL_SKOPE_ID, 'is_global_stylesheet' => true ) ) // fired @'wp_enqueue_scripts' private function _instantiate_css_handler( $params = array() ) { $params = wp_parse_args( $params, array( 'skope_id' => '', 'is_global_stylesheet' => false ) ); // Print inline or enqueue ? $print_mode = Sek_Dyn_CSS_Handler::MODE_FILE; if ( is_customize_preview() ) { $print_mode = Sek_Dyn_CSS_Handler::MODE_INLINE; } // Which hook ? $fire_at_hook = ''; if ( !defined( 'DOING_AJAX' ) && is_customize_preview() ) { $fire_at_hook = 'wp_head'; } $css_handler_instance = new Sek_Dyn_CSS_Handler( array( 'id' => $params['skope_id'], 'skope_id' => $params['skope_id'], // property "is_global_stylesheet" has been added when fixing https://github.com/presscustomizr/nimble-builder/issues/273 'is_global_stylesheet' => $params['is_global_stylesheet'], 'mode' => $print_mode, //these are taken in account only when 'mode' is 'file' 'force_write' => true, //<- write if the file doesn't exist 'force_rewrite' => is_user_logged_in() && current_user_can( 'customize' ), //<- write even if the file exists 'hook' => $fire_at_hook )); return $css_handler_instance; } // When ajaxing, the link#sek-gfonts-{$this->id} gets removed from the dom and replaced by this string // March 2020 added param display=swap => Ensure text remains visible during webfont load #572 function sek_get_gfont_in_ajax( $print_candidates ) { if ( !empty( $print_candidates ) ) { printf('', esc_attr(NIMBLE_GOOGLE_FONTS_STYLESHEET_ID), esc_url("//fonts.googleapis.com/css?family={$print_candidates}&display=swap") ); } } // hook : wp_footer // fired on front only when not customizing // March 2020 preload implemented for https://github.com/presscustomizr/nimble-builder/issues/629 // March 2020 added param display=swap => Ensure text remains visible during webfont load #572 function sek_gfont_print_with_preload( $print_candidates = '' ) { // Check that current page has Nimble content before printing any Google fonts // For https://github.com/presscustomizr/nimble-builder/issues/649 if ( !Nimble_Manager()->page_has_local_or_global_sections ) return; // print candidates must be fetched when sek_preload_google_fonts_on_front() $print_candidates = $this->sek_get_gfont_print_candidates(); if ( !empty( $print_candidates ) ) { ob_start(); ?> nb_.preloadOrDeferAsset( { id : '', as : 'style', href : '//fonts.googleapis.com/css?family=&display=swap', scriptEl : document.currentScript } ); sek_build_global_options_inline_css(); if ( is_string( $global_css ) && !empty( $global_css ) ) { printf('', NIMBLE_GLOBAL_OPTIONS_STYLESHEET_ID, $global_css ); } } } // hook wp_enqueue_script function sek_enqueue_global_css() { $global_css = get_option(NIMBLE_OPT_FOR_GLOBAL_CSS); // following https://developer.wordpress.org/reference/functions/wp_add_inline_script/#comment-5304 wp_register_style( NIMBLE_GLOBAL_OPTIONS_STYLESHEET_ID, false ); wp_enqueue_style( NIMBLE_GLOBAL_OPTIONS_STYLESHEET_ID ); wp_add_inline_style( NIMBLE_GLOBAL_OPTIONS_STYLESHEET_ID, $global_css ); } // Maybe update global inline style with a filter // @return a css string // This CSS is the one generated by global options like global text, global width, global breakpoint function sek_build_global_options_inline_css() { return apply_filters('nimble_set_global_inline_style', ''); } //@return string // sek_model is passed when customizing in SEK_Front_Render_Css::print_or_enqueue_seks_style() function sek_get_gfont_print_candidates( $local_skope_id = null ) { // return the cache version if already set if ( 'not_set' !== Nimble_Manager()->google_fonts_print_candidates ) return Nimble_Manager()->google_fonts_print_candidates; $local_skope_id = is_null( $local_skope_id ) ? apply_filters( 'maybe_set_skope_id_for_site_template_css', skp_build_skope_id() ) : $local_skope_id; // local sections $local_seks = sek_get_skoped_seks( $local_skope_id ); // global sections $global_seks = sek_get_skoped_seks( NIMBLE_GLOBAL_SKOPE_ID ); // global options $global_options = get_option( NIMBLE_OPT_NAME_FOR_GLOBAL_OPTIONS ); $print_candidates = ''; $ffamilies = array(); // Let's build the collection of google fonts from local sections, global sections, global options if ( is_array( $local_seks ) && !empty( $local_seks['fonts'] ) && is_array( $local_seks['fonts'] ) ) { $ffamilies = $local_seks['fonts']; } if ( is_array( $global_seks ) && !empty( $global_seks['fonts'] ) && is_array( $global_seks['fonts'] ) ) { $ffamilies = array_merge( $ffamilies, $global_seks['fonts'] ); } if ( is_array( $global_options ) && !empty( $global_options['fonts'] ) && is_array( $global_options['fonts'] ) ) { $ffamilies = array_merge( $ffamilies, $global_options['fonts'] ); } // remove duplicate if any $ffamilies = array_unique( $ffamilies ); if ( !empty( $ffamilies ) ) { $ffamilies = implode( "|", $ffamilies ); $print_candidates = str_replace( '|', '%7C', $ffamilies ); $print_candidates = str_replace( '[gfont]', '' , $print_candidates ); } // cache now Nimble_Manager()->google_fonts_print_candidates = $print_candidates; return Nimble_Manager()->google_fonts_print_candidates; } }//class endif; ?>form_composition = array( 'nimble_simple_cf' => array( 'type' => 'hidden', 'value' => 'nimble_simple_cf' ), 'nimble_recaptcha_resp' => array( 'type' => 'hidden', 'value' => '' ), 'nimble_skope_id' => array( 'type' => 'hidden', 'value' => '' ), 'nimble_level_id' => array( 'type' => 'hidden', 'value' => '' ), 'nimble_name' => array( 'label' => __( 'Name', 'text_doma' ), 'required' => true, 'type' => 'text', 'wrapper_tag' => 'div' ), 'nimble_email' => array( 'label' => __( 'Email', 'text_doma' ), 'required' => true, 'type' => 'email', 'wrapper_tag' => 'div' ), 'nimble_subject' => array( 'label' => __( 'Subject', 'text_doma' ), 'type' => 'text', 'wrapper_tag' => 'div' ), 'nimble_message' => array( 'label' => __( 'Message', 'text_doma' ), 'required' => true, 'additional_attrs' => array( 'rows' => "10", 'cols' => "50" ), 'type' => 'textarea', 'wrapper_tag' => 'div' ), 'nimble_privacy' => array( 'label' => __( 'I have read and agree to the privacy policy.', 'text_doma' ), 'type' => 'checkbox', 'required' => true, 'value' => false, //'additional_attrs' => array( 'class' => 'sek-btn' ), 'wrapper_tag' => 'div', 'wrapper_class' => array( 'sek-form-field', 'sek-privacy-wrapper' ) ), 'nimble_submit' => array( 'type' => 'submit', 'value' => __( 'Submit', 'text_doma' ), 'additional_attrs' => array( 'class' => 'sek-btn' ), 'wrapper_tag' => 'div', 'wrapper_class' => array( 'sek-form-field', 'sek-form-btn-wrapper' ) ) ); }//_setup_simple_forms //@hook: parse_request function simple_form_parse_request() { if ( !isset( $_POST['nimble_simple_cf'] ) ) return; // get the module options // we are before 'wp', so let's use the posted skope_id and level_id to get our $module_user_values $module_model = array(); if ( isset( $_POST['nimble_skope_id'] ) && '_skope_not_set_' !== sanitize_text_field($_POST['nimble_skope_id']) ) { $local_sektions = sek_get_skoped_seks( sanitize_text_field($_POST['nimble_skope_id']) ); if ( is_array( $local_sektions ) && !empty( $local_sektions ) ) { $sektion_collection = array_key_exists('collection', $local_sektions) ? $local_sektions['collection'] : array(); } if ( is_array($sektion_collection) && !empty( $sektion_collection ) && isset( $_POST['nimble_level_id'] ) ) { $module_model = sek_get_level_model( sanitize_text_field($_POST['nimble_level_id']), $sektion_collection ); $module_model = sek_normalize_module_value_with_defaults( $module_model ); } } else { sek_error_log( __FUNCTION__ . ' => skope_id problem'); return; } if ( empty( $module_model ) ) { sek_error_log( __FUNCTION__ . ' => invalid module model'); return; } //update the form with the posted values foreach ( $this->form_composition as $name => $field ) { $form_composition[ $name ] = $field; if ( isset( $_POST[ $name ] ) ) { $form_composition[ $name ][ 'value' ] = sanitize_text_field($_POST[ $name ]); } } //set the form composition according to the user's options $form_composition = $this->_set_form_composition( $form_composition, $module_model ); //generate fields $this->fields = $this->simple_form_generate_fields( $form_composition ); //generate form $this->form = $this->simple_form_generate_form( $this->fields, $module_model ); //mailer $this->mailer = new Sek_Mailer( $this->form ); $this->mailer->maybe_send( $form_composition, $module_model ); } // Fired @hook wp_enqueue_scripts // @return void() function maybe_enqueue_recaptcha_scripts() { // enabled if // - not customizing // - global 'recaptcha' options has the following values // - enabled === true // - public_key entered // - private_key entered // - the current page does not include a form in a local or global location if ( skp_is_customizing() || !sek_is_recaptcha_globally_enabled() || !sek_front_sections_include_a_form() ) return; // @todo, we don't handle the case when reCaptcha is globally enabled but disabled for a particular form. $global_recaptcha_opts = sek_get_global_option_value('recaptcha'); $global_recaptcha_opts = is_array( $global_recaptcha_opts ) ? $global_recaptcha_opts : array(); $url = add_query_arg( array( 'render' => esc_attr( $global_recaptcha_opts['public_key'] ) ), 'https://www.google.com/recaptcha/api.js' ); wp_enqueue_script( 'google-recaptcha', $url, array(), '3.0', true ); add_action('wp_head', array( $this, 'print_recaptcha_inline_js'), PHP_INT_MAX ); } // @hook wp_footer // printed only when sek_is_recaptcha_globally_enabled() // AND // sek_front_sections_include_a_form() function print_recaptcha_inline_js() { ob_start(); ?> if ( sekFrontLocalized.recaptcha_public_key ) { !( function( grecaptcha, sitekey ) { var recaptcha = { execute: function() { var _action = ( window.sekFrontLocalized && sekFrontLocalized.skope_id ) ? sekFrontLocalized.skope_id.replace( 'skp__' , 'nimble_form__' ) : 'nimble_builder_form'; grecaptcha.execute( sitekey, // see https://developers.google.com/recaptcha/docs/v3#actions { action: _action } ).then( function( token ) { var forms = document.getElementsByTagName( 'form' ); for ( var i = 0; i < forms.length; i++ ) { var fields = forms[ i ].getElementsByTagName( 'input' ); for ( var j = 0; j < fields.length; j++ ) { var field = fields[ j ]; if ( 'nimble_recaptcha_resp' === field.getAttribute( 'name' ) ) { field.setAttribute( 'value', token ); break; } } } } ); } }; grecaptcha.ready( recaptcha.execute ); })( grecaptcha, sekFrontLocalized.recaptcha_public_key ); } else { if ( window.console && window.console.log ) { console.log('Nimble Builder form error => missing reCAPTCHA key'); } } fields ?', $this->fields ); // sek_error_log('$this->form ?', $this->form ); // sek_error_log('$this->mailer ?', $this->mailer ); // sek_error_log('$_POST ?', $_POST ); $html = ''; //set the form composition according to the user's options $form_composition = $this->_set_form_composition( $this->form_composition, $module_model ); //generate fields $fields = isset( $this->fields ) ? $this->fields : $this->simple_form_generate_fields( $form_composition ); //generate form $form = isset( $this->form ) ? $this->form : $this->simple_form_generate_form( $fields, $module_model ); $module_id = is_array( $module_model ) && array_key_exists('id', $module_model ) ? $module_model['id'] : ''; ob_start(); ?>
mailer ) ) { // Make sure we target the right form if several forms are displayed in a page $current_form_has_been_submitted = isset( $_POST['nimble_level_id'] ) && sanitize_text_field($_POST['nimble_level_id']) === $module_id; if ( in_array( $this->mailer->get_status(), ['sent', 'not_sent', 'aborted'] ) && $current_form_has_been_submitted ) { $echo_form = false; } } if ( !$echo_form ) { ob_start(); ?> nb_.listenTo( 'nb-jquery-loaded', function() { jQuery( function($) { var $elToFocusOn = $('div[data-sek-id=""]' ); if ( $elToFocusOn.length > 0 ) { var _do = function() { $('html, body').animate({ scrollTop : $elToFocusOn.offset().top - ( $(window).height() / 2 ) + ( $elToFocusOn.outerHeight() / 2 ) }, 'slow'); }; try { _do(); } catch(er) {} } }); }); mailer->get_message( $this->mailer->get_status(), $module_model ); if ( !empty($message) ) { $class = 'sek-mail-failure'; switch( $this->mailer->get_status() ) { case 'sent' : $class = 'sek-mail-success'; break; case 'not_sent' : $class = ''; break; case 'aborted' : $class = 'sek-mail-aborted'; break; } printf( '
%2$s
', esc_attr($class), wp_kses_post($message) ); } } else { // If we're in the regular case ( not after submission ), echo the form echo $form;//The output is late escaped in tmpl/modules/simple_form_module_tmpl.php, as this function only returns the html content } ?>
ERROR : invalid module options array'); return $user_form_composition; } $module_user_values = array_key_exists( 'value', $module_model ) ? $module_model['value'] : array(); //sek_error_log( '$module_model', $module_model ); $form_fields_options = empty( $module_user_values['form_fields'] ) ? array() : $module_user_values['form_fields']; $form_button_options = empty( $module_user_values['form_button'] ) ? array() : $module_user_values['form_button']; $form_submission_options = empty( $module_user_values['form_submission'] ) ? array() : $module_user_values['form_submission']; foreach ( $form_composition as $field_id => $field_data ) { //sek_error_log( '$field_data', $field_data ); switch ( $field_id ) { case 'nimble_name': if ( !empty( $form_fields_options['show_name_field'] ) && sek_is_checked( $form_fields_options['show_name_field'] ) ) { $user_form_composition[$field_id] = $field_data; $user_form_composition[$field_id]['required'] = sek_is_checked( $form_fields_options['name_field_required'] ); $user_form_composition[$field_id]['label'] = esc_attr( $form_fields_options['name_field_label'] ); } break; case 'nimble_subject': if ( !empty( $form_fields_options['show_subject_field'] ) && sek_is_checked( $form_fields_options['show_subject_field'] ) ) { $user_form_composition[$field_id] = $field_data; $user_form_composition[$field_id]['required'] = sek_is_checked( $form_fields_options['subject_field_required'] ); $user_form_composition[$field_id]['label'] = esc_attr( $form_fields_options['subject_field_label'] ); } break; case 'nimble_message': if ( !empty( $form_fields_options['show_message_field'] ) && sek_is_checked( $form_fields_options['show_message_field'] ) ) { $user_form_composition[$field_id] = $field_data; $user_form_composition[$field_id]['required'] = sek_is_checked( $form_fields_options['message_field_required'] ); $user_form_composition[$field_id]['label'] = esc_attr( $form_fields_options['message_field_label'] ); } break; case 'nimble_email': $user_form_composition[$field_id] = $field_data; $user_form_composition[$field_id]['label'] = esc_attr( $form_fields_options['email_field_label'] ); break; case 'nimble_privacy': if ( !empty( $form_fields_options['show_privacy_field'] ) && sek_is_checked( $form_fields_options['show_privacy_field'] ) ) { $user_form_composition[$field_id] = $field_data; $user_form_composition[$field_id]['required'] = sek_is_checked( $form_fields_options['privacy_field_required'] ); // prevent users running script in this field while customizing $user_form_composition[$field_id]['label'] = sek_strip_script_tags_and_print_js_inline( $form_fields_options['privacy_field_label'], $module_model ); // Feb 2021 : now saved as a json to fix emojis issues // see fix for https://github.com/presscustomizr/nimble-builder/issues/544 // to ensure retrocompatibility with data previously not saved as json, we need to perform a json validity check $user_form_composition[$field_id]['label'] = sek_maybe_decode_richtext( $user_form_composition[$field_id]['label'] ); } break; //'additional_attrs' => array( 'class' => 'sek-btn' ), case 'nimble_submit': $user_form_composition[$field_id] = $field_data; $visual_effect_class = ''; //visual effect classes if ( array_key_exists( 'use_box_shadow', $form_button_options ) && true === sek_booleanize_checkbox_val( $form_button_options['use_box_shadow'] ) ) { $visual_effect_class = ' box-shadow'; if ( array_key_exists( 'push_effect', $form_button_options ) && true === sek_booleanize_checkbox_val( $form_button_options['push_effect'] ) ) { $visual_effect_class .= ' push-effect'; } } $user_form_composition[$field_id]['additional_attrs']['class'] = 'sek-btn' . $visual_effect_class; // Feb 2021 : now saved as a json to fix emojis issues // see fix for https://github.com/presscustomizr/nimble-builder/issues/544 // to ensure retrocompatibility with data previously not saved as json, we need to perform a json validity check $user_form_composition[$field_id]['value'] = sek_maybe_decode_richtext( $form_fields_options['button_text'] ); break; case 'nimble_skope_id': $user_form_composition[$field_id] = $field_data; // When the form is submitted, we grab the skope_id from the posted value, because it is too early to build it. // of course we don't need to set this input value when customizing. $skope_id = ''; if ( !skp_is_customizing() ) { $skope_id = isset( $_POST['nimble_skope_id'] ) ? sanitize_text_field($_POST['nimble_skope_id']) : sek_get_level_skope_id( $module_model['id'] ); } // always use the posted skope_id // => in a scenario in which we post the form several times, the skp_get_skope_id() won't be available after the first submit action $user_form_composition[$field_id]['value'] = $skope_id; break; case 'nimble_level_id': $user_form_composition[$field_id] = $field_data; $user_form_composition[$field_id]['value'] = $module_model['id']; break; // print the recaptcha input field if // 1) reCAPTCHA enabled in the global options AND properly setup with non empty keys // 2) reCAPTCHA enabled for this particular form case 'nimble_recaptcha_resp' : if ( !skp_is_customizing() && sek_is_recaptcha_globally_enabled() && 'disabled' !== $form_submission_options['recaptcha_enabled'] ) { $user_form_composition[$field_id] = $field_data; } break; default: $user_form_composition[$field_id] = $field_data; break; } } return $user_form_composition; } //generate the fields function simple_form_generate_fields( $form_composition = array() ) { $form_composition = empty( $form_composition ) ? $this->form_composition : $form_composition; $fields_ = array(); $id_suffix = rand(); foreach ( $form_composition as $name => $field ) { $field = wp_parse_args( $field, array( 'type' => 'text' ) ); if ( class_exists( $class = '\Nimble\Sek_Input_' . ucfirst( $field['type'] ) ) ) { $fields_ [] = new Sek_Field ( new $class( array_merge( array( 'id_suffix'=> $id_suffix ), $field, array( 'name' => $name ) ) ), $field ); } } return $fields_; } //generate the fields function simple_form_generate_form( $fields, $module_model ) { $form = new Sek_Form( [ 'action' => is_array( $module_model ) && !empty( $module_model['id']) ? '#' . $module_model['id'] :'#', 'method' => 'post' ] ); $form->add_fields( $fields ); return $form; } }//Sek_Simple_Form endif; ?> is_array( $module_model ) && !empty( $module_model['id']) ? $module_model['id'] :'#', // 'method' => 'post' // ] ); public function __construct( $args = array() ) { $this->fields = array(); $this->attributes = wp_parse_args( $args, array( 'action' => '#',// the action attribute doesn't have to be specified when form data is sent to the same page // see https://developer.mozilla.org/en-US/docs/Learn/Forms/Sending_and_retrieving_form_data, // for the moment we use the parent module id, example : #__nimble__cd4d5b307a3b 'method' => 'post' //TODO: add html callback ) ); } public function add_field( Sek_Field $field ) { $this->fields[ sanitize_key( $field->get_input()->get_data('name') ) ] = $field; } public function add_fields( array $fields ) { foreach ($fields as $field) { $this->add_field( $field ); } } public function get_fields() { return $this->fields; } public function get_field( $field_name ) { if ( is_array( $this->fields ) && array_key_exists(sanitize_key( $field_name ), $this->fields) ) { return $this->fields[ sanitize_key( $field_name ) ]; } return null; } //make sure fields are well formed public function has_invalid_field() { $has_invalid_field = false; foreach ( $this->fields as $form_field ) { if ( false !== $has_invalid_field ) continue; $input = $form_field->get_input(); $value = $input->get_value(); $filter = $input->get_data( 'filter' ); $can_be_empty = true !== $input->get_data( 'required' ); if ( $can_be_empty && !$value ) { continue; } if ( $filter && !filter_var( $value, $filter ) ) { $has_invalid_field = $input->get_data('label'); break; } } return $has_invalid_field; } public function get_attributes_html() { return implode( ' ', array_map( function ($k, $v) { return sanitize_key( $k ) .'="'. esc_attr( $v ) .'"'; }, array_keys( $this->attributes ), $this->attributes ) ); } public function __toString() { return $this->html(); } public function html() { $fields = ''; foreach ($this->fields as $name => $field) { $fields .= $field; } // The form output is late escaped on rendering in tmpl\modules\simple_form_module_tmpl.php return sprintf('
%2$s
', $this->get_attributes_html(), $fields ); } }//Sek_Form endif; /* * Field class definition * * label and/or wrapper + input field */ if ( !class_exists( '\Nimble\Sek_Field' ) ) : class Sek_Field { private $input; private $data; public function __construct( Sek_Input_Interface $input, $args = array() ) { $this->input = $input; $this->data = wp_parse_args( $args, [ 'wrapper_tag' => '', 'wrapper_class' => array( 'sek-form-field' ), 'label' => '', //TODO: allow callbacks 'before_label' => '', 'after_label' => '', 'before_input' => '', 'after_input' => '', ]); } public function get_input() { return $this->input; } public function __toString() { return $this->html(); } public function html() { $label = $this->data[ 'label' ]; //label stuff if ( $label ) { if ( true == $this->input->get_data( 'required' ) ) { $label .= ' *'; //$label .= ' ' . esc_html__( '(required)', 'text_doma' ); } $label = sprintf( '%1$s%4$s', $this->data[ 'before_label' ], esc_attr( $this->input->get_data( 'id' ) ), wp_specialchars_decode($label), $this->data[ 'after_label' ] ); } //the input if ( !empty($this->data['type']) && 'checkbox' === $this->data['type'] ) { $html = sprintf( '%s%s%s%s', $this->data[ 'before_input' ], $this->input, $this->data[ 'after_input' ], $label ); } else { $html = sprintf( '%s%s%s%s', $label, $this->data[ 'before_input' ], $this->input, $this->data[ 'after_input' ] ); } //any wrapper? if ( $this->data[ 'wrapper_tag' ] ) { $wrapper_tag = tag_escape( $this->data[ 'wrapper_tag' ] ); $wrapper_class = $this->data[ 'wrapper_class' ] ? ' class="'. implode( ' ', array_map('sanitize_html_class', $this->data[ 'wrapper_class' ] ) ) .'"' : ''; $html = sprintf( '<%1$s%2$s>%3$s', $wrapper_tag, esc_attr($wrapper_class), $html ); } return $html; } } endif; ?> contact form input name not set' ); return; } $defaults = array( 'name' => '', 'id' => '', 'id_suffix' => null, 'additional_attrs' => array(), 'sanitize_cb' => array( $this, 'sanitize' ), 'escape_cb' => array( $this, 'escape' ), 'required' => false, 'filter' => '', 'value' => '' ); $data = wp_parse_args( $args, $defaults ); $data[ 'id_suffix' ] = is_null( $data[ 'id_suffix' ] ) ? rand() : $data[ 'id_suffix' ]; $data[ 'id' ] = empty( $data[ 'id' ] ) ? $data[ 'name' ] : $data[ 'id' ]; $data[ 'id' ] = $data[ 'id' ] . $data[ 'id_suffix' ]; $data[ 'additional_attrs' ] = is_array( $data[ 'additional_attrs' ] ) ? $data[ 'additional_attrs' ] : array(); $this->data = $data; if ( $data[ 'value' ] ) { $this->set_value( $data[ 'value' ] ); } } public function sanitize( $value ) { return $value; } public function escape( $value ) { return esc_attr( $value ); } public function get_value() { $data = (array)$this->data; $value = $this->data['escape_cb']( $data['value'] ); if ( skp_is_customizing() ) { $field_name = $this->get_data('name'); switch( $field_name ) { case 'nimble_name' : $value = ''; break; case 'nimble_email' : $value = ''; break; case 'nimble_subject' : $value = ''; break; // case 'nimble_message' : // $value = __('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus.', 'text-domain'); // break; } } return $value; } public function set_value( $value ) { $this->data['value'] = $this->data['sanitize_cb']( $value ); } public function get_data( $data_key ){ return isset( $this->data[ $data_key ] ) ? $this->data[ $data_key ] : null; } public function get_attributes_html() { $attributes = array_merge( array_intersect_key( array_filter( $this->data ), array_flip( $this->attributes ) ), $this->data[ 'additional_attrs' ] ); if ( skp_is_customizing() ) { $attributes['value'] = array_key_exists('value', $attributes ) ? $attributes['value'] : ''; } $attributes = array_map( function ($k, $v) { switch ( $k ) { case 'value': $v = $this->get_value(); break; default: $v = esc_attr( $v ); break; } // 'required' attribute doesn't need a value : return 'required' === $k ? 'required' : sanitize_key( $k ) .'="'. $v .'"'; }, array_keys($attributes), $attributes ); return implode( ' ', $attributes ); } public function __toString() { return $this->html(); } }//end abstract class if ( !class_exists( '\Nimble\Sek_Input_Basic' ) ) : class Sek_Input_Basic extends Sek_Input_Abstract { public function __construct( $args ) { $this->attributes = array_merge( $this->attributes, array( 'value', 'type' ) ); parent::__construct( $args ); } public function html() { return sprintf( '', $this->get_attributes_html() ); } } endif; if ( !class_exists( '\Nimble\Sek_Input_Hidden' ) ) : class Sek_Input_Hidden extends Sek_Input_Basic { public function __construct( $args ) { $args[ 'type' ] = 'hidden'; parent::__construct( $args ); } } endif; if ( !class_exists( '\Nimble\Sek_Input_Checkbox' ) ) : class Sek_Input_Checkbox extends Sek_Input_Basic { public function __construct( $args ) { $args[ 'type' ] = 'checkbox'; parent::__construct( $args ); } } endif; if ( !class_exists( '\Nimble\Sek_Input_Text' ) ) : class Sek_Input_Text extends Sek_Input_Basic { public function __construct( $args ) { $args = is_array( $args ) ? $args : array(); $args[ 'type' ] = 'text'; $args[ 'filter' ] = FILTER_SANITIZE_STRING; parent::__construct( $args ); } public function sanitize( $value ) { return sanitize_text_field( $value ); } public function escape( $value ) { return esc_html( $value ); } } endif; if ( !class_exists( '\Nimble\Sek_Input_Email' ) ) : class Sek_Input_Email extends Sek_Input_Basic { public function __construct($args) { $args = is_array( $args ) ? $args : array(); $args[ 'type' ] = 'text'; $args[ 'filter' ] = FILTER_SANITIZE_EMAIL; parent::__construct( $args ); } public function sanitize( $value ) { if ( !is_email( $value ) ) { return ''; } return sanitize_email($value); } public function escape( $value ) { return esc_html( $value ); } } endif; if ( !class_exists( '\Nimble\Sek_Input_URL' ) ) : class Sek_Input_URL extends Sek_Input_Basic { public function __construct($args) { $args = is_array( $args ) ? $args : array(); $args[ 'type' ] = 'url'; $args[ 'filter' ] = FILTER_SANITIZE_URL; parent::__construct( $args ); } public function sanitize($value) { return esc_url_raw( $value ); } public function escape( $value ){ return esc_url( $value ); } } endif; if ( !class_exists( '\Nimble\Sek_Input_Submit' ) ) : class Sek_Input_Submit extends Sek_Input_Basic { public function __construct($args) { $args = is_array( $args ) ? $args : array(); $args[ 'type' ] = 'submit'; $args = wp_parse_args($args, [ 'value' => esc_html__( 'Contact', 'text_doma' ), ]); parent::__construct( $args ); } public function escape( $value ) { return esc_html( $value ); } } endif; if ( !class_exists( '\Nimble\Sek_Input_Textarea' ) ) : class Sek_Input_Textarea extends Sek_Input_Abstract { public function __construct($args) { $args = is_array( $args ) ? $args : array(); $args[ 'type' ] = 'textarea'; parent::__construct( $args ); } public function sanitize( $value ) { return wp_kses_post($value); } public function escape( $value ) { return $this->sanitize( $value ); } public function html() { return sprintf( '', $this->get_attributes_html(), $this->get_value() ); } } endif; ?> $endpoint, 'request' => $request, 'response' => '' ); public function __construct( Sek_Form $form ) { $this->form = $form; $this->messages = array( //status => message //'not_sent' => __( 'Message was not sent. Try Again.', 'text_doma'), //'sent' => __( 'Thanks!Your message has been sent.', 'text_doma'), 'aborted' => __( 'Please supply correct information.', 'text_doma') //<-todo too much generic ); $this->status = 'init'; // Validate reCAPTCHA if submitted // When sek_is_recaptcha_globally_enabled(), the hidden input 'nimble_recaptcha_resp' is rendered with a value set to a token remotely fetched with a js script // @see print_recaptcha_inline_js // on submission, we get the posted token value, and validate it with a remote http request to the google api if ( isset( $_POST['nimble_recaptcha_resp'] ) ) { if ( !$this->validate_recaptcha( sanitize_text_field($_POST['nimble_recaptcha_resp']) ) ) { $this->status = 'recaptcha_fail'; if ( sek_is_dev_mode() ) { sek_error_log('reCAPTCHA failure', $this->recaptcha_errors ); } } } } //@return bool private function validate_recaptcha( $recaptcha_token ) { $is_valid = false; $endpoint = 'https://www.google.com/recaptcha/api/siteverify'; $global_recaptcha_opts = sek_get_global_option_value('recaptcha'); $global_recaptcha_opts = is_array( $global_recaptcha_opts ) ? $global_recaptcha_opts : array(); // the user did not enter the key yet. // let's validate if ( empty($global_recaptcha_opts['private_key']) ) return true; //$public = $global_recaptcha_opts['public_key']; $request = array( 'body' => array( 'secret' => $global_recaptcha_opts['private_key'], 'response' => $recaptcha_token ), ); // cache the recaptcha_errors $response = wp_remote_post( esc_url_raw( $endpoint ), $request ); if ( is_array( $response ) ) { $maybe_recaptcha_errors = wp_remote_retrieve_body( $response ); $maybe_recaptcha_errors = json_decode( $maybe_recaptcha_errors ); $maybe_recaptcha_errors = is_object($maybe_recaptcha_errors) ? (array)$maybe_recaptcha_errors : $maybe_recaptcha_errors; if ( is_array( $maybe_recaptcha_errors ) && isset( $maybe_recaptcha_errors['error-codes'] ) && is_array( $maybe_recaptcha_errors['error-codes'] ) ) { $this->recaptcha_errors = implode(', ', $maybe_recaptcha_errors['error-codes'] ); } } //sek_error_log('reCAPTCHA response ?', $response ); // There if ( 200 != wp_remote_retrieve_response_code( $response ) ) { $this->recaptcha_errors = sprintf( __('There was a problem when performing the reCAPTCHA http request.') ); return $is_valid; } // At this point, we can check the score if there not already an error messages, like a re-submission problem for example if ( '_no_error_' === $this->recaptcha_errors ) { $response_body = wp_remote_retrieve_body( $response ); $response_body = json_decode( $response_body, true ); // see https://developers.google.com/recaptcha/docs/v3#score $score = isset( $response_body['score'] ) ? $response_body['score'] : 0; // get the user defined threshold // must be normalized to be 0 >= threshold >= 1 $user_score_threshold = array_key_exists('score', $global_recaptcha_opts) ? $global_recaptcha_opts['score'] : 0.5; $user_score_threshold = !is_numeric( $user_score_threshold ) ? 0.5 : $user_score_threshold; $user_score_threshold = $user_score_threshold > 1 ? 1 : $user_score_threshold; $user_score_threshold = $user_score_threshold < 0 ? 0 : $user_score_threshold; $user_score_threshold = apply_filters( 'nimble_recaptcha_score_treshold', $user_score_threshold ); $is_valid = $is_human = $user_score_threshold < $score; if ( !$is_valid ) { $this->recaptcha_errors = sprintf( __('Google reCAPTCHA returned a score of %s, which is lower than your threshold of %s.', 'text_dom' ), $score, $user_score_threshold ); } } return $is_valid; } // Depending on the user options, some fields might exists in the $form object // We need to check their existence ( @see https://github.com/presscustomizr/nimble-builder/issues/399 ) public function maybe_send( $form_composition, $module_model ) { // the captcha validation has been made on Sek_Mailer instantiation if ( 'recaptcha_fail' === $this->status ) { return; } //sek_error_log('$form_composition ??', $form_composition ); //sek_error_log('$module_model ??', $module_model ); //sek_error_log('$this->form ??', $form_composition , $this->form ); $invalid_field = $this->form->has_invalid_field(); if ( false !== $invalid_field ) { $this->status = 'aborted'; $this->invalid_field = $invalid_field; return; } $module_user_values = array_key_exists( 'value', $module_model ) ? $module_model['value'] : array(); //sek_error_log( '$module_model', $module_model ); $submission_options = empty( $module_user_values['form_submission'] ) ? array() : $module_user_values['form_submission']; //<-allow html?->TODO: turn into option $allow_html = true; $sender_email = $this->form->get_field('nimble_email')->get_input()->get_value(); // Define a default sender name + make sure the field exists // fixes https://github.com/presscustomizr/nimble-builder/issues/513 $sender_name = __('Someone', 'text_doma'); $sender_name_is_set = false; if ( is_array( $form_composition ) && array_key_exists( 'nimble_name', $form_composition ) ) { $sender_name_candidate = sprintf( '%1$s', $this->form->get_field('nimble_name')->get_input()->get_value() ); if ( !empty( $sender_name_candidate ) ) { $sender_name = $sender_name_candidate; $sender_name_is_set = true; } } $sender_body_message = null === $this->form->get_field('nimble_message') ? '' : $this->form->get_field('nimble_message')->get_input()->get_value(); if ( array_key_exists( 'recipients', $submission_options ) ) { $recipient = $submission_options['recipients']; } else { $recipient = get_option( 'admin_email' ); } if ( array_key_exists( 'nimble_subject' , $form_composition ) ) { $subject = $this->form->get_field('nimble_subject')->get_input()->get_value(); } else if ( $sender_name_is_set ) { $subject = sprintf( __( '%1$s sent a message from %2$s', 'text_doma' ), $sender_name, get_bloginfo( 'name' ) ); } else { $subject = sprintf( __( 'Someone sent a message from %1$s', 'text_doma' ), get_bloginfo( 'name' ) ); } // $sender_website = sprintf( __( 'Website: %1$s %2$s', 'text_doma' ), // $this->form->get_field('website')->get_input()->get_value(), // $allow_html ? '


': "\r\n\r\n\r\n" // ); // the sender's email is written in the email's header reply-to field. // But it is also written inside the message body following this issue, https://github.com/presscustomizr/nimble-builder/issues/218 $before_message = sprintf( '%1$s: %2$s <%3$s>', __('From', 'text_doma'), $sender_name, $sender_email );//$sender_website; $before_message .= sprintf( '
%1$s: %2$s', __('Subject', 'text_doma'), $subject ); $after_message = ''; if ( array_key_exists( 'email_footer', $submission_options ) ) { // Feb 2021 : now saved as a json to fix emojis issues // see fix for https://github.com/presscustomizr/nimble-builder/issues/544 // to ensure retrocompatibility with data previously not saved as json, we need to perform a json validity check $email_footer = sek_maybe_decode_richtext( $submission_options['email_footer'] ); $email_footer = sek_strip_script_tags( $email_footer ); } else { $email_footer = sprintf( __( 'This e-mail was sent from a contact form on %1$s (%2$s)', 'text_doma' ), get_bloginfo( 'name' ), get_site_url( 'url' ) ); } if ( !empty( $sender_body_message ) ) { $sender_body_message = sprintf( '

%1$s:
%2$s', __('Message body', 'text_doma'), //$allow_html ? '

': "\r\n\r\n", $sender_body_message ); } $body = sprintf( '%1$s%2$s%3$s%4$s%5$s', $before_message, $sender_body_message, $after_message, $allow_html ? '

--
': "\r\n\r\n--\r\n", $email_footer ); $headers = array(); $headers[] = $allow_html ? 'Content-Type: text/html' : ''; $headers[] = 'charset=UTF-8'; //TODO: maybe turn into option $headers[] = sprintf( 'From: %1$s <%2$s>', $sender_name, $this->get_from_email() ); $headers[] = sprintf( 'Reply-To: %1$s <%2$s>', $sender_name, $sender_email ); $this->status = wp_mail( $recipient, $subject, $body, $headers ) ? 'sent' : 'not_sent'; } public function get_status() { return $this->status; } public function get_message( $status, $module_model ) { $module_user_values = array_key_exists( 'value', $module_model ) ? $module_model['value'] : array(); //sek_error_log( '$module_model', $module_model ); $submission_options = empty( $module_user_values['form_submission'] ) ? array() : $module_user_values['form_submission']; $submission_message = isset( $this->messages[ $status ] ) ? $this->messages[ $status ] : ''; // the check with strlen( preg_replace('/\s+/' ... ) allow user to "hack" the custom submission message with a blank space // because if the field is empty it will fallback on the default value switch( $status ) { case 'not_sent' : if ( array_key_exists( 'failure_message', $submission_options ) && !empty( $submission_options['failure_message'] ) && 0 < strlen( preg_replace('/\s+/', '', $submission_options['failure_message'] ) ) ) { $submission_message = $submission_options['failure_message']; } break; case 'sent' : if ( array_key_exists( 'success_message', $submission_options ) && !empty( $submission_options['success_message'] ) && 0 < strlen( preg_replace('/\s+/', '', $submission_options['success_message'] ) ) ) { $submission_message = $submission_options['success_message']; } break; case 'aborted' : if ( array_key_exists( 'error_message', $submission_options ) && !empty( $submission_options['error_message'] ) && 0 < strlen( preg_replace('/\s+/', '', $submission_options['error_message'] ) ) ) { $submission_message = $submission_options['error_message']; } if ( false !== $this->invalid_field ) { $submission_message = sprintf( __( '%1$s : %2$s.', 'text-domain' ), $submission_message, $this->invalid_field ); } break; case 'recaptcha_fail' : $global_recaptcha_opts = sek_get_global_option_value('recaptcha'); $global_recaptcha_opts = is_array( $global_recaptcha_opts ) ? $global_recaptcha_opts : array(); if ( true === sek_booleanize_checkbox_val($global_recaptcha_opts['show_failure_message']) ) { $submission_message = !empty($global_recaptcha_opts['failure_message']) ? $global_recaptcha_opts['failure_message'] : ''; } break; } if ( '_no_error_' !== $this->recaptcha_errors && current_user_can( 'customize' ) ) { $submission_message .= sprintf( '
%s : %s', __('reCAPTCHA problem (only visible by a logged in administrator )', 'text_doma'), $this->recaptcha_errors ); } return $submission_message; } // inspired from wpcf7 private function get_from_email() { $admin_email = get_option( 'admin_email' ); $sitename = strtolower( sanitize_text_field($_SERVER['SERVER_NAME']) ); if ( in_array( $sitename, array( 'localhost', '127.0.0.1' ) ) ) { return $admin_email; } if ( substr( $sitename, 0, 4 ) == 'www.' ) { $sitename = substr( $sitename, 4 ); } if ( strpbrk( $admin_email, '@' ) == '@' . $sitename ) { return $admin_email; } return 'wordpress@' . $sitename; } }//Sek_Mailer endif; // inspired by wpcf7 function sek_simple_form_mail_template() { $template = array( 'subject' => sprintf( __( '%1$s: new contact request', 'text_doma' ), get_bloginfo( 'name' ) ), 'sender' => sprintf( '[your-name] <%s>', simple_form_from_email() ), 'body' => /* translators: %s: [your-name] <[your-email]> */ sprintf( __( 'From: %s', 'text_doma' ), '[your-name] <[your-email]>' ) . "\n" /* translators: %s: [your-subject] */ . sprintf( __( 'Subject: %s', 'text_doma' ), '[your-subject]' ) . "\n\n" . __( 'Message Body:', 'text_doma' ) . "\n" . '[your-message]' . "\n\n" . '-- ' . "\n" /* translators: 1: blog name, 2: blog URL */ . sprintf( __( 'This e-mail was sent from a contact form on %1$s (%2$s)', 'text_doma' ), get_bloginfo( 'name' ), get_bloginfo( 'url' ) ), 'recipient' => get_option( 'admin_email' ), 'additional_headers' => 'Reply-To: [your-email]', 'attachments' => '', 'use_html' => 0, 'exclude_blank' => 0, ); return $template; }//simple_form_mail_template ?>