Introduce new theme customizer to replace theme preview. Rough first pass. props koopersmith, ocean90. see #19910.

Merges in http://plugins.svn.wordpress.org/gandalf/branches/dev/ rev 510148.

git-svn-id: http://svn.automattic.com/wordpress/trunk@19995 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
koopersmith 2012-02-25 04:12:43 +00:00
parent 8859ba872c
commit c832f904ae
18 changed files with 2396 additions and 0 deletions

View File

@ -0,0 +1,83 @@
<?php
/**
* Customize Section Class
*
* @package WordPress
* @subpackage Customize
* @since 3.4.0
*/
class WP_Customize_Section {
public $id;
public $priority = 10;
public $capability = 'edit_theme_options';
public $theme_supports = '';
public $title = '';
public $description = '';
public $settings;
/**
* Constructor.
*
* @since 3.4.0
*
* @param string $id An specific ID of the section.
* @param array $args Section arguments.
*/
function __construct( $id, $args = array() ) {
$this->id = $id;
$keys = array_keys( get_class_vars( __CLASS__ ) );
foreach ( $keys as $key ) {
if ( isset( $args[ $key ] ) )
$this->$key = $args[ $key ];
}
$this->settings = array(); // Users cannot customize the $settings array.
return $this;
}
/**
* Check if the theme supports the section and check user capabilities.
*
* @since 3.4.0
*
* @return bool False if theme doesn't support the section or user doesn't have the capability.
*/
function check_capabilities() {
if ( ! $this->capability || ! current_user_can( $this->capability ) )
return false;
if ( $this->theme_supports && ! current_theme_supports( $this->theme_supports ) )
return false;
return true;
}
/**
* Render the section.
*
* @since 3.4.0
*/
function render() {
if ( ! $this->check_capabilities() )
return;
?>
<li id="customize-section-<?php echo esc_attr( $this->id ); ?>" class="control-section customize-section">
<h3 class="customize-theme-title"><?php echo esc_html( $this->title ); ?></h3>
<ul>
<?php if ( $this->description ) : ?>
<li><p class="howto"><?php echo $this->description; ?></p></li>
<?php endif; ?>
<?php foreach ( $this->settings as $setting ) : ?>
<li>
<?php $setting->_render(); ?>
</li>
<?php endforeach; ?>
</ul>
</li>
<?php
}
}

View File

