: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
updateRadios = _.debounce( function() {
var x, y, radioInput, inputValue;
x = control.settings.x.get();
y = control.settings.y.get();
inputValue = String( x ) + ' ' + String( y );
radioInput = control.container.find( 'input[name="background-position"][value="' + inputValue + '"]' );
radioInput.trigger( 'click' );
control.settings.x.bind( updateRadios );
control.settings.y.bind( updateRadios );
updateRadios(); // Set initial UI.
* A control for selecting and cropping an image.
* @class wp.customize.CroppedImageControl
* @augments wp.customize.MediaControl
api.CroppedImageControl = api.MediaControl.extend(/** @lends wp.customize.CroppedImageControl.prototype */{
* Open the media modal to the library state.
openFrame: function( event ) {
if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
this.frame.setState( 'library' ).open();
* Create a media modal select frame, and store it so the instance can be reused when needed.
var l10n = _wpMediaViewsL10n;
new wp.media.controller.Library({
title: this.params.button_labels.frame_title,
library: wp.media.query({ type: 'image' }),
suggestedWidth: this.params.width,
suggestedHeight: this.params.height
new wp.media.controller.CustomizeImageCropper({
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 if the image isn't the right size.
var attachment = this.frame.state().get( 'selection' ).first().toJSON();
if ( this.params.width === attachment.width && this.params.height === attachment.height && ! this.params.flex_width && ! this.params.flex_height ) {
this.setImageFromAttachment( attachment );
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 ) {
this.setImageFromAttachment( croppedImage );
* Returns a set of options, computed from the attached image data and
* control-specific data, to be fed to the imgAreaSelect plugin in
* @param {wp.media.model.Attachment} attachment
* @param {wp.media.controller.Cropper} controller
* @return {Object} Options
calculateImageSelectOptions: function( attachment, controller ) {
var control = controller.get( 'control' ),
flexWidth = !! parseInt( control.params.flex_width, 10 ),
flexHeight = !! parseInt( control.params.flex_height, 10 ),
realWidth = attachment.get( 'width' ),
realHeight = attachment.get( 'height' ),
xInit = parseInt( control.params.width, 10 ),
yInit = parseInt( control.params.height, 10 ),
x1, y1, imgSelectOptions;
controller.set( 'canSkipCrop', ! control.mustBeCropped( flexWidth, flexHeight, xInit, yInit, realWidth, realHeight ) );
if ( realWidth / realHeight > ratio ) {
x1 = ( realWidth - xInit ) / 2;
y1 = ( realHeight - yInit ) / 2;
minWidth: xImg > xInit ? xInit : xImg,
minHeight: yImg > yInit ? yInit : yImg,
if ( flexHeight === false && flexWidth === false ) {
imgSelectOptions.aspectRatio = xInit + ':' + yInit;
if ( true === flexHeight ) {
delete imgSelectOptions.minHeight;
imgSelectOptions.maxWidth = realWidth;
if ( true === flexWidth ) {
delete imgSelectOptions.minWidth;
imgSelectOptions.maxHeight = realHeight;
* Return whether the image must be cropped, based on required dimensions.
mustBeCropped: function( flexW, flexH, dstW, dstH, imgW, imgH ) {
if ( true === flexW && true === flexH ) {
if ( true === flexW && dstH === imgH ) {
if ( true === flexH && dstW === imgW ) {
if ( dstW === imgW && dstH === imgH ) {
* If cropping was skipped, apply the image data directly to the setting.
onSkippedCrop: function() {
var attachment = this.frame.state().get( 'selection' ).first().toJSON();
this.setImageFromAttachment( attachment );
* Updates the setting and re-renders the control UI.
* @param {Object} attachment
setImageFromAttachment: function( attachment ) {
this.params.attachment = attachment;
// Set the Customizer setting; the callback takes care of rendering.
this.setting( attachment.id );
* A control for selecting and cropping Site Icons.
* @class wp.customize.SiteIconControl
* @augments wp.customize.CroppedImageControl
api.SiteIconControl = api.CroppedImageControl.extend(/** @lends wp.customize.SiteIconControl.prototype */{
* Create a media modal select frame, and store it so the instance can be reused when needed.
var l10n = _wpMediaViewsL10n;
new wp.media.controller.Library({
title: this.params.button_labels.frame_title,
library: wp.media.query({ type: 'image' }),
suggestedWidth: this.params.width,
suggestedHeight: this.params.height
new wp.media.controller.SiteIconCropper({
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 if the image isn't the right size.
var attachment = this.frame.state().get( 'selection' ).first().toJSON(),
if ( this.params.width === attachment.width && this.params.height === attachment.height && ! this.params.flex_width && ! this.params.flex_height ) {
wp.ajax.post( 'crop-image', {
nonce: attachment.nonces.edit,
width: this.params.width,
height: this.params.height,
dst_width: this.params.width,
dst_height: this.params.height
} ).done( function( croppedImage ) {
controller.setImageFromAttachment( croppedImage );
controller.frame.close();
controller.frame.trigger('content:error:crop');
this.frame.setState( 'cropper' );
* Updates the setting and re-renders the control UI.
* @param {Object} attachment
setImageFromAttachment: function( attachment ) {
var sizes = [ 'site_icon-32', 'thumbnail', 'full' ], link,
_.each( sizes, function( size ) {
if ( ! icon && ! _.isUndefined ( attachment.sizes[ size ] ) ) {
icon = attachment.sizes[ size ];
this.params.attachment = attachment;
// Set the Customizer setting; the callback takes care of rendering.
this.setting( attachment.id );
// Update the icon in-browser.
link = $( 'link[rel="icon"][sizes="32x32"]' );
link.attr( 'href', icon.url );
* Called when the "Remove" link is clicked. Empties the setting.
* @param {Object} event jQuery Event object
removeFile: function( event ) {
if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
this.params.attachment = {};
this.renderContent(); // Not bound to setting change when emptying.
$( 'link[rel="icon"][sizes="32x32"]' ).attr( 'href', '/favicon.ico' ); // Set to default.
* @class wp.customize.HeaderControl
* @augments wp.customize.Control
api.HeaderControl = api.Control.extend(/** @lends wp.customize.HeaderControl.prototype */{
this.btnRemove = $('#customize-control-header_image .actions .remove');
this.btnNew = $('#customize-control-header_image .actions .new');
_.bindAll(this, 'openMedia', 'removeImage');
this.btnNew.on( 'click', this.openMedia );
this.btnRemove.on( 'click', this.removeImage );
api.HeaderTool.currentHeader = this.getInitialHeaderImage();
new api.HeaderTool.CurrentView({
model: api.HeaderTool.currentHeader,
el: '#customize-control-header_image .current .container'
new api.HeaderTool.ChoiceListView({
collection: api.HeaderTool.UploadsList = new api.HeaderTool.ChoiceList(),
el: '#customize-control-header_image .choices .uploaded .list'
new api.HeaderTool.ChoiceListView({
collection: api.HeaderTool.DefaultsList = new api.HeaderTool.DefaultsList(),
el: '#customize-control-header_image .choices .default .list'
api.HeaderTool.combinedList = api.HeaderTool.CombinedList = new api.HeaderTool.CombinedList([
api.HeaderTool.UploadsList,
api.HeaderTool.DefaultsList
// Ensure custom-header-crop Ajax requests bootstrap the Customizer to activate the previewed theme.
wp.media.controller.Cropper.prototype.defaults.doCropArgs.wp_customize = 'on';
wp.media.controller.Cropper.prototype.defaults.doCropArgs.customize_theme = api.settings.theme.stylesheet;
* Returns a new instance of api.HeaderTool.ImageModel based on the currently
* saved header image (if any).
* @return {Object} Options
getInitialHeaderImage: function() {
if ( ! api.get().header_image || ! api.get().header_image_data || _.contains( [ 'remove-header', 'random-default-image', 'random-uploaded-image' ], api.get().header_image ) ) {
return new api.HeaderTool.ImageModel();
// Get the matching uploaded image object.
var currentHeaderObject = _.find( _wpCustomizeHeader.uploads, function( imageObj ) {
return ( imageObj.attachment_id === api.get().header_image_data.attachment_id );
// Fall back to raw current header image.
if ( ! currentHeaderObject ) {
url: api.get().header_image,
thumbnail_url: api.get().header_image,
attachment_id: api.get().header_image_data.attachment_id
return new api.HeaderTool.ImageModel({
header: currentHeaderObject,
choice: currentHeaderObject.url.split( '/' ).pop()
* Returns a set of options, computed from the attached image data and
* theme-specific data, to be fed to the imgAreaSelect plugin in
* @param {wp.media.model.Attachment} attachment
* @param {wp.media.controller.Cropper} controller
* @return {Object} Options
calculateImageSelectOptions: function(attachment, controller) {
var xInit = parseInt(_wpCustomizeHeader.data.width, 10),
yInit = parseInt(_wpCustomizeHeader.data.height, 10),
flexWidth = !! parseInt(_wpCustomizeHeader.data['flex-width'], 10),
flexHeight = !! parseInt(_wpCustomizeHeader.data['flex-height'], 10),
ratio, xImg, yImg, realHeight, realWidth,
realWidth = attachment.get('width');
realHeight = attachment.get('height');
this.headerImage = new api.HeaderTool.ImageModel();
themeFlexWidth: flexWidth,
themeFlexHeight: flexHeight,
controller.set( 'canSkipCrop', ! this.headerImage.shouldBeCropped() );
if ( xImg / yImg > ratio ) {
if (flexHeight === false && flexWidth === false) {
imgSelectOptions.aspectRatio = xInit + ':' + yInit;
if (flexHeight === false ) {
imgSelectOptions.maxHeight = yInit;
if (flexWidth === false ) {
imgSelectOptions.maxWidth = xInit;
* Sets up and opens the Media Manager in order to select an image.
* Depending on both the size of the image and the properties of the
* current theme, a cropping step after selection may be required or
openMedia: function(event) {
var l10n = _wpMediaViewsL10n;
text: l10n.selectAndCrop,
new wp.media.controller.Library({