: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
library: wp.media.query({ type: 'image' }),
suggestedWidth: _wpCustomizeHeader.data.width,
suggestedHeight: _wpCustomizeHeader.data.height
new wp.media.controller.Cropper({
imgSelectOptions: this.calculateImageSelectOptions
this.frame.on('select', this.onSelect, this);
this.frame.on('cropped', this.onCropped, this);
this.frame.on('skippedcrop', this.onSkippedCrop, this);
* After an image is selected in the media modal,
* switch to the cropper state.
this.frame.setState('cropper');
* After the image has been cropped, apply the cropped image data to the setting.
* @param {Object} croppedImage Cropped attachment data.
onCropped: function(croppedImage) {
var url = croppedImage.url,
attachmentId = croppedImage.attachment_id,
this.setImageFromURL(url, attachmentId, w, h);
* If cropping was skipped, apply the image data directly to the setting.
* @param {Object} selection
onSkippedCrop: function(selection) {
var url = selection.get('url'),
w = selection.get('width'),
h = selection.get('height');
this.setImageFromURL(url, selection.id, w, h);
* Creates a new wp.customize.HeaderTool.ImageModel from provided
* header image data and inserts it into the user-uploaded headers
* @param {number} attachmentId
setImageFromURL: function(url, attachmentId, width, height) {
data.thumbnail_url = url;
data.timestamp = _.now();
data.attachment_id = attachmentId;
choice = new api.HeaderTool.ImageModel({
choice: url.split('/').pop()
api.HeaderTool.UploadsList.add(choice);
api.HeaderTool.currentHeader.set(choice.toJSON());
* Triggers the necessary events to deselect an image which was set as
* the currently selected one.
removeImage: function() {
api.HeaderTool.currentHeader.trigger('hide');
api.HeaderTool.CombinedList.trigger('control:removeImage');
* wp.customize.ThemeControl
* @class wp.customize.ThemeControl
* @augments wp.customize.Control
api.ThemeControl = api.Control.extend(/** @lends wp.customize.ThemeControl.prototype */{
screenshotRendered: false,
var control = this, panel = api.panel( 'themes' );
function disableSwitchButtons() {
return ! panel.canSwitchTheme( control.params.theme.id );
// Temporary special function since supplying SFTP credentials does not work yet. See #42184.
function disableInstallButtons() {
return disableSwitchButtons() || false === api.settings.theme._canInstall || true === api.settings.theme._filesystemCredentialsNeeded;
function updateButtons() {
control.container.find( 'button.preview, button.preview-theme' ).toggleClass( 'disabled', disableSwitchButtons() );
control.container.find( 'button.theme-install' ).toggleClass( 'disabled', disableInstallButtons() );
api.state( 'selectedChangesetStatus' ).bind( updateButtons );
api.state( 'changesetStatus' ).bind( updateButtons );
control.container.on( 'touchmove', '.theme', function() {
control.touchDrag = true;
// Bind details view trigger.
control.container.on( 'click keydown touchend', '.theme', function( event ) {
if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
// Bail if the user scrolled on a touch device.
if ( control.touchDrag === true ) {
return control.touchDrag = false;
// Prevent the modal from showing when the user clicks the action button.
if ( $( event.target ).is( '.theme-actions .button, .update-theme' ) ) {
event.preventDefault(); // Keep this AFTER the key filter above.
section = api.section( control.section() );
section.showDetails( control.params.theme, function() {
// Temporary special function since supplying SFTP credentials does not work yet. See #42184.
if ( api.settings.theme._filesystemCredentialsNeeded ) {
section.overlay.find( '.theme-actions .delete-theme' ).remove();
control.container.on( 'render-screenshot', function() {
var $screenshot = $( this ).find( 'img' ),
source = $screenshot.data( 'src' );
$screenshot.attr( 'src', source );
control.screenshotRendered = true;
* Show or hide the theme based on the presence of the term in the title, description, tags, and author.
* @param {Array} terms - An array of terms to search for.
* @return {boolean} Whether a theme control was activated or not.
filter: function( terms ) {
haystack = control.params.theme.name + ' ' +
control.params.theme.description + ' ' +
control.params.theme.tags + ' ' +
control.params.theme.author + ' ';
haystack = haystack.toLowerCase().replace( '-', ' ' );
// Back-compat for behavior in WordPress 4.2.0 to 4.8.X.
if ( ! _.isArray( terms ) ) {
// Always give exact name matches highest ranking.
if ( control.params.theme.name.toLowerCase() === terms.join( ' ' ) ) {
// Search for and weight (by 10) complete term matches.
matchCount = matchCount + 10 * ( haystack.split( terms.join( ' ' ) ).length - 1 );
// Search for each term individually (as whole-word and partial match) and sum weighted match counts.
_.each( terms, function( term ) {
matchCount = matchCount + 2 * ( haystack.split( term + ' ' ).length - 1 ); // Whole-word, double-weighted.
matchCount = matchCount + haystack.split( term ).length - 1; // Partial word, to minimize empty intermediate searches while typing.
// Upper limit on match ranking.
if ( 0 !== matchCount ) {
control.params.priority = 101 - matchCount; // Sort results by match count.
control.deactivate(); // Hide control.
control.params.priority = 101;
* Rerender the theme from its JS template with the installed type.
rerenderAsInstalled: function( installed ) {
var control = this, section;
control.params.theme.type = 'installed';
section = api.section( control.params.section );
control.params.theme.type = section.params.action;
control.renderContent(); // Replaces existing content.
control.container.trigger( 'render-screenshot' );
* Class wp.customize.CodeEditorControl
* @class wp.customize.CodeEditorControl
* @augments wp.customize.Control
api.CodeEditorControl = api.Control.extend(/** @lends wp.customize.CodeEditorControl.prototype */{
* @param {string} id - Unique identifier for the control instance.
* @param {Object} options - Options hash for the control instance.
initialize: function( id, options ) {
control.deferred = _.extend( control.deferred || {}, {
api.Control.prototype.initialize.call( control, id, options );
// Note that rendering is debounced so the props will be used when rendering happens after add event.
control.notifications.bind( 'add', function( notification ) {
// Skip if control notification is not from setting csslint_error notification.
if ( notification.code !== control.setting.id + ':csslint_error' ) {
// Customize the template and behavior of csslint_error notifications.
notification.templateId = 'customize-code-editor-lint-error-notification';
notification.render = (function( render ) {
var li = render.call( this );
li.find( 'input[type=checkbox]' ).on( 'click', function() {
control.setting.notifications.remove( 'csslint_error' );
})( notification.render );
* Initialize the editor when the containing section is ready and expanded.
if ( ! control.section() ) {
// Wait to initialize editor until section is embedded and expanded.
api.section( control.section(), function( section ) {
section.deferred.embedded.done( function() {
if ( section.expanded() ) {
onceExpanded = function( isExpanded ) {
section.expanded.unbind( onceExpanded );
section.expanded.bind( onceExpanded );
var control = this, element, editorSettings = false;
// Obtain editorSettings for instantiation.
if ( wp.codeEditor && ( _.isUndefined( control.params.editor_settings ) || false !== control.params.editor_settings ) ) {
// Obtain default editor settings.
editorSettings = wp.codeEditor.defaultSettings ? _.clone( wp.codeEditor.defaultSettings ) : {};
editorSettings.codemirror = _.extend(
editorSettings.codemirror,
// Merge editor_settings param on top of defaults.
if ( _.isObject( control.params.editor_settings ) ) {
_.each( control.params.editor_settings, function( value, key ) {
if ( _.isObject( value ) ) {
editorSettings[ key ] = _.extend(
element = new api.Element( control.container.find( 'textarea' ) );
control.elements.push( element );
element.sync( control.setting );
element.set( control.setting() );
control.initSyntaxHighlightingEditor( editorSettings );
control.initPlainTextareaEditor();
* Make sure editor gets focused when control is focused.
* @param {Object} [params] - Focus params.
* @param {Function} [params.completeCallback] - Function to call when expansion is complete.
focus: function( params ) {
var control = this, extendedParams = _.extend( {}, params ), originalCompleteCallback;
originalCompleteCallback = extendedParams.completeCallback;
extendedParams.completeCallback = function() {
if ( originalCompleteCallback ) {
originalCompleteCallback();
control.editor.codemirror.focus();
api.Control.prototype.focus.call( control, extendedParams );
* Initialize syntax-highlighting editor.
* @param {Object} codeEditorSettings - Code editor settings.
initSyntaxHighlightingEditor: function( codeEditorSettings ) {
var control = this, $textarea = control.container.find( 'textarea' ), settings, suspendEditorUpdate = false;
settings = _.extend( {}, codeEditorSettings, {
onTabNext: _.bind( control.onTabNext, control ),
onTabPrevious: _.bind( control.onTabPrevious, control ),
onUpdateErrorNotice: _.bind( control.onUpdateErrorNotice, control )
control.editor = wp.codeEditor.initialize( $textarea, settings );
// Improve the editor accessibility.
$( control.editor.codemirror.display.lineDiv )
'aria-multiline': 'true',
'aria-label': control.params.label,
'aria-describedby': 'editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4'
// Focus the editor when clicking on its label.
control.container.find( 'label' ).on( 'click', function() {
control.editor.codemirror.focus();
* When the CodeMirror instance changes, mirror to the textarea,
* where we have our "true" change event handler bound.
control.editor.codemirror.on( 'change', function( codemirror ) {
suspendEditorUpdate = true;
$textarea.val( codemirror.getValue() ).trigger( 'change' );
suspendEditorUpdate = false;
// Update CodeMirror when the setting is changed by another plugin.
control.setting.bind( function( value ) {
if ( ! suspendEditorUpdate ) {
control.editor.codemirror.setValue( value );
// Prevent collapsing section when hitting Esc to tab out of editor.
control.editor.codemirror.on( 'keydown', function onKeydown( codemirror, event ) {
if ( escKeyCode === event.keyCode ) {
control.deferred.codemirror.resolveWith( control, [ control.editor.codemirror ] );
* Handle tabbing to the field after the editor.
onTabNext: function onTabNext() {
var control = this, controls, controlIndex, section;
section = api.section( control.section() );
controls = section.controls();
controlIndex = controls.indexOf( control );
if ( controls.length === controlIndex + 1 ) {
$( '#customize-footer-actions .collapse-sidebar' ).trigger( 'focus' );
controls[ controlIndex + 1 ].container.find( ':focusable:first' ).focus();
* Handle tabbing to the field before the editor.
onTabPrevious: function onTabPrevious() {
var control = this, controls, controlIndex, section;
section = api.section( control.section() );
controls = section.controls();
controlIndex = controls.indexOf( control );
if ( 0 === controlIndex ) {
section.contentContainer.find( '.customize-section-title .customize-help-toggle, .customize-section-title .customize-section-description.open .section-description-close' ).last().focus();
controls[ controlIndex - 1 ].contentContainer.find( ':focusable:first' ).focus();