@ -0,0 +1,489 @@
<?php
/**
* Customize Setting Class
*
* @package WordPress
* @subpackage Customize
* @since 3.4.0
*/
class WP_Customize_Setting {
public $id;
public $priority = 10;
public $section = '';
public $label = '';
public $control = 'text';
public $type = 'theme_mod';
public $choices = array();
public $capability = 'edit_theme_options';
public $theme_supports = '';
public $default = '';
public $sanitize_callback = '';
protected $id_data = array();
private $_post_value; // Cached, sanitized $_POST value.
// Prefix for $_POST values to prevent naming conflicts.
const name_prefix = 'customize_';
/**
* Constructor.
*
* @since 3.4.0
*
* @param string $id An specific ID of the setting. Can be a
* theme mod or option name.
* @param array $args Setting arguments.
*/
function __construct( $id, $args = array() ) {
$keys = array_keys( get_class_vars( __CLASS__ ) );
foreach ( $keys as $key ) {
if ( isset( $args[ $key ] ) )
$this->$key = $args[ $key ];
}
$this->id = $id;
// Parse the ID for array keys.
$this->id_data[ 'keys' ] = preg_split( '/\[/', str_replace( ']', '', $this->id ) );
$this->id_data[ 'base' ] = array_shift( $this->id_data[ 'keys' ] );
// Rebuild the ID.
$this->id = $this->id_data[ 'base' ];
if ( ! empty( $this->id_data[ 'keys' ] ) )
$this->id .= '[' . implode( '][', $this->id_data[ 'keys' ] ) . ']';
if ( $this->sanitize_callback != '' )
add_filter( "customize_sanitize_{$this->id}", $this->sanitize_callback );
return $this;
}
/**
* Enqueue setting related scripts/styles.
*
* @since 3.4.0
*/
public function enqueue() {
switch( $this->control ) {
case 'color':
wp_enqueue_script( 'farbtastic' );
wp_enqueue_style( 'farbtastic' );
break;
}
}
/**
* Handle previewing the setting.
*
* @since 3.4.0
*/
public function preview() {
switch( $this->type ) {
case 'theme_mod' :
add_filter( 'theme_mod_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) );
break;
case 'option' :
if ( empty( $this->id_data[ 'keys' ] ) )
add_filter( 'pre_option_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) );
else
add_filter( 'option_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) );
break;
default :
do_action( 'customize_preview_' . $this->id );
}
}
/**
* Callback function to filter the theme mods and options.
*
* @since 3.4.0
*
* @param mixed Old value.
* @return mixed New or old value.
*/
public function _preview_filter( $original ) {
return $this->multidimensional_replace( $original, $this->id_data[ 'keys' ], $this->post_value() );
}
/**
* Set the value of the parameter for a specific theme.
*
* @since 3.4.0
*
* @return bool False if cap check fails or value isn't set.
*/
public final function save() {
$value = $this->post_value();
if ( ! $this->check_capabilities() || ! isset( $value ) )
return false;
do_action( 'customize_save_' . $this->id_data[ 'base' ] );
$this->update( $value );
}
/**
* Fetches, validates, and sanitizes the $_POST value.
*
* @since 3.4.0
*
* @param $default mixed A default value which is used as a fallback. Default is null.
* @return mixed Either the default value on failure or sanitized value.
*/
public final function post_value( $default = null ) {
if ( isset( $this->_post_value ) )
return $this->_post_value;
$base = self::name_prefix . $this->id_data[ 'base' ];
if ( ! isset( $_POST[ $base ] ) )
return $default;
$result = $this->multidimensional_get( $_POST[ $base ], $this->id_data[ 'keys' ] );
if ( ! isset( $result ) )
return $default;
$result = $this->sanitize( $result );
if ( isset( $result ) )
return $this->_post_value = $result;
else
return $default;
}
/**
* Sanitize an input.
*
* @since 3.4.0
*
* @param $value mixed The value to sanitize.
* @return mixed Null if an input isn't valid, otherwise the sanitized value.
*/
public function sanitize( $value ) {
return apply_filters( "customize_sanitize_{$this->id}", $value );
}
/**
* Set the value of the parameter for a specific theme.
*
* @since 3.4.0
*
* @param $value mixed The value to update.
* @return mixed The result of saving the value.
*/
protected function update( $value ) {
switch( $this->type ) {
case 'theme_mod' :
return $this->_update_theme_mod( $value );
break;
case 'option' :
return $this->_update_option( $value );
break;
default :
return do_action( 'customize_update_' . $this->type, $value );
}
}
/**
* Update the theme mod from the value of the parameter.
*
* @since 3.4.0
*
* @param $value mixed The value to update.
* @return mixed The result of saving the value.
*/
protected function _update_theme_mod( $value ) {
// Handle non-array theme mod.
if ( empty( $this->id_data[ 'keys' ] ) )
return set_theme_mod( $this->id_data[ 'base' ], $value );
// Handle array-based theme mod.
$mods = get_theme_mod( $this->id_data[ 'base' ] );
$mods = $this->multidimensional_replace( $mods, $this->id_data[ 'keys' ], $value );
if ( isset( $mods ) )
return set_theme_mod( $this->id_data[ 'base' ], $mods );
}
/**
* Update the theme mod from the value of the parameter.
*
* @since 3.4.0
*
* @param $value mixed The value to update.
* @return mixed The result of saving the value.
*/
protected function _update_option( $value ) {
// Handle non-array option.
if ( empty( $this->id_data[ 'keys' ] ) )
return update_option( $this->id_data[ 'base' ], $value );
// Handle array-based options.
$options = get_option( $this->id_data[ 'base' ] );
$options = $this->multidimensional_replace( $options, $this->id_data[ 'keys' ], $value );
if ( isset( $options ) )
return update_option( $this->id_data[ 'base' ], $options );
}
/**
* Fetch the value of the parameter for a specific theme.
*
* @since 3.4.0
*
* @return mixed The requested value.
*/
public function value() {
switch( $this->type ) {
case 'theme_mod' :
$function = 'get_theme_mod';
break;
case 'option' :
$function = 'get_option';
break;
default :
return apply_filters( 'customize_value_' . $this->id_data[ 'base' ], $this->default );
}
// Handle non-array value
if ( empty( $this->id_data[ 'keys' ] ) )
return $function( $this->id_data[ 'base' ], $this->default );
// Handle array-based value
$values = $function( $this->id_data[ 'base' ] );
return $this->multidimensional_get( $values, $this->id_data[ 'keys' ], $this->default );
}
/**
* Check if the theme supports the setting and check user capabilities.
*
* @since 3.4.0
*
* @return bool False if theme doesn't support the setting or user can't change setting, otherwise true.
*/
public final function check_capabilities() {
global $customize;
if ( ! $this->capability || ! current_user_can( $this->capability ) )
return false;
if ( $this->theme_supports && ! current_theme_supports( $this->theme_supports ) )
return false;
$section = $customize->get_section( $this->section );
if ( isset( $section ) && ! $section->check_capabilities() )
return false;
return true;
}
/**
* Render the control.
*
* @since 3.4.0
*/
public final function _render() {
if ( ! $this->check_capabilities() )
return;
do_action( 'customize_render_' . $this->id );
$this->render();
}
/**
* Render the control.
*
* @since 3.4.0
*/
protected function render() {
$this->_render_type();
}
/**
* Retrieve the name attribute for an input.
*
* @since 3.4.0
*
* @return string The name.
*/
public final function get_name() {
return self::name_prefix . esc_attr( $this->id );
}
/**
* Echo the HTML name attribute for an input.
*
* @since 3.4.0
*
* @return string The HTML name attribute.
*/
public final function name() {
echo 'name="' . $this->get_name() . '"';
}
/**
* Render the control type.
*
* @todo Improve value and checked attributes.
*
* @since 3.4.0
*/
public final function _render_type() {
switch( $this->control ) {
case 'text':
?>
<label><?php echo esc_html( $this->label ); ?><br/>
<input type="text" value="<?php echo esc_attr( $this->value() ); ?>" <?php $this->name(); ?> />
</label>
<?php
break;
case 'color':
?>
<label><?php echo esc_html( $this->label ); ?><br/>
<span class="hex-prepend">#</span>
<input type="text" class="hex-input" value="<?php echo esc_attr( $this->value() ); ?>" <?php $this->name(); ?> />
<a href="#" class="pickcolor hex-color-example"></a>
</label>
<?php
break;
case 'checkbox':
?>
<label>
<input type="checkbox" value="<?php echo esc_attr( $this->value() ); ?>" <?php $this->name(); checked( $this->value() ); ?> />
<?php echo esc_html( $this->label ); ?>
</label>
<?php
break;
case 'radio':
if ( empty( $this->choices ) )
return;
echo esc_html( $this->label ) . '<br/>';
foreach ( $this->choices as $value => $label ) :
?>
<label>
<input type="radio" value="<?php echo esc_attr( $value ); ?>" <?php $this->name(); checked( $this->value(), $value ); ?> />
<?php echo esc_html( $label ); ?><br/>
</label>
<?php
endforeach;
break;
case 'select':
if ( empty( $this->choices ) )
return;
?>
<label><?php echo esc_html( $this->label ); ?><br/>
<select <?php $this->name(); ?>>
<?php
foreach ( $this->choices as $value => $label )
echo '<option value="' . esc_attr( $value ) . '"' . selected( $this->value(), $value, false ) . '>' . $label . '</option>';
?>
</select>
<?php
break;
default:
do_action( 'customize_render_control-' . $this->control, $this );
}
}
/**
* Multidimensional helper function.
*
* @since 3.4.0
*
* @param $root
* @param $keys
* @param bool $create Default is false.
* @return null|array
*/
final protected function multidimensional( $root, $keys, $create = false ) {
if ( $create && empty( $root ) )
$root = array();
if ( ! isset( $root ) || empty( $keys ) )
return;
$last = array_pop( $keys );
$node = &$root;
foreach ( $keys as $key ) {
if ( $create && ! isset( $node[ $key ] ) )
$node[ $key ] = array();
if ( ! is_array( $node ) || ! isset( $node[ $key ] ) )
return;
$node = &$node[ $key ];
}
if ( $create && ! isset( $node[ $last ] ) )
$node[ $last ] = array();
if ( ! isset( $node[ $last ] ) )
return;
return array(
'root' => &$root,
'node' => &$node,
'key' => $last,
);
}
/**
* Will attempt to replace a specific value in a multidimensional array.
*
* @since 3.4.0
*
* @param $root
* @param $keys
* @param mixed $value The value to update.
* @return
*/
final protected function multidimensional_replace( $root, $keys, $value ) {
if ( ! isset( $value ) )
return $root;
elseif ( empty( $keys ) ) // If there are no keys, we're replacing the root.
return $value;
$result = $this->multidimensional( &$root, $keys, true );
if ( isset( $result ) )
$result['node'][ $result['key'] ] = $value;
return $root;
}
/**
* Will attempt to fetch a specific value from a multidimensional array.
*
* @since 3.4.0
*
* @param $root
* @param $keys
* @param $default A default value which is used as a fallback. Default is null.
* @return mixed The requested value or the default value.
*/
final protected function multidimensional_get( $root, $keys, $default = null ) {
if ( empty( $keys ) ) // If there are no keys, test the root.
return isset( $root ) ? $root : $default;
$result = $this->multidimensional( $root, $keys );
return isset( $result ) ? $result['node'][ $result['key'] ] : $default;
}
/**
* Will attempt to check if a specific value in a multidimensional array is set.
*
* @since 3.4.0
*
* @param $root
* @param $keys
* @return bool True if value is set, false if not.
*/
final protected function multidimensional_isset( $root, $keys ) {
$result = $this->multidimensional_get( $root, $keys );
return isset( $result );
}
}

View File

@ -0,0 +1,660 @@
<?php
/**
* Customize
*
* @package WordPress
* @subpackage Customize
* @since 3.4.0
*/
final class WP_Customize {
protected $template;
protected $stylesheet;
protected $previewing = false;
protected $settings = array();
protected $sections = array();
/**
* Constructor.
*
* @since 3.4.0
*/
public function __construct() {
require( ABSPATH . WPINC . '/class-wp-customize-setting.php' );
require( ABSPATH . WPINC . '/class-wp-customize-section.php' );
add_action( 'setup_theme', array( $this, 'setup_theme' ) );
add_action( 'admin_init', array( $this, 'admin_init' ) );
add_action( 'init', array( $this, 'init' ) );
add_action( 'admin_footer', array( $this, 'admin_footer' ) );
add_action( 'customize_previewing', array( $this, 'customize_previewing' ) );
add_action( 'customize_register', array( $this, 'register_controls' ) );
add_action( 'customize_controls_init', array( $this, 'prepare_controls' ) );
}
/**
* Update theme modifications for the current theme.
* Note: Candidate core function.
* http://core.trac.wordpress.org/ticket/20091
*
* @since 3.4.0
*
* @param array $mods Theme modifications.
*/
public function set_theme_mods( $mods ) {
$current = get_theme_mods();
$mods = wp_parse_args( $mods, $current );
$theme = get_stylesheet();
update_option( "theme_mods_$theme", $mods );
}
/**
* Start preview and customize theme.
* Check if customize query variable exist.
*
* @since 3.4.0
*/
public function setup_theme() {
if ( ! isset( $_REQUEST['customize'] ) || 'on' != $_REQUEST['customize'] )
return;
if ( ! $this->set_stylesheet() || isset( $_REQUEST['save'] ) )
return;
$this->previewing = true;
do_action( 'customize_previewing' );
}
/**
* Init filters to filter theme options.
*
* @since 3.4.0
*/
public function customize_previewing() {
global $wp_theme_directories;
show_admin_bar( false );
add_filter( 'template', array( $this, 'get_template' ) );
add_filter( 'stylesheet', array( $this, 'get_stylesheet' ) );
add_filter( 'pre_option_current_theme', array( $this, 'current_theme' ) );
// @link: http://core.trac.wordpress.org/ticket/20027
add_filter( 'pre_option_stylesheet', array( $this, 'get_stylesheet' ) );
add_filter( 'pre_option_template', array( $this, 'get_template' ) );
// Handle custom theme roots.
if ( count( $wp_theme_directories ) > 1 ) {
add_filter( 'pre_option_stylesheet_root', array( $this, 'get_stylesheet_root' ) );
add_filter( 'pre_option_template_root', array( $this, 'get_template_root' ) );
}
}
/**
* Register styles/scripts and Init the preview of each setting
*
* @since 3.4.0
*/
public function init() {
do_action( 'customize_register' );
if ( ! $this->is_preview() )
return;
wp_enqueue_script( 'customize-preview' );
add_action( 'wp_footer', array( $this, 'customize_preview_settings' ), 20 );
foreach ( $this->settings as $setting ) {
$setting->preview();
}
}
/**
* Print javascript settings.
*
* @since 3.4.0
*/
public function customize_preview_settings() {
$settings = array(
// @todo: Perhaps grab the URL via $_POST?
'parent' => esc_url( admin_url( 'themes.php' ) ),
);
?>
<script type="text/javascript">
(function() {
if ( typeof wp === 'undefined' || ! wp.customize )
return;
wp.customize.settings = <?php echo json_encode( $settings ); ?>;
})();
</script>
<?php
}
/**
* Is it a theme preview?
*
* @since 3.4.0
*
* @return bool True if it's a preview, false if not.
*/
public function is_preview() {
return (bool) $this->previewing;
}
/**
* Set the template name of the previewed theme.
*
* @since 3.4.0
*
* @return bool|string Template name.
*/
public function set_template() {
if ( ! empty( $this->template ) )
return $this->template;
$template = preg_replace('|[^a-z0-9_./-]|i', '', $_REQUEST['template'] );
if ( validate_file( $template ) )
return false;
return $this->template = $template;
}
/**
* Set the stylesheet name of the previewed theme.
*
* @since 3.4.0
*
* @return bool|string Stylesheet name.
*/
public function set_stylesheet() {
if ( ! empty( $this->stylesheet ) )
return $this->stylesheet;
$this->set_template();
if ( empty( $this->template ) )
return false;
if ( empty( $_REQUEST['stylesheet'] ) ) {
$stylesheet = $this->template;
} else {
$stylesheet = preg_replace( '|[^a-z0-9_./-]|i', '', $_REQUEST['stylesheet'] );
if ( $stylesheet != $this->template && validate_file( $stylesheet ) )
return false;
}
return $this->stylesheet = $stylesheet;
}
/**
* Retrieve the template name of the previewed theme.
*
* @since 3.4.0
*
* @return string Template name.
*/
public function get_template() {
return $this->template;
}
/**
* Retrieve the stylesheet name of the previewed theme.
*
* @since 3.4.0
*
* @return string Stylesheet name.
*/
public function get_stylesheet() {
return $this->stylesheet;
}
/**
* Retrieve the template root of the previewed theme.
*
* @since 3.4.0
*
* @return string Theme root.
*/
public function get_template_root() {
return get_raw_theme_root( $this->template, true );
}
/**
* Retrieve the stylesheet root of the previewed theme.
*
* @since 3.4.0
*
* @return string Theme root.
*/
public function get_stylesheet_root() {
return get_raw_theme_root( $this->stylesheet, true );
}
/**
* Filter the current theme and return the name of the previewed theme.
*
* @since 3.4.0
*
* @return string Theme name.
*/
public function current_theme( $current_theme ) {
$themes = get_themes();
if ( ! $themes )
return $current_theme;
foreach ( $themes as $theme ) {
if ( $theme['Stylesheet'] == $this->stylesheet && $theme['Template'] == $this->template )
return $theme['Name'];
}
return $current_theme;
}
/**
* Trigger save action and load customize controls.
*
* @since 3.4.0
*/
public function admin_init() {
if ( isset( $_REQUEST['save'] ) )
$this->save();
wp_enqueue_script( 'customize-loader' );
wp_enqueue_style( 'customize-loader' );
if ( ( defined( 'DOING_AJAX' ) && DOING_AJAX ) )
return;
if ( ! isset( $_GET['customize'] ) || 'on' != $_GET['customize'] )
return;
if ( ! $this->is_preview() )
return;
if ( ! current_user_can( 'edit_theme_options' ) )
return;
include( ABSPATH . WPINC . '/customize-controls.php' );
die;
}
/**
* Print the customize template.
*
* @since 3.4.0
*/
public function admin_footer() {
?>
<div id="customize-container">
<input type="hidden" class="admin-url" value="<?php echo esc_url( admin_url( 'admin.php' ) ); ?>" />
<div>
<a href="#" class="return-to-admin"><?php printf( __( '&larr; Return to %s' ), get_admin_page_title() ); ?></a>
<a href="#" class="collapse-sidebar button-secondary" title="<?php esc_attr_e('Collapse Sidebar'); ?>"><span></span></a>
</div>
</div>
<?php
}
/**
* Switch the theme and trigger the save action of each setting.
*
* @since 3.4.0
*/
public function save() {
if ( $this->is_preview() )
return;
check_admin_referer( 'customize_controls' );
if ( ! $this->set_stylesheet() )
return;
$active_template = get_template();
$active_stylesheet = get_stylesheet();
// Do we have to switch themes?
if ( $this->get_template() != $active_template || $this->get_stylesheet() != $active_stylesheet ) {
if ( ! current_user_can( 'switch_themes' ) )
return;
switch_theme( $this->get_template(), $this->get_stylesheet() );
}
do_action( 'customize_save' );
foreach ( $this->settings as $setting ) {
$setting->save();
}
add_action( 'admin_notices', array( $this, '_save_feedback' ) );
}
/**
* Show an admin notice after settings are saved.
*
* @since 3.4.0
*/
public function _save_feedback() {
?>
<div class="updated"><p><?php printf( __( 'Settings saved and theme activated. <a href="%s">Visit site</a>.' ), home_url( '/' ) ); ?></p></div>
<?php
}
/**
* Add a customize setting.
*
* @since 3.4.0
*
* @param string $id An specific ID of the setting. Can be a
* theme mod or option name.
* @param array $args Setting arguments.
*/
public function add_setting( $id, $args = array() ) {
$setting = new WP_Customize_Setting( $id, $args );
$this->settings[ $setting->id ] = $setting;
}
/**
* Retrieve a customize setting.
*
* @since 3.4.0
*
* @param string $id An specific ID of the setting.
* @return object The settings object.
*/
public function get_setting( $id ) {
if ( isset( $this->settings[ $id ] ) )
return $this->settings[ $id ];
}
/**
* Remove a customize setting.
*
* @since 3.4.0
*
* @param string $id An specific ID of the setting.
*/
public function remove_setting( $id ) {
unset( $this->settings[ $id ] );
}
/**
* Add a customize section.
*
* @since 3.4.0
*
* @param string $id An specific ID of the section.
* @param array $args Section arguments.
*/
public function add_section( $id, $args = array() ) {
$section = new WP_Customize_Section( $id, $args );
$this->sections[ $section->id ] = $section;
}
/**
* Retrieve a customize section.
*
* @since 3.4.0
*
* @param string $id An specific ID of the section.
* @return object The section object.
*/
public function get_section( $id ) {
if ( isset( $this->sections[ $id ] ) )
return $this->sections[ $id ];
}
/**
* Remove a customize section.
*
* @since 3.4.0
*
* @param string $id An specific ID of the section.
*/
public function remove_section( $id ) {
unset( $this->sections[ $id ] );
}
/**
* Helper function to compare two objects by priority.
*
* @since 3.4.0
*
* @param object $a Object A.
* @param object $b Object B.
*/
protected function _cmp_priority( $a, $b ) {
$ap = $a->priority;
$bp = $b->priority;
if ( $ap == $bp )
return 0;
return ( $ap > $bp ) ? 1 : -1;
}
/**
* Prepare settings and sections. Also enqueue needed scripts/styles.
*
* @since 3.4.0
*/
public function prepare_controls() {
// Reversing makes uasort sort by time added when conflicts occur.
$this->sections = array_reverse( $this->sections );
uasort( $this->sections, array( $this, '_cmp_priority' ) );
$this->settings = array_reverse( $this->settings );
foreach ( $this->settings as $setting ) {
if ( ! isset( $this->sections[ $setting->section ] ) )
continue;
$this->sections[ $setting->section ]->settings[] = $setting;
if ( $setting->check_capabilities() )
$setting->enqueue();
}
foreach ( $this->sections as $section ) {
usort( $section->settings, array( $this, '_cmp_priority' ) );
}
}
/**
* Register some default controls.
*
* @since 3.4.0
*/
public function register_controls() {
/* Custom Header */
$this->add_section( 'header', array(
'title' => __( 'Header' ),
'theme_supports' => 'custom-header',
) );
$this->add_setting( 'header_textcolor', array(
'label' => 'Text Color',
'section' => 'header',
'sanitize_callback' => 'sanitize_hexcolor',
'control' => 'color',
'default' => defined( 'HEADER_TEXTCOLOR' ) ? HEADER_TEXTCOLOR : ''
) );
/*
$this->add_setting( 'display_header', array(
'label' => 'Display Text',
'section' => 'header',
'type' => 'radio',
'choices' => array(
'show' => 'Yes',
'hide' => 'No'
),
// Showing header text is actually done by setting header_textcolor to 'blank'.
// @todo: Do some JS magic to make this work (since we'll be hiding the textcolor input).
'theme_mod' => false,
) );
*/
// Input type: checkbox
// With custom value
$this->add_setting( 'header_image', array(
'label' => 'Random Image',
'section' => 'header',
'control' => 'checkbox',
// @todo
// not the default, it's the value.
// value is saved in get_theme_support( 'custom-header' )[0][ 'random-default' ]
'default' => 'random-default-image'
) );
/* Custom Background */
$this->add_section( 'background', array(
'title' => __( 'Background' ),
'theme_supports' => 'custom-background',
) );
// Input type: Color
// With sanitize_callback
$this->add_setting( 'background_color', array(
'label' => 'Background Color',
'section' => 'background',
'control' => 'color',
'default' => defined( 'BACKGROUND_COLOR' ) ? BACKGROUND_COLOR : '',
'sanitize_callback' => 'sanitize_hexcolor'
) );
/* Nav Menus */
$locations = get_registered_nav_menus();
$menus = wp_get_nav_menus();
$menu_locations = get_nav_menu_locations();
$num_locations = count( array_keys( $locations ) );
$this->add_section( 'nav', array(
'title' => __( 'Navigation' ),
'theme_supports' => 'menus',
'description' => sprintf( _n('Your theme supports %s menu. Select which menu you would like to use.', 'Your theme supports %s menus. Select which menu appears in each location.', $num_locations ), number_format_i18n( $num_locations ) ),
) );
foreach ( $locations as $location => $description ) {
$choices = array( 0 => '' );
foreach ( $menus as $menu ) {
$truncated_name = wp_html_excerpt( $menu->name, 40 );
$truncated_name == $menu->name ? $menu->name : trim( $truncated_name ) . '&hellip;';
$choices[ $menu->term_id ] = $truncated_name;
}
$this->add_setting( "nav_menu_locations[{$location}]", array(
'label' => $description,
'theme_supports' => 'menus', // Todo: Needs also widgets -- array( 'menus', 'widgets' )
'section' => 'nav',
'control' => 'select',
'choices' => $choices,
'sanitize_callback' => 'absint',
) );
}
/* Static Front Page */
// #WP19627
$this->add_section( 'static_front_page', array(
'title' => __( 'Static Front Page' ),
// 'theme_supports' => 'static-front-page',
'description' => __( 'Your theme supports a static front page.' ),
) );
$choices = array();
$choices['posts'] = __( 'Your latest posts' );
$choices['page'] = __( 'A static page (select below)' );
$this->add_setting( 'show_on_front', array(
'label' => __( 'Front page displays' ),
// 'theme_supports' => 'static-front-page',
'section' => 'static_front_page',
'control' => 'radio',
'choices' => $choices,
'default' => get_option( 'show_on_front' ),
'type' => 'option',
'capability' => 'manage_options'
) );
$this->add_setting( 'page_on_front', array(
'label' => __( 'Front page:' ),
// 'theme_supports' => 'static-front-page',
'section' => 'static_front_page',
'control' => 'dropdown-pages',
'type' => 'option',
'capability' => 'manage_options'
) );
$this->add_setting( 'page_for_posts', array(
'label' => __( 'Posts page:' ),
// 'theme_supports' => 'static-front-page',
'section' => 'static_front_page',
'control' => 'dropdown-pages',
'type' => 'option',
'capability' => 'manage_options'
) );
/* Site Title & Tagline */
$this->add_section( 'strings', array(
'title' => __( 'Site Title & Tagline' ),
'description' => __( 'Customize some strings.' ),
) );
$this->add_setting( 'blogname', array(
'label' => __( 'Site Title' ),
'section' => 'strings',
'default' => get_option( 'blogname' ),
'type' => 'option',
'capability' => 'manage_options'
) );
$this->add_setting( 'blogdescription', array(
'label' => __( 'Tagline' ),
'section' => 'strings',
'default' => get_option( 'blogdescription' ),
'type' => 'option',
'capability' => 'manage_options'
) );
}
};
// Callback function for sanitizing a hex color
function sanitize_hexcolor( $color ) {
$color = preg_replace( '/[^0-9a-fA-F]/', '', $color );
if ( preg_match('|[A-Fa-f0-9]{3,6}|', $color ) )
return $color;
return $color;
}
// Custome render type for a dropdown menu.
function customize_control_dropdown_pages( $setting ) {
printf(
__( '<label>%s %s</label>' ),
$setting->label,
wp_dropdown_pages(
array(
'name' => $setting->get_name(),
'echo' => 0,
'show_option_none' => __( '&mdash; Select &mdash;' ),
'option_none_value' => '0',
'selected' => get_option( $setting->id )
)
)
);
}
add_action( 'customize_render_control-dropdown-pages', 'customize_control_dropdown_pages' );

View File

View File

@ -0,0 +1,216 @@
body {
overflow: hidden;
}
.customize-section {
border-top: 1px solid #fff;
border-bottom: 1px solid #dfdfdf;
padding: 15px 20px;
margin: 0;
}
.customize-section:last-child {
box-shadow: 0 1px 0 0px #fff;
}
#customize-controls {
width: 300px;
height: 100%;
padding: 0;
margin: 0;
z-index: 10;
position: relative;
overflow: auto;
background: #f5f5f5;
box-shadow: inset -11px 0 8px -8px rgba( 0, 0, 0, 0.1 );
border-right: 1px solid rgba( 0, 0, 0, 0.2 );
}
#customize-controls .theme-name {
font-size: 16px;
font-weight: bold;
line-height: 24px;
display: block;
}
#customize-controls .theme-screenshot {
width: 258px;
border: 1px solid #ccc;
}
#customize-controls .submit {
text-align: center;
}
#customize-info {
padding-top: 55px;
}
#customize-theme-controls {
padding-bottom: 60px;
}
#customize-theme-controls ul {
margin: 0;
}
#customize-theme-controls ul ul {
margin-top: 1em;
display: none;
}
#customize-theme-controls ul h3 {
margin: 0;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
#customize-theme-controls ul h3:hover {
cursor: pointer;
}
#customize-theme-controls ul h3:after {
content: '';
border-color: #ccc transparent transparent transparent;
border-style: solid;
border-width: 6px;
float: right;
margin-top: 5px;
margin-left: 5px;
}
#customize-theme-controls ul h3.open:after {
-moz-transform: rotate(180deg);
-webkit-transform: rotate(180deg);
-o-transform: rotate(180deg);
-ms-transform: rotate(180deg);
transform: rotate(180deg);
-moz-transform-origin: center 3px;
-webkit-transform-origin: center 3px;
-o-transform-origin: center 3px;
-ms-transform-origin: center 3px;
transform-origin: center 3px;
}
#customize-footer {
border-bottom: 0;
border-top: 1px solid #dfdfdf;
position: fixed;
bottom: 0;
left: 0;
width: 260px;
padding: 15px 20px;
margin: 0;
z-index: 10;
background: #f5f5f5;
box-shadow:
inset -11px 0 8px -8px rgba( 0, 0, 0, 0.1 ),
inset 0 1px 0 0px #fff,
0 0 11px 0 rgba( 0, 0, 0, 0.1 );
}
#customize-preview {
position: fixed;
left: 300px;
right: 0;
top: 0;
bottom: 0;
}
#customize-preview iframe {
width: 100%;
height: 100%;
}
.customize-loader {
background: transparent;
border: 4px solid #666;
border-radius: 50%;
display: block;
margin: 10px auto;
text-indent: -9999px;
height: 12px;
width: 12px;
/* Animation */
-webkit-animation: customize-loader 1s infinite ease-out;
-moz-animation: customize-loader 1s infinite ease-out;
animation: customize-loader 1s infinite ease-out;
}
@-moz-keyframes customize-loader {
0% {
opacity: 0;
-moz-transform: scale(0.7);
}
40% {
opacity: 1;
}
100% {
opacity: 0;
-moz-transform: scale(1);
}
}
@-webkit-keyframes customize-loader {
0% {
opacity: 0;
-webkit-transform: scale(0.7);
}
40% {
opacity: 1;
}
100% {
opacity: 0;
-webkit-transform: scale(1);
}
}
@keyframes customize-loader {
0% {
opacity: 0;
transform: scale(0.7);
}
40% {
opacity: 1;
}
100% {
opacity: 0;
transform: scale(1);
}
}
/*
* Style for custom settings
*/
.customize-section select {
max-width: 150px;
}
.customize-section .hex-prepend {
float: left;
display: block;
margin: 1px -2px 0 0;
line-height: 15px;
padding: 3px 5px;
color: #777;
text-align: center;
background-color: #fff;
border: 1px solid #dfdfdf;
-webkit-border-radius: 3px 0 0 3px;
border-radius: 3px 0 0 3px;
}
.customize-section input[type="text"].hex-input {
-webkit-border-radius: 0 3px 3px 0;
border-radius: 0 3px 3px 0;
width: 150px;
}
.customize-section .hex-color-example {
border: 1px solid #dfdfdf;
-webkit-border-radius: 3px;
border-radius: 3px;
padding: 3px 14px;
background-color: #ef45da
}

