6688 lines
318 KiB
PHP
6688 lines
318 KiB
PHP
<?php
|
|
namespace Nimble;
|
|
if ( !defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
|
|
/**
|
|
* Sek Dyn CSS Builder: class responsible for building Stylesheet from a sek model
|
|
*/
|
|
class Sek_Dyn_CSS_Builder {
|
|
|
|
/*min widths, considering CSS min widths BP:
|
|
$grid-breakpoints: (
|
|
xs: 0,
|
|
sm: 576px,
|
|
md: 768px,
|
|
lg: 992px,
|
|
xl: 1200px
|
|
)
|
|
|
|
we could have a constant array since php 5.6
|
|
*/
|
|
public static $breakpoints = [
|
|
'xs' => 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
|
|
|
|
?><?php
|
|
/**
|
|
* Sek Dyn CSS Handler: class responsible for enqueuing/writing CSS file or enqueuing/printing inline CSS
|
|
*/
|
|
class Sek_Dyn_CSS_Handler {
|
|
|
|
/**
|
|
* CSS files base dir constant
|
|
* Relative dir in the WordPress uploads dir
|
|
*
|
|
* @access public
|
|
*/
|
|
const CSS_BASE_DIR = NIMBLE_CSS_FOLDER_NAME;
|
|
|
|
/**
|
|
* Functioning mode constant
|
|
* @access public
|
|
*/
|
|
const MODE_INLINE = 'inline';
|
|
|
|
/**
|
|
* Functioning mode constant
|
|
* @access public
|
|
*/
|
|
const MODE_FILE = 'file';
|
|
|
|
/**
|
|
* CSS resource ID
|
|
*
|
|
* Holds the CSS resource ID
|
|
* Will be used to generate both the file name and the CSS handle when enqueued_or_printed
|
|
* Usually set to skope_id
|
|
*
|
|
* @access private
|
|
* @var string
|
|
*/
|
|
private $id;
|
|
|
|
|
|
|
|
/**
|
|
* Requested skope_id
|
|
*
|
|
* Will be used as id
|
|
* Must be provided
|
|
*
|
|
* @access private
|
|
* @var string
|
|
*/
|
|
private $skope_id;
|
|
|
|
// property "is_global_stylesheet" has been added when fixing https://github.com/presscustomizr/nimble-builder/issues/273
|
|
private $is_global_stylesheet;
|
|
|
|
/**
|
|
* the CSS
|
|
*
|
|
* Holds the CSS string: whether to inline print or to write in the proper file
|
|
*
|
|
* @access private
|
|
* @var string
|
|
*/
|
|
private $css_string_to_enqueue_or_print = '';
|
|
|
|
|
|
/**
|
|
* CSS enqueuing / inline printing status
|
|
*
|
|
* Hold the enqueuing status
|
|
*
|
|
* @access private
|
|
* @var bool
|
|
*/
|
|
private $enqueued_or_printed = false;
|
|
|
|
|
|
|
|
/**
|
|
* Enqueuing hook
|
|
*
|
|
* Holds the wp action hook name at whose occurrence the CSS will be enqueued_or_printed
|
|
*
|
|
* @access private
|
|
* @var string
|
|
*/
|
|
private $hook;
|
|
|
|
|
|
/**
|
|
* Enqueuing hook priority
|
|
*
|
|
* Holds the wp action hook priority at whose occurrence the CSS will be enqueued
|
|
* (see the $hook param)
|
|
*
|
|
* @var int
|
|
*/
|
|
private $priority = 10;
|
|
|
|
|
|
|
|
/**
|
|
* Enqueuing dependencies
|
|
*
|
|
* Holds the style dependencies for this CSS
|
|
*
|
|
* @access private
|
|
* @var array
|
|
*/
|
|
private $dep = array();
|
|
|
|
|
|
|
|
/**
|
|
* Functioning mode
|
|
*
|
|
* Holds the object functioning mode: MODE_FILE or MODE_INLINE
|
|
*
|
|
* @access private
|
|
* @var string
|
|
*/
|
|
private $mode;
|
|
|
|
/**
|
|
* File writing flag
|
|
*
|
|
* Indicates if we need to only write, not print or enqueuing
|
|
* This is used when saving the customizer options + writing the css file.
|
|
*
|
|
* @access private
|
|
* @var bool
|
|
*/
|
|
private $customizer_save = false;
|
|
|
|
|
|
/**
|
|
* File writing flag
|
|
*
|
|
* Holds whether or not the file writing should be forced before enqueuing if the file doesn't exist
|
|
* This is valid only when $mode == MODE_FILE
|
|
*
|
|
* @access private
|
|
* @var bool
|
|
*/
|
|
private $force_write = false;
|
|
|
|
|
|
|
|
/**
|
|
* File writing flag
|
|
*
|
|
* Holds whether or not the file writing should be forced before enqueuing even if the file exists
|
|
* This is valid only when $mode == MODE_FILE
|
|
*
|
|
* @access private
|
|
* @var bool
|
|
*/
|
|
private $force_rewrite = false;
|
|
|
|
|
|
/**
|
|
* File status
|
|
*
|
|
* Holds the file existence status (true|false)
|
|
*
|
|
* @access private
|
|
* @var bool
|
|
*/
|
|
private $file_exists = false;
|
|
|
|
|
|
|
|
/**
|
|
* CSS file base PATH
|
|
*
|
|
* Holds the CSS relative base path
|
|
* This is simply CSS_BASE_DIR in single sites, while its structure takes in account network and site id in multisites
|
|
*
|
|
* @access private
|
|
* @var string
|
|
*/
|
|
private $relative_base_path;
|
|
|
|
|
|
|
|
/**
|
|
* CSS file base URI
|
|
*
|
|
* Holds the CSS folder URI
|
|
*
|
|
* @access private
|
|
* @var string
|
|
*/
|
|
private $base_uri;
|
|
|
|
|
|
/**
|
|
* CSS file base URL
|
|
*
|
|
* Holds the CSS folder URL
|
|
*
|
|
* @access private
|
|
* @var string
|
|
*/
|
|
private $base_url;
|
|
|
|
|
|
|
|
/**
|
|
* CSS file URL
|
|
*
|
|
* Holds the CSS file URL
|
|
*
|
|
* @access private
|
|
* @var string
|
|
*/
|
|
private $url;
|
|
|
|
|
|
|
|
|
|
/**
|
|
* CSS file URI
|
|
*
|
|
* Holds the CSS file URI
|
|
*
|
|
* @access private
|
|
* @var string
|
|
*/
|
|
private $uri;
|
|
|
|
private $builder;//will hold the Sek_Dyn_CSS_Builder instance
|
|
|
|
public $sek_model = 'no_set';
|
|
|
|
|
|
/**
|
|
* Sek Dyn CSS Handler constructor.
|
|
*
|
|
* Initializing the object.
|
|
*
|
|
* @access public
|
|
* @param array $args Optional.
|
|
*
|
|
*/
|
|
public function __construct( $args = array() ) {
|
|
|
|
$defaults = array(
|
|
'id' => '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( '<style id="sek-%1$s" media="all">%2$s</style>',
|
|
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, "<?php\n// Silence is golden.\n", $chmod_dir );
|
|
}
|
|
|
|
|
|
if ( !wp_is_writable( $base_uri ) ) {
|
|
return false;
|
|
}
|
|
|
|
//actual write try and update the file_exists status
|
|
$this->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();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
?><?php
|
|
// filter declared in Sek_Dyn_CSS_Builder::sek_css_rules_sniffer_walker()
|
|
// $rules = apply_filters( "sek_add_css_rules_for_input_id", $rules, $key, $entry, $this->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;
|
|
}
|
|
?><?php
|
|
////////////////////////////////////////////////////////////////
|
|
// SEK Front Class
|
|
if ( !class_exists( 'SEK_Front_Construct' ) ) :
|
|
class SEK_Front_Construct {
|
|
static $instance;
|
|
public $seks_posts = [];// <= march 2020 : used to cache the current local and global sektion posts
|
|
public $model = array();//<= when rendering, the current level model
|
|
public $parent_model = array();//<= when rendering, the current parent model
|
|
public $default_models = array();// <= will be populated to cache the default models when invoking sek_get_default_module_model
|
|
public $cached_input_lists = array(); // <= will be populated to cache the input_list of each registered module. Useful when we need to get info like css_selector for a particular input type or id.
|
|
public $ajax_action_map = array();
|
|
public $default_locations = [
|
|
'loop_start' => 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 = '<div class="sek-css-loader sek-mr-loader"><div></div><div></div><div></div></div>';
|
|
|
|
// 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;
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
// <CONSTRUCTOR>
|
|
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' => '<aside id="%1$s" class="widget %2$s">',
|
|
'after_widget' => '</aside>',
|
|
'before_title' => '<h2 class="widget-title">',
|
|
'after_title' => '</h2>',
|
|
);
|
|
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;
|
|
?><?php
|
|
if ( !class_exists( 'SEK_Front_Ajax' ) ) :
|
|
class SEK_Front_Ajax extends SEK_Front_Construct {
|
|
// Fired in __construct()
|
|
function _schedule_front_ajax_actions() {
|
|
add_action( 'wp_ajax_sek_get_content', array( $this, 'sek_get_level_content_for_injection' ) );
|
|
|
|
// Returns the customize url for the edit button when using Gutenberg editor
|
|
// implemented for https://github.com/presscustomizr/nimble-builder/issues/449
|
|
// @see assets/admin/js/nimble-gutenberg.js
|
|
add_action( 'wp_ajax_sek_get_customize_url_for_nimble_edit_button', array( $this, 'sek_get_customize_url_for_nimble_edit_button' ) );
|
|
|
|
// This is the list of accepted actions
|
|
$this->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;
|
|
?><?php
|
|
if ( !class_exists( 'SEK_Front_Assets' ) ) :
|
|
class SEK_Front_Assets extends SEK_Front_Ajax {
|
|
// Fired in __construct()
|
|
function _schedule_front_assets_printing() {
|
|
// Maybe adds `defer` support for scripts registered or enqueued
|
|
// and for which we've added an attribute with sek_defer_script( $_hand, 'defer', true );
|
|
// inspired from Twentytwenty WP theme
|
|
// @see https://core.trac.wordpress.org/ticket/12009
|
|
add_filter( 'script_loader_tag', array( $this, 'sek_filter_script_loader_tag' ), 10, 2 );
|
|
|
|
add_action( 'template_redirect', array( $this, 'sek_check_if_page_has_nimble_content' ) );
|
|
|
|
// Load Front CSS
|
|
// 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
|
|
add_action( 'wp_enqueue_scripts', array( $this, 'sek_maybe_enqueue_front_css_assets' ) );
|
|
|
|
// Load Front JS
|
|
add_action( 'wp_enqueue_scripts', array( $this, 'sek_enqueue_front_js_assets' ) );
|
|
|
|
// Scheduling loading of needed js files
|
|
add_action( 'wp_head', array( $this, 'sek_main_front_js_preloading_when_not_customizing') );
|
|
|
|
// If supported by the browser, woff2 fonts of Font Awesome will be preloaded
|
|
add_action( 'wp_head', array( $this, 'sek_maybe_preload_fa_fonts') );
|
|
// Maybe preload Font assets when really needed ( sniff first ) + nb_.listenTo('nb-needs-fa')
|
|
add_action( 'wp_head', array( $this, 'sek_maybe_preload_front_assets_when_not_customizing' ), PHP_INT_MAX );
|
|
|
|
add_action( 'wp_head', array( $this, 'sek_inline_init_js'), 0 );
|
|
}//_schedule_front_and_preview_assets_printing
|
|
|
|
|
|
//@template redirect
|
|
function sek_check_if_page_has_nimble_content() {
|
|
// do we have local or global sections to render in this page ?
|
|
// see https://github.com/presscustomizr/nimble-builder/issues/586
|
|
// we know the skope_id because 'wp' has been fired
|
|
// October 2020
|
|
if ( 'not_set' === Nimble_Manager()->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<this.length;n++)e.call(t,this[n],n,this)}),nb_.listenTo("nb-docready",function(){var e=document.querySelectorAll("div.sek-has-bg");!nb_.isObject(e)||e.length<1||e.forEach(function(e){nb_.isObject(e)&&(window.sekFrontLocalized&&window.sekFrontLocalized.lazyload_enabled?nb_.isInScreen(e)&&nb_.mayBeRevealBG.call(e):nb_.mayBeRevealBG.call(e))})}),"complete"===document.readyState||"loading"!==document.readyState&&!document.documentElement.doScroll)nb_.emit("nb-docready");else{var n=function(){nb_.wasEmitted("nb-docready")||nb_.emit("nb-docready")};document.addEventListener("DOMContentLoaded",n),window.addEventListener("load",n)}}(window,document),function(){var e=function(){var e="nb-jquery-loaded";nb_.wasEmitted(e)||nb_.emit(e)},t=function(n){n=n||0,void 0!==window.jQuery?e():n<30?setTimeout(function(){t(++n)},200):window.console&&window.console.log&&console.log("Nimble Builder problem : jQuery.js was not detected on your website")},n=document.getElementById("nb-jquery");n&&n.addEventListener("load",function(){e()}),t()}(),window,document,nb_.listenTo("nb-jquery-loaded",function(){sekFrontLocalized.load_front_assets_on_dynamically&&(nb_.scriptsLoadingStatus={},nb_.ajaxLoadScript=function(e){jQuery(function(t){e=t.extend({path:"",complete:"",loadcheck:!1},e),nb_.scriptsLoadingStatus[e.path]&&"pending"===nb_.scriptsLoadingStatus[e.path].state()||(nb_.scriptsLoadingStatus[e.path]=nb_.scriptsLoadingStatus[e.path]||t.Deferred(),jQuery.ajax({url:sekFrontLocalized.frontAssetsPath+e.path+"?"+sekFrontLocalized.assetVersion,cache:!0,dataType:"script"}).done(function(){"function"!=typeof e.loadcheck||e.loadcheck()?"function"==typeof e.complete&&e.complete():nb_.errorLog("ajaxLoadScript success but loadcheck failed for => "+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"})})}))})});
|
|
<?php
|
|
$init_script = ob_get_clean();
|
|
wp_register_script( 'nb-js-app', '');
|
|
wp_enqueue_script( 'nb-js-app' );
|
|
wp_add_inline_script( 'nb-js-app', $init_script );
|
|
}
|
|
}
|
|
|
|
|
|
//@'wp_enqueue_scripts'
|
|
// ==>>> 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 : "<?php echo esc_url($script_url); ?>",
|
|
scriptEl : document.getElementById('<?php echo "nb-load-main-script"; ?>')
|
|
});
|
|
});
|
|
<?php
|
|
|
|
|
|
// Schedule loading of partial scripts
|
|
$partial_front_scripts = Nimble_Manager()->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('<?php echo esc_attr($event); ?>', function() {
|
|
nb_.preloadOrDeferAsset( {
|
|
id : "<?php echo esc_attr($name); ?>",
|
|
as : 'script',
|
|
href : "<?php echo esc_url($url); ?>",
|
|
scriptEl : document.getElementById('<?php echo esc_attr("nb-load-script-{$name}"); ?>')
|
|
});
|
|
});
|
|
<?php
|
|
}
|
|
$script = ob_get_clean();
|
|
wp_register_script( 'nb_main_front_js_preloading', '');
|
|
wp_enqueue_script( 'nb_main_front_js_preloading' );
|
|
wp_add_inline_script( 'nb_main_front_js_preloading', $script );
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//@wp_footer
|
|
// preload is applied when 'load_assets_in_ajax' is not active
|
|
// ==>>> 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 : "<?php echo esc_url($assets_urls['nb-swipebox']); ?>",
|
|
onEvent : 'nb-docready',
|
|
// scriptEl : document.currentScript
|
|
});
|
|
nb_.preloadOrDeferAsset( {
|
|
id : 'nb-swipebox-style',
|
|
as : 'style',
|
|
href : "<?php echo esc_url($assets_urls['nb-swipebox-style']); ?>",
|
|
onEvent : 'nb-docready',
|
|
// scriptEl : document.currentScript
|
|
});
|
|
});
|
|
|
|
nb_.listenTo('nb-needs-swiper', function() {
|
|
nb_.preloadOrDeferAsset( {
|
|
id : 'nb-swiper',
|
|
as : 'script',
|
|
href : "<?php echo esc_url($assets_urls['nb-swiper']); ?>",
|
|
onEvent : 'nb-docready',
|
|
// scriptEl : document.currentScript
|
|
});
|
|
});
|
|
nb_.listenTo('nb-needs-videobg-js', function() {
|
|
nb_.preloadOrDeferAsset( {
|
|
id : 'nb-video-bg-plugin',
|
|
as : 'script',
|
|
href : "<?php echo esc_url($assets_urls['nb-video-bg-plugin']); ?>",
|
|
onEvent : 'nb-docready',
|
|
// scriptEl : document.currentScript
|
|
});
|
|
});
|
|
<?php
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* FONT AWESOME
|
|
/* ------------------------------------------------------------------------- */
|
|
// if active theme is Hueman or Customizr, Font Awesome may already been enqueued.
|
|
// asset handle for Customizr => '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
|
|
?>
|
|
<?php $fa_style_url = NIMBLE_BASE_URL .'/assets/front/fonts/css/fontawesome-all.min.css?'.NIMBLE_ASSETS_VERSION; ?>
|
|
nb_.listenTo('nb-needs-fa', function() {
|
|
nb_.preloadOrDeferAsset( {
|
|
id : 'nb-font-awesome',
|
|
as : 'style',
|
|
href : "<?php echo esc_url($fa_style_url); ?>",
|
|
onEvent : 'nb-docready',
|
|
scriptEl : document.currentScript
|
|
});
|
|
});
|
|
<?php
|
|
}
|
|
|
|
$script = ob_get_clean();
|
|
wp_register_script( 'nb_preload_front_assets', '');
|
|
wp_enqueue_script( 'nb_preload_front_assets' );
|
|
wp_add_inline_script( 'nb_preload_front_assets', $script );
|
|
}
|
|
|
|
|
|
|
|
// hook : wp_head
|
|
// October 2020 Better preload implementation
|
|
// As explained here https://stackoverflow.com/questions/49268352/preload-font-awesome
|
|
// FA fonts can be preloaded. the crossorigin param has to be added
|
|
// => 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();
|
|
?>
|
|
<?php foreach( $fonts as $id => $name ) : ?>
|
|
<?php $font_url = NIMBLE_BASE_URL .'/assets/front/fonts/webfonts/'.$name; ?>
|
|
nb_.listenTo('nb-needs-fa', function() {
|
|
nb_.preloadOrDeferAsset( {
|
|
id : "<?php echo esc_attr($id); ?>",
|
|
as : 'font',
|
|
href : "<?php echo esc_url($font_url); ?>",
|
|
type : 'font/woff2',
|
|
//onEvent : 'nb-docready',
|
|
//eventOnLoad : 'nb-font-awesome-preloaded',
|
|
scriptEl : document.getElementById('<?php echo esc_attr("nb-load-{$id}"); ?>')
|
|
});
|
|
});
|
|
<?php endforeach; ?>
|
|
<?php
|
|
$script = ob_get_clean();
|
|
wp_add_inline_script( 'nb-js-init', $script );
|
|
}
|
|
|
|
|
|
/**
|
|
* Fired @'script_loader_tag'
|
|
* Adds async/defer attributes to enqueued / registered scripts.
|
|
* works with sek_defer_script()
|
|
* see https://html.spec.whatwg.org/multipage/scripting.html#attr-script-defer
|
|
* based on a solution found in Twentytwenty
|
|
* and for which we've added an attribute with sek_defer_script( $_hand, 'defer', true );
|
|
* If #12009 lands in WordPress, this function can no-op since it would be handled in core.
|
|
*
|
|
* @param string $tag The script tag.
|
|
* @param string $handle The script handle.
|
|
* @return string Script HTML string.
|
|
*/
|
|
public function sek_filter_script_loader_tag( $tag, $handle ) {
|
|
// adds an id to jquery core so we can detect when it's loaded
|
|
if ( 'jquery-core' === $handle ) {
|
|
// tag is a string and looks like script src='http://customizr-dev.test/wp-includes/js/jquery/jquery-migrate.js?ver=1.4.1'></script>
|
|
$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( ':(?=></script>):', " $attr", $tag, 1 );
|
|
}
|
|
// Only allow async or defer, not both.
|
|
break;
|
|
}
|
|
return $tag;
|
|
}
|
|
}//class
|
|
endif;
|
|
?><?php
|
|
if ( !class_exists( 'SEK_Front_Assets_Customizer_Preview' ) ) :
|
|
class SEK_Front_Assets_Customizer_Preview extends SEK_Front_Assets {
|
|
// Fired in __construct()
|
|
function _schedule_preview_assets_printing() {
|
|
add_action( 'wp_footer', array( $this, 'sek_customizr_js_stuff' ), PHP_INT_MAX );
|
|
|
|
// Load customize preview js
|
|
add_action( 'customize_preview_init', array( $this, 'sek_schedule_customize_preview_assets' ) );
|
|
}//_schedule_preview_assets_printing
|
|
|
|
// @'wp_footer'
|
|
function sek_customizr_js_stuff() {
|
|
if ( !sek_current_user_can_access_nb_ui() )
|
|
return;
|
|
if( !skp_is_customizing() )
|
|
return;
|
|
|
|
ob_start();
|
|
?>
|
|
(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));
|
|
<?php
|
|
$script = ob_get_clean();
|
|
wp_add_inline_script( 'nb-js-init', $script );
|
|
}
|
|
|
|
// enqueue / print customize preview assets
|
|
// hook : 'customize_preview_init'
|
|
function sek_schedule_customize_preview_assets() {
|
|
if ( !sek_current_user_can_access_nb_ui() )
|
|
return;
|
|
|
|
// we don't need those assets when previewing a customize changeset
|
|
// added when fixing https://github.com/presscustomizr/nimble-builder/issues/351
|
|
if ( sek_is_customize_previewing_a_changeset_post() )
|
|
return;
|
|
|
|
// Load preview ui js tmpl
|
|
add_action( 'wp_footer', array( $this, 'sek_print_ui_tmpl' ) );
|
|
|
|
wp_enqueue_style(
|
|
'sek-preview',
|
|
sprintf(
|
|
'%1$s/assets/czr/sek/css/%2$s' ,
|
|
NIMBLE_BASE_URL,
|
|
sek_is_dev_mode() ? 'sek-preview.css' : 'sek-preview.min.css'
|
|
),
|
|
array( 'sek-base' ),
|
|
NIMBLE_ASSETS_VERSION,
|
|
'all'
|
|
);
|
|
|
|
// Communication between preview and customizer panel
|
|
wp_enqueue_script(
|
|
'sek-customize-preview',
|
|
sprintf(
|
|
'%1$s/assets/czr/sek/js/%2$s' ,
|
|
NIMBLE_BASE_URL,
|
|
sek_is_dev_mode() ? 'ccat-sek-preview.js' : 'ccat-sek-preview.min.js'
|
|
),
|
|
array( 'customize-preview', 'underscore'),
|
|
NIMBLE_ASSETS_VERSION,
|
|
true
|
|
);
|
|
|
|
wp_localize_script(
|
|
'sek-customize-preview',
|
|
'sekPreviewLocalized',
|
|
array(
|
|
'i18n' => 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() {
|
|
?>
|
|
<script type="text/html" id="sek-tmpl-add-content-button">
|
|
<# //console.log( 'data', data ); #>
|
|
<div class="sek-add-content-button <# if ( data.is_last ) { #>is_last<# } #>">
|
|
<div class="sek-add-content-button-wrapper">
|
|
<# var hook_location = '', btn_title = true !== data.is_global_location ? sekPreviewLocalized.i18n['Insert a new section'] : sekPreviewLocalized.i18n['Insert a new global section'], addContentBtnWidth = true !== data.is_global_location ? '83px' : '113px' #>
|
|
<# if ( data.location ) {
|
|
hook_location = ['(' , sekPreviewLocalized.i18n['@location'] , ':"',data.location , '")'].join('');
|
|
} #>
|
|
<button title="{{btn_title}} {{hook_location}}" data-sek-click-on="add-content" data-sek-add="section" class="sek-add-content-btn" style="--sek-add-content-btn-width:{{addContentBtnWidth}};">
|
|
<span class="sek-click-on-button-icon sek-click-on">+</span><span class="action-button-text">{{btn_title}}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</script>
|
|
|
|
<?php
|
|
$icon_right_side_class = is_rtl() ? 'sek-dyn-left-icons' : 'sek-dyn-right-icons';
|
|
$icon_left_side_class = is_rtl() ? 'sek-dyn-right-icons' : 'sek-dyn-left-icons';
|
|
?>
|
|
|
|
<script type="text/html" id="sek-dyn-ui-tmpl-section">
|
|
<?php //<# console.log( 'data', data ); #> ?>
|
|
<div class="sek-dyn-ui-wrapper sek-section-dyn-ui">
|
|
<div class="sek-dyn-ui-inner <?php echo esc_attr($icon_left_side_class); ?>">
|
|
<div class="sek-dyn-ui-icons">
|
|
|
|
<?php if ( sek_is_dev_mode() ) : ?>
|
|
<i class="sek-to-json fas fa-code"></i>
|
|
<?php endif; ?>
|
|
<?php
|
|
// Code before implementing https://github.com/presscustomizr/nimble-builder/issues/521 :
|
|
/* <# if ( true !== data.is_first_section_in_parent ) { #>
|
|
<i data-sek-click-on="move-section-up" class="material-icons sek-click-on" title="<?php _e( 'Move section up', 'text_domain' ); ?>">keyboard_arrow_up</i>
|
|
<# } #>
|
|
<# if ( true !== data.is_last_section_in_parent ) { #>
|
|
<i data-sek-click-on="move-section-down" class="material-icons sek-click-on" title="<?php _e( 'Move section down', 'text_domain' ); ?>">keyboard_arrow_down</i>
|
|
<# } #>*/
|
|
?>
|
|
<i data-sek-click-on="move-section-up" class="material-icons sek-click-on" title="<?php _e( 'Move section up', 'text_domain' ); ?>">keyboard_arrow_up</i>
|
|
<i data-sek-click-on="move-section-down" class="material-icons sek-click-on" title="<?php _e( 'Move section down', 'text_domain' ); ?>">keyboard_arrow_down</i>
|
|
|
|
|
|
<?php // if this is a nested section, it has the is_nested property set to true. We don't want to make it draggable for the moment. @todo ?>
|
|
<# if ( !data.is_nested ) { #>
|
|
<# if ( true !== data.is_global_location ) { #>
|
|
<i class="fas fa-arrows-alt sek-move-section" title="<?php _e( 'Drag section', 'text_domain' ); ?>"></i>
|
|
<# } #>
|
|
<# } #>
|
|
<i data-sek-click-on="edit-options" class="material-icons sek-click-on" title="<?php _e( 'Edit section settings', 'text_domain' ); ?>">tune</i>
|
|
<# if ( data.can_have_more_columns ) { #>
|
|
<i data-sek-click-on="add-column" class="material-icons sek-click-on" title="<?php _e( 'Add a column', 'text_domain' ); ?>">view_column</i>
|
|
<# } #>
|
|
<i data-sek-click-on="duplicate" class="material-icons sek-click-on" title="<?php _e( 'Duplicate section', 'text_domain' ); ?>">filter_none</i>
|
|
<i data-sek-click-on="toggle-save-section-ui" class="sek-save far fa-save" title="<?php _e( 'Save this section', 'text_domain' ); ?>"></i>
|
|
<i data-sek-click-on="remove" class="material-icons sek-click-on" title="<?php _e( 'Remove section', 'text_domain' ); ?>">delete_forever</i>
|
|
</div>
|
|
</div><?php // .sek-dyn-ui-inner ?>
|
|
<div class="sek-dyn-ui-location-type" data-sek-click-on="edit-options" title="<?php _e( 'Edit section settings', 'text_domain' ); ?>">
|
|
<div class="sek-dyn-ui-location-inner">
|
|
<div class="sek-dyn-ui-hamb-menu-wrapper sek-collapsed">
|
|
<div class="sek-ham__toggler-spn-wrapper"><span class="line line-1"></span><span class="line line-2"></span><span class="line line-3"></span></div>
|
|
</div>
|
|
<#
|
|
var section_title = true !== data.is_global_location ? sekPreviewLocalized.i18n['section'] : sekPreviewLocalized.i18n['section (global)'];
|
|
var section_title = !data.is_nested ? sekPreviewLocalized.i18n['section'] : sekPreviewLocalized.i18n['nested section'];
|
|
if ( true === data.is_header_location && !data.is_nested ) {
|
|
section_title = sekPreviewLocalized.i18n['header section'];
|
|
} else if ( true === data.is_footer_location && !data.is_nested ) {
|
|
section_title = sekPreviewLocalized.i18n['footer section'];
|
|
}
|
|
|
|
section_title = true !== data.is_global_location ? section_title : [ section_title, sekPreviewLocalized.i18n['(global)'] ].join(' ');
|
|
#>
|
|
<div class="sek-dyn-ui-level-type">{{section_title}}</div>
|
|
</div><?php // .sek-dyn-ui-location-inner ?>
|
|
<div class="sek-minimize-ui" title="<?php _e('Hide this menu if you need to access behind', 'text-domain'); ?>"><i class="far fa-eye-slash"></i></div>
|
|
</div><?php // .sek-dyn-ui-location-type ?>
|
|
</div><?php // .sek-dyn-ui-wrapper ?>
|
|
</script>
|
|
|
|
<script type="text/html" id="sek-dyn-ui-tmpl-column">
|
|
<?php //<# console.log( 'data', data ); #> ?>
|
|
<?php
|
|
// when a column has nested section(s), its ui might be hidden by deeper columns.
|
|
// that's why a CSS class is added to position it on the top right corner, instead of bottom right
|
|
// @see https://github.com/presscustomizr/nimble-builder/issues/488
|
|
?>
|
|
<# var has_nested_section_class = true === data.has_nested_section ? 'sek-col-has-nested-section' : ''; #>
|
|
<div class="sek-dyn-ui-wrapper sek-column-dyn-ui {{has_nested_section_class}}">
|
|
<div class="sek-dyn-ui-inner <?php echo esc_attr($icon_right_side_class); ?>">
|
|
<div class="sek-dyn-ui-icons">
|
|
<i class="fas fa-arrows-alt sek-move-column" title="<?php _e( 'Move column', 'text_domain' ); ?>"></i>
|
|
<i data-sek-click-on="edit-options" class="material-icons sek-click-on" title="<?php _e( 'Edit column settings', 'text_domain' ); ?>">tune</i>
|
|
<# if ( !data.parent_is_last_allowed_nested ) { #>
|
|
<i data-sek-click-on="add-section" class="material-icons sek-click-on" title="<?php _e( 'Add a nested section', 'text_domain' ); ?>">account_balance_wallet</i>
|
|
<# } #>
|
|
<# if ( data.parent_can_have_more_columns ) { #>
|
|
<i data-sek-click-on="duplicate" class="material-icons sek-click-on" title="<?php _e( 'Duplicate column', 'text_domain' ); ?>">filter_none</i>
|
|
<# } #>
|
|
|
|
<i data-sek-click-on="pick-content" data-sek-content-type="module" class="material-icons sek-click-on" title="<?php _e( 'Add a module', 'text_domain' ); ?>">add_circle_outline</i>
|
|
<# if ( !data.parent_is_single_column ) { #>
|
|
<i data-sek-click-on="remove" class="material-icons sek-click-on" title="<?php _e( 'Remove column', 'text_domain' ); ?>">delete_forever</i>
|
|
<# } #>
|
|
</div>
|
|
</div><?php // .sek-dyn-ui-inner ?>
|
|
|
|
<div class="sek-dyn-ui-location-type" data-sek-click-on="edit-options" title="<?php _e( 'Edit column settings', 'text_domain' ); ?>">
|
|
<div class="sek-minimize-ui" title="<?php _e('Hide this menu if you need to access behind', 'text-domain'); ?>"><i class="far fa-eye-slash"></i></div>
|
|
<div class="sek-dyn-ui-location-inner">
|
|
<div class="sek-dyn-ui-hamb-menu-wrapper sek-collapsed">
|
|
<div class="sek-ham__toggler-spn-wrapper"><span class="line line-1"></span><span class="line line-2"></span><span class="line line-3"></span></div>
|
|
</div>
|
|
<div class="sek-dyn-ui-level-type"><?php _e( 'column', 'text_domain' ); ?></div>
|
|
</div><?php // .sek-dyn-ui-location-inner ?>
|
|
</div><?php // .sek-dyn-ui-location-type ?>
|
|
</div><?php // .sek-dyn-ui-wrapper ?>
|
|
</script>
|
|
|
|
<script type="text/html" id="sek-dyn-ui-tmpl-module">
|
|
<div class="sek-dyn-ui-wrapper sek-module-dyn-ui">
|
|
<div class="sek-dyn-ui-inner <?php echo esc_attr($icon_left_side_class); ?>">
|
|
<div class="sek-dyn-ui-icons">
|
|
<i class="fas fa-arrows-alt sek-move-module" title="<?php _e( 'Move module', 'text_domain' ); ?>"></i>
|
|
<i data-sek-click-on="edit-module" class="fas fa-pencil-alt sek-tip sek-click-on" title="<?php _e( 'Edit module content', 'text_domain' ); ?>"></i>
|
|
<i data-sek-click-on="edit-options" class="material-icons sek-click-on" title="<?php _e( 'Edit module settings', 'text_domain' ); ?>">tune</i>
|
|
<i data-sek-click-on="duplicate" class="material-icons sek-click-on" title="<?php _e( 'Duplicate module', 'text_domain' ); ?>">filter_none</i>
|
|
<i data-sek-click-on="remove" class="material-icons sek-click-on" title="<?php _e( 'Remove module', 'text_domain' ); ?>">delete_forever</i>
|
|
</div>
|
|
</div><?php // .sek-dyn-ui-inner ?>
|
|
<#
|
|
var module_name = !_.isEmpty( data.module_name ) ? data.module_name + ' ' + '<?php _e("module", "text_domain"); ?>' : '<?php _e("module", "text_domain"); ?>';
|
|
#>
|
|
<div class="sek-dyn-ui-location-type" data-sek-click-on="edit-module" title="<?php _e( 'Edit module settings', 'text_domain' ); ?>">
|
|
<div class="sek-dyn-ui-location-inner">
|
|
<div class="sek-dyn-ui-hamb-menu-wrapper sek-collapsed">
|
|
<div class="sek-ham__toggler-spn-wrapper"><span class="line line-1"></span><span class="line line-2"></span><span class="line line-3"></span></div>
|
|
</div>
|
|
<div class="sek-dyn-ui-level-type">{{module_name}}</div>
|
|
</div>
|
|
<div class="sek-minimize-ui" title="<?php _e('Hide this menu if you need to access behind', 'text-domain'); ?>"><i class="far fa-eye-slash"></i></div>
|
|
</div>
|
|
</div><?php // .sek-dyn-ui-wrapper ?>
|
|
</script>
|
|
|
|
<script type="text/html" id="sek-dyn-ui-tmpl-wp-content">
|
|
<div class="sek-dyn-ui-wrapper sek-wp-content-dyn-ui">
|
|
<div class="sek-dyn-ui-inner">
|
|
<div class="sek-dyn-ui-icons">
|
|
<i class="fas fa-pencil-alt sek-edit-wp-content" title="<?php _e( 'Edit this WordPress content', 'text_domain' ); ?>"></i>
|
|
</div>
|
|
</div><?php // .sek-dyn-ui-inner ?>
|
|
|
|
<span class="sek-dyn-ui-location-type" title="<?php _e( 'Edit module settings', 'text_domain' ); ?>">
|
|
<i class="fab fa-wordpress sek-edit-wp-content" title="<?php _e( 'Edit this WordPress content', 'text_domain' ); ?>"> <?php _e( 'WordPress content', 'text_domain'); ?></i>
|
|
</span>
|
|
</div><?php // .sek-dyn-ui-wrapper ?>
|
|
</script>
|
|
<?php
|
|
}
|
|
}//class
|
|
endif;
|
|
?><?php
|
|
if ( !class_exists( 'SEK_Front_Render' ) ) :
|
|
class SEK_Front_Render extends SEK_Front_Assets_Customizer_Preview {
|
|
// Fired in __construct()
|
|
function _schedule_front_rendering() {
|
|
if ( !defined( "NIMBLE_BEFORE_CONTENT_FILTER_PRIORITY" ) ) { define( "NIMBLE_BEFORE_CONTENT_FILTER_PRIORITY", PHP_INT_MAX ); }
|
|
if ( !defined( "NIMBLE_AFTER_CONTENT_FILTER_PRIORITY" ) ) { define( "NIMBLE_AFTER_CONTENT_FILTER_PRIORITY", PHP_INT_MAX ); }
|
|
if ( !defined( "NIMBLE_WP_CONTENT_WRAP_FILTER_PRIORITY" ) ) { define( "NIMBLE_WP_CONTENT_WRAP_FILTER_PRIORITY", - PHP_INT_MAX ); }
|
|
|
|
// Fires after 'wp' and before the 'get_header' template file is loaded.
|
|
add_action( 'template_redirect', array( $this, 'sek_schedule_rendering_hooks') );
|
|
|
|
// Encapsulate the singular post / page content so we can generate a dynamic ui around it when customizing
|
|
add_filter( 'the_content', array( $this, 'sek_wrap_wp_content' ), NIMBLE_WP_CONTENT_WRAP_FILTER_PRIORITY );
|
|
|
|
// SCHEDULE THE ASSETS ENQUEUING
|
|
add_action( 'wp_enqueue_scripts', array( $this, 'sek_enqueue_the_printed_module_assets') );
|
|
|
|
// SMART LOAD
|
|
add_filter( 'nimble_parse_for_smart_load', array( $this, 'sek_maybe_process_img_for_js_smart_load') );
|
|
|
|
// SETUP OUR the_content FILTER for the Tiny MCE module
|
|
$this->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( '<div class="sek-wp-content-wrapper" data-sek-wp-post-id="%1$s" data-sek-wp-edit-link="%2$s" title="%3$s">%4$s</div>',
|
|
$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 <div> 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, '<div' ) !== false ) {
|
|
$html = preg_replace( '|\s*<div|', '<div', $html );
|
|
$html = preg_replace( '|</div>\s*|', '</div>', $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("/(?<!\d)(\,|\.)(?!\d)/", "", $level_css_classes);
|
|
//$level_css_classes = preg_replace("/[^0-9a-zA-Z]/","", $level_css_classes);
|
|
}
|
|
}
|
|
|
|
// sept 2020 => 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 = '<i class="material-icons">pan_tool</i>';
|
|
switch( $input_type ) {
|
|
case 'detached_tinymce_editor' :
|
|
case 'nimble_tinymce_editor' :
|
|
case 'text' :
|
|
$ph = skp_is_customizing() ? '<div class="sek-tiny-mce-module-placeholder-text">' . __('Click to edit', 'here') .'</div>' : '';
|
|
break;
|
|
case 'upload' :
|
|
$ph = '<i class="material-icons">image</i>';
|
|
break;
|
|
}
|
|
switch( $input_id ) {
|
|
case 'html_content' :
|
|
$ph = skp_is_customizing() ? sprintf('<pre>%1$s<br/>%2$s</pre>', __('Html code goes here', 'text-domain'), __('Click to edit', 'here') ) : '';
|
|
break;
|
|
}
|
|
if ( skp_is_customizing() ) {
|
|
return sprintf('<div class="sek-module-placeholder" title="%4$s" data-sek-input-type="%1$s" data-sek-input-id="%2$s">%3$s</div>', $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 = '#<img([^>]+?)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('<div class="nimble-notice-in-preview"><i class="fas fa-info-circle"></i> %1$s</div>%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( '<p>%1$s</p>', __('You need to login to view this content.', 'text_doma') ) );
|
|
do_action('nimble_after_restricted_content_html');
|
|
}
|
|
}
|
|
}//class
|
|
endif;
|
|
?><?php
|
|
if ( !class_exists( 'SEK_Front_Render_Css' ) ) :
|
|
class SEK_Front_Render_Css extends SEK_Front_Render {
|
|
// Fired in __construct()
|
|
function _setup_hook_for_front_css_printing_or_enqueuing() {
|
|
add_action( 'wp_enqueue_scripts', array( $this, 'print_or_enqueue_seks_style'), PHP_INT_MAX );
|
|
|
|
// wp_add_inline_style for global CSS
|
|
add_action( 'wp_head', array( $this, 'sek_enqueue_global_css' ) );
|
|
|
|
}
|
|
|
|
|
|
// Can be fired :
|
|
// 1) on wp_enqueue_scripts or wp_head
|
|
// 2) when ajaxing, for actions 'sek-resize-columns', 'sek-refresh-stylesheet'
|
|
function print_or_enqueue_seks_style( $skope_id = null ) {
|
|
// when this method is fired in a customize preview context :
|
|
// - the skope_id has to be built. Since we are after 'wp', this is not a problem.
|
|
// - the css rules are printed inline in the <head>
|
|
// - 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 <head>
|
|
// - 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('<link rel="stylesheet" id="%1$s" href="%2$s">',
|
|
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 : '<?php echo esc_attr(NIMBLE_GOOGLE_FONTS_STYLESHEET_ID); ?>', as : 'style', href : '//fonts.googleapis.com/css?family=<?php echo esc_attr($print_candidates); ?>&display=swap', scriptEl : document.currentScript } );
|
|
<?php
|
|
$script = ob_get_clean();
|
|
wp_register_script( 'nb_preload_gfonts', '');
|
|
wp_enqueue_script( 'nb_preload_gfonts' );
|
|
wp_add_inline_script( 'nb_preload_gfonts', $script );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// invoked when ajaxing during customization
|
|
function sek_get_global_css_for_ajax() {
|
|
// During customization, always rebuild the css from fresh values instead of relying on the saved option
|
|
// because on first call we get the customized option value, but on another one quickly after, we get the current option value in the database
|
|
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
|
|
$global_css = $this->sek_build_global_options_inline_css();
|
|
if ( is_string( $global_css ) && !empty( $global_css ) ) {
|
|
printf('<style id="%1$s">%2$s</style>', 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;
|
|
|
|
?><?php
|
|
if ( !class_exists( '\Nimble\Sek_Simple_Form' ) ) :
|
|
class Sek_Simple_Form extends SEK_Front_Render_Css {
|
|
|
|
private $form;
|
|
private $fields;
|
|
private $mailer;
|
|
|
|
private $form_composition;
|
|
|
|
function _setup_simple_forms() {
|
|
//Hooks
|
|
add_action( 'parse_request', array( $this, 'simple_form_parse_request' ), 20 );
|
|
add_action( 'wp_enqueue_scripts', array( $this, 'maybe_enqueue_recaptcha_scripts' ), 0 );
|
|
add_filter( 'body_class', array( $this, 'set_the_recaptcha_badge_visibility_class') );
|
|
|
|
// Note : form input need to be prefixed to avoid a collision with reserved WordPress input
|
|
// @see : https://stackoverflow.com/questions/15685020/wordpress-form-submission-and-the-404-error-page#16636051
|
|
$this->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');
|
|
}
|
|
}
|
|
<?php
|
|
$script = ob_get_clean();
|
|
wp_register_script( 'nb_recaptcha_js', '');
|
|
wp_enqueue_script( 'nb_recaptcha_js' );
|
|
wp_add_inline_script( 'nb_recaptcha_js', $script );
|
|
}
|
|
|
|
// @hook body_class
|
|
public function set_the_recaptcha_badge_visibility_class( $classes ) {
|
|
// Shall we print the badge ?
|
|
// @todo : we don't handle the case when recaptcha badge is globally displayed but the current page has disabled recaptcha
|
|
$classes[] = !sek_is_recaptcha_badge_globally_displayed() ? 'sek-hide-rc-badge' : 'sek-show-rc-badge';
|
|
return $classes;
|
|
}
|
|
|
|
|
|
// Rendering
|
|
// Invoked from the tmpl
|
|
// @return string
|
|
// @param module_options is the module level "value" property. @see tmpl/modules/simple_form_module_tmpl.php
|
|
function get_simple_form_html( $module_model ) {
|
|
// sek_error_log('$module_model ?', $module_model );
|
|
// sek_error_log('$this->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();
|
|
?>
|
|
<div id="sek-form-respond">
|
|
<?php
|
|
$echo_form = true;
|
|
// When loading the page after a send attempt, focus on the module html element with a javascript animation
|
|
// In this case, don't echo the form, but only the user defined message which should be displayed after submitting the form
|
|
if ( !is_null( $this->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="<?php echo esc_attr($module_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) {}
|
|
}
|
|
});
|
|
});
|
|
<?php
|
|
$script = ob_get_clean();
|
|
wp_register_script( 'nb_simple_form_js', '');
|
|
wp_enqueue_script( 'nb_simple_form_js' );
|
|
wp_add_inline_script( 'nb_simple_form_js', $script );
|
|
|
|
$message = $this->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( '<div class="sek-form-message %1$s">%2$s</div>', 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
|
|
}
|
|
?>
|
|
</div>
|
|
<?php
|
|
return ob_get_clean();
|
|
}
|
|
|
|
|
|
//set the fields to render
|
|
private function _set_form_composition( $form_composition, $module_model = array() ) {
|
|
|
|
$user_form_composition = array();
|
|
if ( !is_array( $module_model ) ) {
|
|
sek_error_log( __CLASS__ . '::' . __FUNCTION__ . ' => 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;
|
|
|
|
?><?php
|
|
/*
|
|
*&
|
|
*
|
|
*/
|
|
if ( !class_exists( '\Nimble\Sek_Form' ) ) :
|
|
class Sek_Form {
|
|
private $fields;
|
|
private $attributes;
|
|
|
|
// Sek_Form is instantiated from Sek_Simple_Form::simple_form_generate_form
|
|
//$form = new Sek_Form( [
|
|
// 'action' => 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('<form %1$s>%2$s</form>',
|
|
$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<label for="%2$s">%3$s</label>%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</%1$s>',
|
|
$wrapper_tag,
|
|
esc_attr($wrapper_class),
|
|
$html
|
|
);
|
|
}
|
|
|
|
return $html;
|
|
}
|
|
}
|
|
endif;
|
|
|
|
?><?php
|
|
/*
|
|
*
|
|
* Input objects definition
|
|
*
|
|
*/
|
|
interface Sek_Input_Interface {
|
|
public function sanitize( $value );
|
|
public function escape( $value );
|
|
public function get_value();
|
|
public function set_value( $value );
|
|
public function get_data( $data_key );
|
|
public function html();
|
|
}
|
|
|
|
abstract class Sek_Input_Abstract implements Sek_Input_Interface {
|
|
private $data;
|
|
protected $attributes = array( 'id', 'name', 'required' );
|
|
|
|
public function __construct( $args ) {
|
|
//no name no party
|
|
//TODO: raise exception
|
|
if ( !isset( $args['name'] ) ) {
|
|
error_log( __FUNCTION__ . ' => 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 : <input name="nimble_email" id="nimble_email1163989492" type="text" required/>
|
|
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( '<input %s/>',
|
|
$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( '<textarea %1$s>%2$s</textarea>',
|
|
$this->get_attributes_html(),
|
|
$this->get_value()
|
|
);
|
|
}
|
|
}
|
|
endif;
|
|
?><?php
|
|
/*
|
|
*
|
|
* Mailer class definition
|
|
*
|
|
*/
|
|
if ( !class_exists( '\Nimble\Sek_Mailer' ) ) :
|
|
class Sek_Mailer {
|
|
private $form;
|
|
private $status;
|
|
private $messages;
|
|
private $invalid_field = false;
|
|
public $recaptcha_errors = '_no_error_';//will store array( 'endpoint' => $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 ? '<br><br><br>': "\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( '<br>%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 (<a href="%2$s" target="_blank">%2$s</a>)', 'text_doma' ),
|
|
get_bloginfo( 'name' ),
|
|
get_site_url( 'url' )
|
|
);
|
|
}
|
|
|
|
if ( !empty( $sender_body_message ) ) {
|
|
$sender_body_message = sprintf( '<br><br>%1$s: <br>%2$s',
|
|
__('Message body', 'text_doma'),
|
|
//$allow_html ? '<br><br>': "\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 ? '<br><br>--<br>': "\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 : <strong>%2$s</strong>.', '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( '<br/>%s : <i>%s</i>', __('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
|
|
|
|
|
|
?><?php
|
|
|
|
if ( !class_exists( '\Nimble\Sek_Nimble_Manager' ) ) :
|
|
final class Sek_Nimble_Manager extends Sek_Simple_Form {}
|
|
endif;
|
|
|
|
function Nimble_Manager( $params = array() ) {
|
|
return Sek_Nimble_Manager::get_instance( $params );
|
|
}
|
|
|
|
Nimble_Manager();
|
|
?>
|