<?php
if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly
}
class TitanFrameworkCustomizer {
private $defaultSettings = array(
'name' => '', // Name of the menu item
// 'parent' => null, // slug of parent, if blank, then this is a top level menu
'id' => '', // Unique ID of the menu item
'panel' => '', // The Name of the panel to create
'panel_desc' => '', // The description to display on the panel
'panel_id' => '', // The panel ID to create / add to. If this is blank & `panel` is given, this will be generated
'capability' => 'edit_theme_options', // User role
// 'icon' => 'dashicons-admin-generic', // Menu icon for top level menus only
'desc' => '', // Description
'position' => 30,// Menu position for top level menus only
);
public $settings;
public $options = array();
public $owner;
// Makes sure we only load live previewing CSS only once
private static $namespacesWithPrintedPreviewCSS = array();
function __construct( $settings, $owner ) {
$this->owner = $owner;
$this->settings = array_merge( $this->defaultSettings, $settings );
if ( empty( $this->settings['name'] ) ) {
$this->settings['name'] = __( 'More Options', TF_I18NDOMAIN );
}
if ( empty( $this->settings['id'] ) ) {
$this->settings['id'] = $this->owner->optionNamespace . '_' . str_replace( ' ', '-', trim( strtolower( $this->settings['name'] ) ) );
}
if ( empty( $this->settings['panel_id'] ) && ! empty( $this->settings['panel'] ) ) {
$this->settings['panel_id'] = $this->owner->optionNamespace . '_' . str_replace( ' ', '-', trim( strtolower( $this->settings['panel'] ) ) );
}
// Register the customizer control.
add_action( 'customize_register', array( $this, 'register' ) );
// Enqueue required customizer styles & scripts.
tf_add_action_once( 'customize_controls_enqueue_scripts', array( $this, 'loadUploaderScript' ) );
// Clear local storage, we use it for remembering modified customizer values.
tf_add_action_once( 'customize_controls_print_footer_scripts', array( $this, 'initLocalStorage' ) );
// Generate the custom CSS for live previews.
tf_add_action_once( 'wp_ajax_tf_generate_customizer_css', array( $this, 'ajaxGenerateCustomizerCSS' ) );
// Modify the values of the options for the generation of CSS with the values from the customizer $_POST.
global $wp_customize;
if ( isset( $wp_customize ) ) {
tf_add_filter_once( 'tf_pre_get_value_' . $this->owner->optionNamespace, array( $this, 'useCustomizerModifiedValue' ), 10, 3 );
}
}
public function loadUploaderScript() {
wp_enqueue_media();
wp_enqueue_script( 'tf-theme-customizer-serialize', TitanFramework::getURL( '../js/min/serialize-min.js', __FILE__ ) );
wp_enqueue_style( 'tf-admin-theme-customizer-styles', TitanFramework::getURL( '../css/admin-theme-customizer-styles.css', __FILE__ ) );
}
public function getID() {
return $this->settings['id'];
}
/**
* Ajax handler for generating CSS based on the existing options with values changed to
* match the customizer modified values.
*
* @since 1.9.2
*
* @return void
*/
public function ajaxGenerateCustomizerCSS() {
// This value is passed back to the live preview ajax handler in $this->livePreviewMainScript()
$generated = array(
'css' => '',
);
foreach ( TitanFramework::getAllInstances() as $framework ) {
// Modify the values of the options for the generation of CSS with the values from the customizer $_POST.
$namespace = $framework->optionNamespace;
add_filter( "tf_pre_get_value_{$namespace}", array( $this, 'useCustomizerModifiedValue' ), 10, 3 );
// Generate our new CSS based on the customizer values
$css = $framework->cssInstance->generateCSS();
$generated['css'] .= $css;
/**
* Allow options to add customizer live preview parameters. The tf_generate_customizer_preview_js hook allows for manipulating these values.
*
* @since 1.9.2
*
* @see tf_generate_customizer_preview_js
*/
$generated = apply_filters( "tf_generate_customizer_preview_css_{$namespace}", $generated );
}
wp_send_json_success( $generated );
}
/**
* Override the getOption value with the customizer value which comes from the $_POST array
*
* @since 1.9.2
*
* @param mixed $value The value of the option.
* @param int $postID The post ID if there is one (always null in this case).
* @param TitanFrameworkOption $option The option being parsed.
*
* @return mixed The new value
*
* @see tf_pre_get_value_{namespace}
*/
public function useCustomizerModifiedValue( $value, $postID, $option ) {
if ( empty( $_POST ) ) {
return $value;
}
if ( ! is_array( $_POST ) ) {
return $value;
}
if ( array_key_exists( $option->getID(), $_POST ) ) {
return $_POST[ $option->getID() ];
}
if ( ! empty( $_POST['customized'] ) ) {
$customizedSettings = (array) json_decode( stripslashes( $_POST['customized'] ) );
if ( is_array( $customizedSettings ) && ! empty( $customizedSettings ) ) {
if ( array_key_exists( $option->getID(), $customizedSettings ) ) {
return $customizedSettings[ $option->getID() ];
}
}
}
return $value;
}
/**
* Prints the script that clears the JS local storage when customizer loads, this ensures we start fresh.
* Use localStorage so we can still use values when the customizer refreshes.
*
* @since 1.9.2
*
* @return void
*/
public function initLocalStorage() {
?>
<script>
if ( typeof localStorage !== 'undefined' ) {
localStorage.clear();
}
</script>
<?php
}
/**
* Prints the script that uses ajax to adjust the live customizer CSS.
* Use localStorage so we can still use values when the customizer refreshes.
*
* @since 1.9.2
*
* @return void
*/
public function livePreviewMainScript() {
?>
<script>
window.tf_refresh_css = function() {
if ( typeof localStorage !== 'undefined' ) {
// Using localStorage directly as an object-value dictionary doesn't work in FF, create a new object
var localStorageData = {}, keys = Object.keys( localStorage );
for ( var i in keys ) {
localStorageData[ keys[ i ] ] = localStorage.getItem( keys[ i ] );
}
wp.ajax.send( 'tf_generate_customizer_css', {
success: function( data ) {
// Add the modified CSS Titan has generated from the preview values.
var style = document.querySelector('style#tf_live_preview');
if ( ! style ) {
var style = document.createElement('STYLE');
style.setAttribute( 'id', 'tf_live_preview' );
style.innerHTML = data.css;
document.head.appendChild( style );
} else {
style.innerHTML = data.css;
}
<?php
/**
* Render additional Javascript code for handling different data received for
* live previewing
*
* @since 1.9.2
*
* @see $this->ajaxGenerateCustomizerCSS()
*/
do_action( 'tf_generate_customizer_preview_js' );
?>
},
data: localStorageData
});
}
};
</script>
<?php
}
/**
* Prints the script PER option that handles customizer option changes for live previews
*
* @since 1.0
*
* @return void
*/
public function livePreview() {
$printStart = false;
foreach ( $this->options as $option ) {
if ( empty( $option->settings['css'] ) && empty( $option->settings['livepreview'] ) ) {
continue;
}
// Print the starting script tag.
if ( ! $printStart ) {
$printStart = true;
?>
<script>
jQuery(document).ready(function($) {
<?php
}
?>
wp.customize( '<?php echo $option->getID() ?>', function( v ) {
v.bind( function( value ) {
<?php
if ( empty( $option->settings['livepreview'] ) ) {
/**
* If css parameter is given and there is no custom livepreview,
* we can simulate live previewing using just the css parameter.
*/
?>
if ( typeof localStorage !== 'undefined' ) {
localStorage.setItem( '<?php echo $option->getID() ?>', value );
}
window.tf_refresh_css();
<?php
} else {
/**
* If the livepreview parameter is given, use that. This is the original behavior.
*/
// Some options may want to insert custom jQuery code before manipulation of live preview/
if ( ! empty( $option->settings['id'] ) ) {
do_action( 'tf_livepreview_pre_' . $this->owner->optionNamespace, $option->settings['id'], $option->settings['type'], $option );
}
echo $option->settings['livepreview'];
// Some options may want to insert custom jQuery code after manipulation of live preview.
if ( ! empty( $option->settings['id'] ) ) {
do_action( 'tf_livepreview_post_' . $this->owner->optionNamespace, $option->settings['id'], $option->settings['type'], $option );
}
}
?>
} );
} );
<?php
}
// Print the ending script tag.
if ( $printStart ) {
?>
});
</script>
<?php
}
}
/**
* Prints out CSS styles for the current namespace refresh previewing
*
* @since 1.3
*
* @return void
*
* @see self::$namespacesWithPrintedPreviewCSS
*/
public function printPreviewCSS() {
// Only print the styles once per namespace
if ( ! in_array( $this->owner->optionNamespace, self::$namespacesWithPrintedPreviewCSS ) ) {
self::$namespacesWithPrintedPreviewCSS[] = $this->owner->optionNamespace;
echo '<style id="titan-preview-' . esc_attr( $this->owner->optionNamespace ) . '">';
echo $this->owner->cssInstance->generateCSS();
echo '</style>';
}
}
public function register( $wp_customize ) {
add_action( 'wp_head', array( $this, 'printPreviewCSS' ), 1000 );
// Create the panel
if ( ! empty( $this->settings['panel_id'] ) ) {
$existingPanels = $wp_customize->panels();
if ( ! array_key_exists( $this->settings['panel_id'], $existingPanels ) ) {
$wp_customize->add_panel( $this->settings['panel_id'], array(
'title' => $this->settings['panel'],
'priority' => $this->settings['position'],
'capability' => $this->settings['capability'],
'description' => ! empty( $this->settings['panel_desc'] ) ? $this->settings['panel_desc'] : '',
) );
}
}
// Create the section
$existingSections = $wp_customize->sections();
if ( ! array_key_exists( $this->settings['id'], $existingSections ) ) {
$wp_customize->add_section( $this->settings['id'], array(
'title' => $this->settings['name'],
'priority' => $this->settings['position'],
'description' => $this->settings['desc'],
'capability' => $this->settings['capability'],
'panel' => empty( $this->settings['panel_id'] ) ? '' : $this->settings['panel_id'],
) );
}
// Unfortunately we have to call each option's register from here
foreach ( $this->options as $index => $option ) {
if ( ! empty( $option->settings['id'] ) ) {
$namespace = $this->owner->optionNamespace;
$option_type = $option->settings['type'];
$transport = empty( $option->settings['livepreview'] ) && empty( $option->settings['css'] ) ? 'refresh' : 'postMessage';
// Allow options to override the transport parameter
if ( ! empty( $option->settings['transport'] ) ) {
$transport = $option->settings['transport'];
}
/**
* Allow options to override the transport mode of an option in the customizer
*
* @since 1.9.2
*/
$transport = apply_filters( "tf_customizer_transport_{$option_type}_{$namespace}", $transport, $option );
$wp_customize->add_setting( $option->getID(), array(
'default' => $option->settings['default'],
'transport' => $transport,
'sanitize_callback' => $option->settings['sanitize_callback'], // Sanitize callback function can be used before storing to DB
) );
}
// We add the index here, this will be used to order the controls because of this minor bug:
// https://core.trac.wordpress.org/ticket/20733
$option->registerCustomizerControl( $wp_customize, $this, $index + 100 );
}
add_action( 'wp_footer', array( $this, 'livePreview' ) );
tf_add_action_once( 'wp_footer', array( $this, 'livePreviewMainScript' ) );
}
public function createOption( $settings ) {
if ( ! apply_filters( 'tf_create_option_continue_' . $this->owner->optionNamespace, true, $settings ) ) {
return null;
}
$obj = TitanFrameworkOption::factory( $settings, $this );
$this->options[] = $obj;
do_action( 'tf_create_option_' . $this->owner->optionNamespace, $obj );
return $obj;
}
}