View File

View File

@ -0,0 +1,90 @@
body.customize-active {
overflow: hidden;
}
#customize-container {
display: none;
background: #fff;
z-index: 500000;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.customize-active #customize-container {
display: block;
}
#customize-container.collapsed {
left: -302px;
}
#customize-container .collapse-sidebar,
#customize-container .return-to-admin {
z-index: 50;
}
#customize-container div {
position: absolute;
left: 0;
top: 0;
width: 260px;
padding: 15px 20px;
background: #f5f5f5;
box-shadow:
inset -11px 0 8px -8px rgba( 0, 0, 0, 0.1 ),
inset 0 -1px 0 #dfdfdf,
-1px 1px 0 #fff;
border-right: 1px solid rgba( 0, 0, 0, 0.2 );
}
#customize-container iframe {
height: 100%;
width: 100%;
z-index: 20;
}
/* Collapse Button */
#customize-container .collapse-sidebar {
position: absolute;
top: 13px;
left: 265px;
z-index: 50;
display: block;
width: 19px;
height: 19px;
padding: 0;
border-radius: 50%;
}
#customize-container.collapsed .collapse-sidebar {
position: absolute;
left: 315px;
}
#customize-container .collapse-sidebar span {
margin-top: 2px;
margin-left: 2px;
display: block;
width: 15px;
height: 15px;
background: transparent url('../../wp-admin/images/arrows.png') no-repeat 0 -72px;
}
#customize-container.collapsed .collapse-sidebar span {
background-position: 0 -108px;
}
/* Animations */
#customize-container,
#customize-container .collapse-sidebar {
-moz-transition-property: left, right, top, bottom;
-webkit-transition-property: left, right, top, bottom;
-o-transition-property: left, right, top, bottom;
-ms-transition-property: left, right, top, bottom;
transition-property: left, right, top, bottom;
-moz-transition-duration: 0.2s;
-webkit-transition-duration: 0.2s;
-o-transition-duration: 0.2s;
-ms-transition-duration: 0.2s;
transition-duration: 0.2s;
}

