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',/ deactivated for now. Replaced by sek_level_width_section 'sek_level_height_module', 'sek_level_spacing_module', 'sek_level_spacing_module_for_columns', 'sek_level_width_module', 'sek_level_width_column', 'sek_level_width_section', 'sek_level_anchor_module', 'sek_level_visibility_module', 'sek_level_breakpoint_module' ]; public static $ui_local_global_options_modules = [ // local skope options modules 'sek_local_template', 'sek_local_widths', 'sek_local_custom_css', 'sek_local_reset', 'sek_local_performances', 'sek_local_header_footer', 'sek_local_revisions', 'sek_local_imp_exp', // global options modules 'sek_global_text', 'sek_global_widths', 'sek_global_breakpoint', 'sek_global_header_footer', 'sek_global_performances', 'sek_global_recaptcha', 'sek_global_revisions', 'sek_global_reset', 'sek_global_imp_exp', 'sek_global_beta_features', // site template options module 'sek_site_tmpl_pickers' ]; // Is merged with front module when sek_is_header_footer_enabled() === true // @see sek_register_modules_when_customizing_or_ajaxing // and sek_register_modules_when_not_customizing_and_not_ajaxing public static $ui_front_beta_modules = []; // introduced for https://github.com/presscustomizr/nimble-builder/issues/456 public $global_sections_rendered = false; // 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 // @see ::render() method // otherwise the preview UI can be broken public $preview_level_guid = '_preview_level_guid_not_set_'; // March 2020 : introduction of individual stylesheet for some modules // October 2020 : implementation of dynamic stylesheet concatenation when generating stylesheets 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', '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 = '
%1$s', __('Html code goes here', 'text-domain'), __('Click to edit', 'here') ) : ''; break; } if ( skp_is_customizing() ) { return sprintf('
%2$s
%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(); ?>