View File

@ -0,0 +1,108 @@
<?php
/**
* Customize Controls
*
* @package WordPress
* @subpackage Customize
* @since 3.4.0
*/
if ( ! defined( 'ABSPATH' ) )
die;
global $wp_scripts;
$registered = $wp_scripts->registered;
$wp_scripts = new WP_Scripts;
$wp_scripts->registered = $registered;
add_action( 'customize_controls_print_scripts', 'print_head_scripts', 20 );
add_action( 'customize_controls_print_footer_scripts', '_wp_footer_scripts' );
add_action( 'customize_controls_print_styles', 'print_admin_styles', 20 );
do_action( 'customize_controls_init' );
wp_enqueue_script( 'customize-controls' );
wp_enqueue_style( 'customize-controls' );
do_action( 'customize_controls_enqueue_scripts' );
$theme = get_theme( get_current_theme() );
if ( $theme['Screenshot'] )
$screenshot = $theme['Theme Root URI'] . '/' . $theme['Stylesheet'] . '/' . $theme['Screenshot'];
else
$screenshot = '';
// Let's roll.
@header('Content-Type: ' . get_option('html_type') . '; charset=' . get_option('blog_charset'));
wp_user_settings();
_wp_admin_html_begin();
$admin_title = sprintf( __( '%1$s &#8212; WordPress' ), strip_tags( sprintf( __( 'Customize %s' ), $theme['Name'] ) ) );
?><title><?php echo $admin_title; ?></title><?php
do_action( 'customize_controls_print_styles' );
do_action( 'customize_controls_print_scripts' );
?>
</head>
<body>
<form id="customize-controls" method="post" class="wrap" target="_parent" action="<?php echo esc_url( add_query_arg( 'save', '1', admin_url( 'themes.php' ) ) ); ?>">
<?php wp_nonce_field( 'customize_controls' ); ?>
<input type="hidden" name="customize" value="on" />
<input type="hidden" id="customize-template" name="template" value="<?php echo esc_attr( $theme['Template'] ); ?>" />
<input type="hidden" id="customize-stylesheet" name="stylesheet" value="<?php echo esc_attr( $theme['Stylesheet'] ); ?>" />
<div id="customize-info" class="customize-section">
<p>
<strong class="theme-name"><?php echo $theme['Name']; ?></strong>
<span class="theme-by"><?php printf( __( 'By %s' ), $theme['Author'] ); ?></span>
</p>
<?php if ( $screenshot ) : ?>
<img class="theme-screenshot" src="<?php echo esc_url( $screenshot ); ?>" />
<?php endif; ?>
</div>
<div id="customize-theme-controls"><ul>
<?php
foreach ( $this->sections as $section )
$section->render();
?>
</ul></div>
<div id="customize-footer" class="customize-section">
<?php
submit_button( __( 'Refresh' ), 'secondary', 'refresh', false );
submit_button( __( 'Save' ), 'primary', 'save', false );
?>
</div>
</form>
<div id="customize-preview">
<iframe name="customize-target"></iframe>
</div>
<?php
do_action( 'customize_controls_print_footer_scripts' );
$settings = array(
'preview' => esc_url( home_url( '/' ) ),
'values' => array(),
'prefix' => WP_Customize_Setting::name_prefix,
);
foreach ( $this->settings as $id => $setting ) {
$settings['values'][ $id ] = $setting->value();
}
?>
<script type="text/javascript">
(function() {
if ( typeof wp === 'undefined' || ! wp.customize )
return;
wp.customize.settings = <?php echo json_encode( $settings ); ?>;
})();
</script>
</body>
</html>

View File

@ -0,0 +1,496 @@
if ( typeof wp === 'undefined' )
var wp = {};
(function( exports, $ ){
var api, extend, ctor, inherits, ready,
slice = Array.prototype.slice;
/* =====================================================================
* Micro-inheritance - thank you, backbone.js.
* ===================================================================== */
extend = function( protoProps, classProps ) {
var child = inherits( this, protoProps, classProps );
child.extend = this.extend;
return child;
};
// Shared empty constructor function to aid in prototype-chain creation.
ctor = function() {};
// Helper function to correctly set up the prototype chain, for subclasses.
// Similar to `goog.inherits`, but uses a hash of prototype properties and
// class properties to be extended.
inherits = function( parent, protoProps, staticProps ) {
var child;
// The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
// by us to simply call `super()`.
if ( protoProps && protoProps.hasOwnProperty( 'constructor' ) ) {
child = protoProps.constructor;
} else {
child = function() {
// Storing the result `super()` before returning the value
// prevents a bug in Opera where, if the constructor returns
// a function, Opera will reject the return value in favor of
// the original object. This causes all sorts of trouble.
var result = parent.apply( this, arguments );
return result;
};
}
// Inherit class (static) properties from parent.
$.extend( child, parent );
// Set the prototype chain to inherit from `parent`, without calling
// `parent`'s constructor function.
ctor.prototype = parent.prototype;
child.prototype = new ctor();
// Add prototype properties (instance properties) to the subclass,
// if supplied.
if ( protoProps )
$.extend( child.prototype, protoProps );
// Add static properties to the constructor function, if supplied.
if ( staticProps )
$.extend( child, staticProps );
// Correctly set child's `prototype.constructor`.
child.prototype.constructor = child;
// Set a convenience property in case the parent's prototype is needed later.
child.__super__ = parent.prototype;
return child;
};
/* =====================================================================
* customize function.
* ===================================================================== */
ready = $.Callbacks( 'once memory' );
/*
* Sugar for main customize function. Supports several signatures.
*
* customize( callback, [context] );
* Binds a callback to be fired when the customizer is ready.
* - callback, function
* - context, object
*
* customize( setting );
* Fetches a setting object by ID.
* - setting, string - The setting ID.
*
*/
api = {};
// api = function( callback, context ) {
// if ( $.isFunction( callback ) ) {
// if ( context )
// callback = $.proxy( callback, context );
// ready.add( callback );
//
// return api;
// }
// }
/* =====================================================================
* Base class.
* ===================================================================== */
api.Class = function( applicator, argsArray, options ) {
var magic, args = arguments;
if ( applicator && argsArray && api.Class.applicator === applicator ) {
args = argsArray;
$.extend( this, options || {} );
}
magic = this;
if ( this.instance ) {
magic = function() {
return magic.instance.apply( magic, arguments );
};
$.extend( magic, this );
}
magic.initialize.apply( magic, args );
return magic;
};
api.Class.applicator = {};
api.Class.prototype.initialize = function() {};
/*
* Checks whether a given instance extended a constructor.
*
* The magic surrounding the instance parameter causes the instanceof
* keyword to return inaccurate results; it defaults to the function's
* prototype instead of the constructor chain. Hence this function.
*/
api.Class.prototype.extended = function( constructor ) {
var proto = this;
while ( typeof proto.constructor !== 'undefined' ) {
if ( proto.constructor === constructor )
return true;
if ( typeof proto.constructor.__super__ === 'undefined' )
return false;
proto = proto.constructor.__super__;
}
return false;
};
api.Class.extend = extend;
/* =====================================================================
* Light two-way binding.
* ===================================================================== */
api.Value = api.Class.extend({
initialize: function( initial, options ) {
this._value = initial;
this.callbacks = $.Callbacks();
$.extend( this, options || {} );
},
/*
* Magic. Returns a function that will become the instance.
* Set to null to prevent the instance from extending a function.
*/
instance: function() {
return arguments.length ? this.set.apply( this, arguments ) : this.get();
},
get: function() {
return this._value;
},
set: function( to ) {
var from = this._value;
to = this.validate( to );
// Bail if the sanitized value is null or unchanged.
if ( null === to || this._value === to )
return this;
this._value = to;
this.callbacks.fireWith( this, [ to, from ] );
return this;
},
validate: function( value ) {
return value;
},
bind: function( callback ) {
this.callbacks.add.apply( this.callbacks, arguments );
return this;
},
unbind: function( callback ) {
this.callbacks.remove.apply( this.callbacks, arguments );
return this;
},
/*
* Allows the creation of composite values.
* Overrides the native link method (can be reverted with `unlink`).
*/
link: function() {
var keys = slice.call( arguments ),
callback = keys.pop(),
self = this,
set, key, active;
if ( this.links )
this.unlink();
this.links = [];
// Single argument means a direct binding.
if ( ! keys.length ) {
keys = [ callback ];
callback = function( value, to ) {
return to;
};
}
while ( key = keys.shift() ) {
if ( this._parent && $.type( key ) == 'string' )
this.links.push( this._parent[ key ] );
else
this.links.push( key );
}
// Replace this.set with the assignment function.
set = function() {
var args, result;
// If we call set from within the assignment function,
// pass the arguments to the original set.
if ( active )
return self.set.original.apply( self, arguments );
active = true;
args = self.links.concat( slice.call( arguments ) );
result = callback.apply( self, args );
active = false;
if ( typeof result !== 'undefined' )
self.set.original.call( self, result );
};
set.original = this.set;
this.set = set;
// Bind the new function to the master values.
$.each( this.links, function( key, value ) {
value.bind( self.set );
});
this.set( this.get() );
return this;
},
unlink: function() {
var set = this.set;
$.each( this.links, function( key, value ) {
value.unbind( set );
});
delete this.links;
this.set = this.set.original;
return this;
}
});
api.ensure = function( element ) {
return typeof element == 'string' ? $( element ) : element;
};
sync = {
'val': {
update: function() {
this.element[ this._updater ]( this._value );
},
refresh: function() {
this.set( this.element[ this._refresher ]() );
}
}
}
api.Element = api.Value.extend({
initialize: function( element, options ) {
var synchronizer = api.Element.synchronizer.html,
type;
this.element = api.ensure( element );
this.events = '';
if ( this.element.is('input, select, textarea') ) {
this.events += 'change';
synchronizer = api.Element.synchronizer.val;
if ( this.element.is('input') ) {
type = this.element.prop('type');
if ( api.Element.synchronizer[ type ] )
synchronizer = api.Element.synchronizer[ type ];
if ( 'text' === type || 'password' === type )
this.events += ' keyup';
}
}
api.Value.prototype.initialize.call( this, null, $.extend( options || {}, synchronizer ) );
this._value = this.get();
this.bind( this.update );
this.refresh = $.proxy( this.refresh, this );
this.element.bind( this.events, this.refresh );
},
find: function( selector ) {
return $( selector, this.element );
},
refresh: function() {},
update: function() {}
});
api.Element.synchronizer = {};
$.each( [ 'html', 'val' ], function( i, method ) {
api.Element.synchronizer[ method ] = {
update: function( to ) {
this.element[ method ]( to );
},
refresh: function() {
this.set( this.element[ method ]() );
}
};
});
api.Element.synchronizer.checkbox = {
update: function( to ) {
this.element.prop( 'checked', to );
},
refresh: function() {
this.set( this.element.prop( 'checked' ) );
}
};
api.Element.synchronizer.radio = {
update: function( to ) {
this.element.filter( function() {
return this.value === to;
}).prop( 'checked', true );
},
refresh: function() {
this.set( this.element.filter( ':checked' ).val() );
}
};
api.ValueFactory = function( constructor ) {
constructor = constructor || api.Value;
return function( key ) {
var args = slice.call( arguments, 1 );
this[ key ] = new constructor( api.Class.applicator, args );
this[ key ]._parent = this;
return this[ key ];
};
};
api.Values = api.Value.extend({
defaultConstructor: api.Value,
initialize: function( options ) {
api.Value.prototype.initialize.call( this, {}, options || {} );
},
instance: function( id ) {
return this.value( id );
},
value: function( id ) {
return this._value[ id ];
},
has: function( id ) {
return typeof this._value[ id ] !== 'undefined';
},
add: function( id, value ) {
if ( this.has( id ) )
return;
this._value[ id ] = value;
this._value[ id ]._parent = this._value;
return this._value[ id ];
},
set: function( id ) {
if ( this.has( id ) )
return this.pass( 'set', arguments );
return this.add( id, new this.defaultConstructor( api.Class.applicator, slice.call( arguments, 1 ) ) );
},
remove: function( id ) {
delete this._value[ id ];
},
pass: function( fn, args ) {
var id, value;
args = slice.call( args );
id = args.shift();
if ( ! this.has( id ) )
return;
value = this.value( id );
return value[ fn ].apply( value, args );
}
});
$.each( [ 'get', 'bind', 'unbind', 'link', 'unlink' ], function( i, method ) {
api.Values.prototype[ method ] = function() {
return this.pass( method, arguments );
};
});
/* =====================================================================
* Messenger for postMessage.
* ===================================================================== */
api.Messenger = api.Class.extend({
add: api.ValueFactory(),
initialize: function( url, options ) {
$.extend( this, options || {} );
this.add( 'url', url );
this.add( 'origin' ).link( 'url', function( url ) {
return url().replace( /([^:]+:\/\/[^\/]+).*/, '$1' );
});
this.topics = {};
$.receiveMessage( $.proxy( this.receive, this ), this.origin() || null );
},
receive: function( event ) {
var message;
console.log( 'messenger receiveMessage', arguments );
// @todo: remove, this is done in the postMessage plugin.
// if ( this.origin && event.origin !== this.origin )
// return;
message = JSON.parse( event.data );
if ( message && message.id && message.data && this.topics[ message.id ] )
this.topics[ message.id ].fireWith( this, [ message.data ]);
},
send: function( id, data ) {
var message;
if ( ! this.url() )
return;
console.log( 'sending message', id, data );
message = JSON.stringify({ id: id, data: data });
$.postMessage( message, this.url(), this.targetWindow || null );
},
bind: function( id, callback ) {
var topic = this.topics[ id ] || ( this.topics[ id ] = $.Callbacks() );
topic.add( callback );
},
unbind: function( id, callback ) {
if ( this.topics[ id ] )
this.topics[ id ].remove( callback );
}
});
/* =====================================================================
* Core customize object.
* ===================================================================== */
api = $.extend( new api.Values(), api );
// Expose the API to the world.
exports.customize = api;
})( wp, jQuery );

View File

View File

@ -0,0 +1,101 @@
(function( exports, $ ){
var api = wp.customize;
api.Previewer = api.Messenger.extend({
/**
* Requires params:
* - iframe - a selector or jQuery element
* - form - a selector or jQuery element
* - url - the URL of preview frame
*/
initialize: function( params, options ) {
$.extend( this, options || {} );
this.iframe = api.ensure( params.iframe );
this.form = api.ensure( params.form );
api.Messenger.prototype.initialize.call( this, params.url, {
targetWindow: this.iframe[0].contentWindow
});
this._formOriginalProps = {
target: this.form.prop('target'),
action: this.form.prop('action')
};
this.bind( 'url', function( url ) {
// Bail if we're navigating to the current url, to a different origin, or wp-admin.
if ( this.url() == url || 0 !== url.indexOf( this.origin() + '/' ) || -1 !== url.indexOf( 'wp-admin' ) )
return;
this.url( url );
this.refresh();
});
this.refresh();
},
refresh: function() {
this.submit({
target: this.iframe.prop('name'),
action: this.url()
});
},
submit: function( props ) {
if ( props )
this.form.prop( props );
this.form.submit();
if ( props )
this.form.prop( this._formOriginalProps );
}
});
/* =====================================================================
* Ready.
* ===================================================================== */
$( function() {
var previewer,
controls = $('[name^="' + api.settings.prefix + '"]');
// Initialize Previewer
previewer = new api.Previewer({
iframe: '#customize-preview iframe',
form: '#customize-controls',
url: api.settings.preview
});
$.each( api.settings.values, function( id, value ) {
var elements = controls.filter( '[name="' + api.settings.prefix + id + '"]' ),
setting = api.set( id, value );
setting.control = new wp.customize.Element( elements );
setting.control.link( setting );
setting.link( setting.control );
});
// Temporary accordion code.
$('.control-section h3').click( function() {
$( this ).siblings('ul').slideToggle( 200 );
$( this ).toggleClass( 'open' );
return false;
});
// Button bindings.
$('#save').click( function() {
previewer.submit();
return false;
});
$('#refresh').click( function() {
previewer.refresh();
return false;
});
// Fetch prefixed settings.
$('[name^="' + api.settings.prefix + '"]').each( function() {
// console.log( this.name );
});
});
})( wp, jQuery );

View File

View File

@ -0,0 +1,75 @@
if ( typeof wp === 'undefined' )
var wp = {};
(function( exports, $ ){
var Loader = {
initialize: function() {
this.body = $( document.body );
this.element = $( '#customize-container' );
this.base = $( '.admin-url', this.element ).val();
this.doc_title = $( document ).attr( 'title' );
this.element.on( 'click', '.return-to-admin', function() {
Loader.close();
return false;
});
this.element.on( 'click', '.collapse-sidebar', function() {
Loader.element.toggleClass('collapsed');
return false;
});
},
open: function( params ) {
params.customize = 'on';
this.element.append( '<iframe />' );
this.iframe = $( 'iframe' ).attr( 'src', this.base + '?' + jQuery.param( params ) );
$('iframe').load( function() {
title = $(this).contents().find( 'title' ).text();
$( document ).attr( 'title', title );
});
this.element.fadeIn( 200, function() {
Loader.body.addClass( 'customize-active' );
});
},
close: function() {
this.element.fadeOut( 200, function() {
Loader.iframe.remove();
Loader.iframe = null;
Loader.body.removeClass( 'customize-active' );
$( document ).attr( 'title', Loader.doc_title );
});
}
};
$( function() {
Loader.initialize();
// Override 'preview' links on themes page.
$('.thickbox-preview').click( function( event ) {
var href, template, stylesheet;
// Stop the thickbox.
event.preventDefault();
event.stopImmediatePropagation();
// Extract the template/stylesheet from the preview link's url.
href = $(this).attr('href');
template = href.match('template=([^&]*)')[1];
stylesheet = href.match('stylesheet=([^&]*)')[1];
// Load the theme.
Loader.open({
template: template,
stylesheet: stylesheet
});
}).filter( function() {
return 'Preview' == $(this).text();
}).text('Customize');
});
// Expose the API to the world.
exports.CustomizeLoader = Loader;
})( wp, jQuery );

View File

View File

@ -0,0 +1,59 @@
(function( exports, $ ){
var api = wp.customize;
api.Preview = api.Messenger.extend({
/**
* Requires params:
* - url - the URL of preview frame
*
* @todo: Perhaps add a window.onbeforeunload dialog in case the theme
* somehow attempts to leave the page and we don't catch it
* (which really shouldn't happen).
*/
initialize: function( url, options ) {
var self = this;
$.extend( this, options || {} );
api.Messenger.prototype.initialize.call( this, url );
this.body = $( document.body );
this.body.on( 'click.preview', 'a', function( event ) {
event.preventDefault();
self.send( 'url', $(this).attr('href') );
});
// You cannot submit forms.
// @todo: Namespace customizer settings so that we can mix the
// $_POST data with the customize setting $_POST data.
this.body.on( 'submit.preview', 'form', function( event ) {
event.preventDefault();
});
this.bind( 'url', function( url ) {
this.url( url );
this.refresh();
});
},
refresh: function() {
this.submit({
target: this.iframe.prop('name'),
action: this.url()
});
},
submit: function( props ) {
if ( props )
this.form.prop( props );
this.form.submit();
if ( props )
this.form.prop( this._formOriginalProps );
}
});
$( function() {
var preview;
preview = new api.Preview( api.settings.parent );
});
})( wp, jQuery );

View File

View File

@ -289,6 +289,11 @@ function wp_default_scripts( &$scripts ) {
$scripts->add( 'hoverIntent', "/wp-includes/js/hoverIntent$suffix.js", array('jquery'), 'r6', 1 ); $scripts->add( 'hoverIntent', "/wp-includes/js/hoverIntent$suffix.js", array('jquery'), 'r6', 1 );
$scripts->add( 'customize-loader', "/wp-includes/js/customize-loader$suffix.js", array( 'jquery' ), false, 1 );
$scripts->add( 'customize-base', "/wp-includes/js/customize-base$suffix.js", array( 'jquery-postmessage', 'json2' ), false, 1 );
$scripts->add( 'customize-controls', "/wp-includes/js/customize-controls$suffix.js", array( 'customize-base' ), false, 1 );
$scripts->add( 'customize-preview', "/wp-includes/js/customize-preview$suffix.js", array( 'customize-base' ), false, 1 );
if ( is_admin() ) { if ( is_admin() ) {
$scripts->add( 'ajaxcat', "/wp-admin/js/cat$suffix.js", array( 'wp-lists' ) ); $scripts->add( 'ajaxcat', "/wp-admin/js/cat$suffix.js", array( 'wp-lists' ) );
$scripts->add_data( 'ajaxcat', 'group', 1 ); $scripts->add_data( 'ajaxcat', 'group', 1 );
@ -464,6 +469,8 @@ function wp_default_styles( &$styles ) {
$styles->add( 'wp-jquery-ui-dialog', "/wp-includes/css/jquery-ui-dialog$suffix.css" ); $styles->add( 'wp-jquery-ui-dialog', "/wp-includes/css/jquery-ui-dialog$suffix.css" );
$styles->add( 'editor-buttons', "/wp-includes/css/editor-buttons$suffix.css" ); $styles->add( 'editor-buttons', "/wp-includes/css/editor-buttons$suffix.css" );
$styles->add( 'wp-pointer', "/wp-includes/css/wp-pointer$suffix.css" ); $styles->add( 'wp-pointer', "/wp-includes/css/wp-pointer$suffix.css" );
$styles->add( 'customize-loader', "/wp-includes/css/customize-loader$suffix.css" );
$styles->add( 'customize-controls', "/wp-includes/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'ie' ) );
foreach ( $rtl_styles as $rtl_style ) { foreach ( $rtl_styles as $rtl_style ) {
$styles->add_data( $rtl_style, 'rtl', true ); $styles->add_data( $rtl_style, 'rtl', true );

View File

@ -2082,3 +2082,15 @@ function check_theme_switched() {
update_option( 'theme_switched', false ); update_option( 'theme_switched', false );
} }
} }
function wp_customize_load() {
// Load on themes.php or ?customize=on
if ( ! ( isset( $_REQUEST['customize'] ) && 'on' == $_REQUEST['customize'] ) && 'themes.php' != $GLOBALS['pagenow'] )
return;
require( ABSPATH . WPINC . '/class-wp-customize.php' );
// Init Customize class
// @todo Dependency injection instead
$GLOBALS['customize'] = new WP_Customize;
}
add_action( 'plugins_loaded', 'wp_customize_load' );