: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @param {Array} errorAnnotations - Error annotations.
onUpdateErrorNotice: function onUpdateErrorNotice( errorAnnotations ) {
var control = this, message;
control.setting.notifications.remove( 'csslint_error' );
if ( 0 !== errorAnnotations.length ) {
if ( 1 === errorAnnotations.length ) {
message = api.l10n.customCssError.singular.replace( '%d', '1' );
message = api.l10n.customCssError.plural.replace( '%d', String( errorAnnotations.length ) );
control.setting.notifications.add( new api.Notification( 'csslint_error', {
* Initialize plain-textarea editor when syntax highlighting is disabled.
initPlainTextareaEditor: function() {
var control = this, $textarea = control.container.find( 'textarea' ), textarea = $textarea[0];
$textarea.on( 'blur', function onBlur() {
$textarea.data( 'next-tab-blurs', false );
$textarea.on( 'keydown', function onKeydown( event ) {
var selectionStart, selectionEnd, value, tabKeyCode = 9, escKeyCode = 27;
if ( escKeyCode === event.keyCode ) {
if ( ! $textarea.data( 'next-tab-blurs' ) ) {
$textarea.data( 'next-tab-blurs', true );
event.stopPropagation(); // Prevent collapsing the section.
// Short-circuit if tab key is not being pressed or if a modifier key *is* being pressed.
if ( tabKeyCode !== event.keyCode || event.ctrlKey || event.altKey || event.shiftKey ) {
// Prevent capturing Tab characters if Esc was pressed.
if ( $textarea.data( 'next-tab-blurs' ) ) {
selectionStart = textarea.selectionStart;
selectionEnd = textarea.selectionEnd;
if ( selectionStart >= 0 ) {
textarea.value = value.substring( 0, selectionStart ).concat( '\t', value.substring( selectionEnd ) );
$textarea.selectionStart = textarea.selectionEnd = selectionStart + 1;
control.deferred.codemirror.rejectWith( control );
* Class wp.customize.DateTimeControl.
* @class wp.customize.DateTimeControl
* @augments wp.customize.Control
api.DateTimeControl = api.Control.extend(/** @lends wp.customize.DateTimeControl.prototype */{
ready: function ready() {
control.inputElements = {};
control.invalidDate = false;
_.bindAll( control, 'populateSetting', 'updateDaysForMonth', 'populateDateInputs' );
if ( ! control.setting ) {
throw new Error( 'Missing setting' );
control.container.find( '.date-input' ).each( function() {
var input = $( this ), component, element;
component = input.data( 'component' );
element = new api.Element( input );
control.inputElements[ component ] = element;
control.elements.push( element );
// Add invalid date error once user changes (and has blurred the input).
input.on( 'change', function() {
if ( control.invalidDate ) {
control.notifications.add( new api.Notification( 'invalid_date', {
message: api.l10n.invalidDate
// Remove the error immediately after validity change.
input.on( 'input', _.debounce( function() {
if ( ! control.invalidDate ) {
control.notifications.remove( 'invalid_date' );
// Add zero-padding when blurring field.
input.on( 'blur', _.debounce( function() {
if ( ! control.invalidDate ) {
control.populateDateInputs();
control.inputElements.month.bind( control.updateDaysForMonth );
control.inputElements.year.bind( control.updateDaysForMonth );
control.populateDateInputs();
control.setting.bind( control.populateDateInputs );
// Start populating setting after inputs have been populated.
_.each( control.inputElements, function( element ) {
element.bind( control.populateSetting );
* @param {string} datetime - Date/Time string. Accepts Y-m-d[ H:i[:s]] format.
* @return {Object|null} Returns object containing date components or null if parse error.
parseDateTime: function parseDateTime( datetime ) {
var control = this, matches, date, midDayHour = 12;
matches = datetime.match( /^(\d\d\d\d)-(\d\d)-(\d\d)(?: (\d\d):(\d\d)(?::(\d\d))?)?$/ );
hour: matches.shift() || '00',
minute: matches.shift() || '00',
second: matches.shift() || '00'
if ( control.params.includeTime && control.params.twelveHourFormat ) {
date.hour = parseInt( date.hour, 10 );
date.meridian = date.hour >= midDayHour ? 'pm' : 'am';
date.hour = date.hour % midDayHour ? String( date.hour % midDayHour ) : String( midDayHour );
delete date.second; // @todo Why only if twelveHourFormat?
* Validates if input components have valid date and time.
* @return {boolean} If date input fields has error.
validateInputs: function validateInputs() {
var control = this, components, validityInput;
control.invalidDate = false;
components = [ 'year', 'day' ];
if ( control.params.includeTime ) {
components.push( 'hour', 'minute' );
_.find( components, function( component ) {
var element, max, min, value;
element = control.inputElements[ component ];
validityInput = element.element.get( 0 );
max = parseInt( element.element.attr( 'max' ), 10 );
min = parseInt( element.element.attr( 'min' ), 10 );
value = parseInt( element(), 10 );
control.invalidDate = isNaN( value ) || value > max || value < min;
if ( ! control.invalidDate ) {
validityInput.setCustomValidity( '' );
return control.invalidDate;
if ( control.inputElements.meridian && ! control.invalidDate ) {
validityInput = control.inputElements.meridian.element.get( 0 );
if ( 'am' !== control.inputElements.meridian.get() && 'pm' !== control.inputElements.meridian.get() ) {
control.invalidDate = true;
validityInput.setCustomValidity( '' );
if ( control.invalidDate ) {
validityInput.setCustomValidity( api.l10n.invalidValue );
validityInput.setCustomValidity( '' );
if ( ! control.section() || api.section.has( control.section() ) && api.section( control.section() ).expanded() ) {
_.result( validityInput, 'reportValidity' );
return control.invalidDate;
* Updates number of days according to the month and year selected.
updateDaysForMonth: function updateDaysForMonth() {
var control = this, daysInMonth, year, month, day;
month = parseInt( control.inputElements.month(), 10 );
year = parseInt( control.inputElements.year(), 10 );
day = parseInt( control.inputElements.day(), 10 );
daysInMonth = new Date( year, month, 0 ).getDate();
control.inputElements.day.element.attr( 'max', daysInMonth );
if ( day > daysInMonth ) {
control.inputElements.day( String( daysInMonth ) );
* Populate setting value from the inputs.
* @return {boolean} If setting updated.
populateSetting: function populateSetting() {
var control = this, date;
if ( control.validateInputs() || ! control.params.allowPastDate && ! control.isFutureDate() ) {
date = control.convertInputDateToString();
control.setting.set( date );
* Converts input values to string in Y-m-d H:i:s format.
* @return {string} Date string.
convertInputDateToString: function convertInputDateToString() {
var control = this, date = '', dateFormat, hourInTwentyFourHourFormat,
pad = function( number, padding ) {
if ( String( number ).length < padding ) {
zeros = padding - String( number ).length;
number = Math.pow( 10, zeros ).toString().substr( 1 ) + String( number );
getElementValue = function( component ) {
var value = parseInt( control.inputElements[ component ].get(), 10 );
if ( _.contains( [ 'month', 'day', 'hour', 'minute' ], component ) ) {
} else if ( 'year' === component ) {
dateFormat = [ 'year', '-', 'month', '-', 'day' ];
if ( control.params.includeTime ) {
hourInTwentyFourHourFormat = control.inputElements.meridian ? control.convertHourToTwentyFourHourFormat( control.inputElements.hour(), control.inputElements.meridian() ) : control.inputElements.hour();
dateFormat = dateFormat.concat( [ ' ', pad( hourInTwentyFourHourFormat, 2 ), ':', 'minute', ':', '00' ] );
_.each( dateFormat, function( component ) {
date += control.inputElements[ component ] ? getElementValue( component ) : component;
* Check if the date is in the future.
* @return {boolean} True if future date.
isFutureDate: function isFutureDate() {
return 0 < api.utils.getRemainingTime( control.convertInputDateToString() );
* Convert hour in twelve hour format to twenty four hour format.
* @param {string} hourInTwelveHourFormat - Hour in twelve hour format.
* @param {string} meridian - Either 'am' or 'pm'.
* @return {string} Hour in twenty four hour format.
convertHourToTwentyFourHourFormat: function convertHour( hourInTwelveHourFormat, meridian ) {
var hourInTwentyFourHourFormat, hour, midDayHour = 12;
hour = parseInt( hourInTwelveHourFormat, 10 );
if ( 'pm' === meridian && hour < midDayHour ) {
hourInTwentyFourHourFormat = hour + midDayHour;
} else if ( 'am' === meridian && midDayHour === hour ) {
hourInTwentyFourHourFormat = hour - midDayHour;
hourInTwentyFourHourFormat = hour;
return String( hourInTwentyFourHourFormat );
* Populates date inputs in date fields.
* @return {boolean} Whether the inputs were populated.
populateDateInputs: function populateDateInputs() {
var control = this, parsed;
parsed = control.parseDateTime( control.setting.get() );
_.each( control.inputElements, function( element, component ) {
var value = parsed[ component ]; // This will be zero-padded string.
// Set month and meridian regardless of focused state since they are dropdowns.
if ( 'month' === component || 'meridian' === component ) {
// Options in dropdowns are not zero-padded.
value = value.replace( /^0/, '' );
value = parseInt( value, 10 );
if ( ! element.element.is( document.activeElement ) ) {
// Populate element with zero-padded value if not focused.
element.set( parsed[ component ] );
} else if ( value !== parseInt( element(), 10 ) ) {
// Forcibly update the value if its underlying value changed, regardless of zero-padding.
element.set( String( value ) );
* Toggle future date notification for date control.
* @param {boolean} notify Add or remove the notification.
* @return {wp.customize.DateTimeControl}
toggleFutureDateNotification: function toggleFutureDateNotification( notify ) {
var control = this, notificationCode, notification;
notificationCode = 'not_future_date';
notification = new api.Notification( notificationCode, {
message: api.l10n.futureDateError
control.notifications.add( notification );
control.notifications.remove( notificationCode );
* Class PreviewLinkControl.
* @class wp.customize.PreviewLinkControl
* @augments wp.customize.Control
api.PreviewLinkControl = api.Control.extend(/** @lends wp.customize.PreviewLinkControl.prototype */{
defaults: _.extend( {}, api.Control.prototype.defaults, {
templateId: 'customize-preview-link-control'
ready: function ready() {
var control = this, element, component, node, url, input, button;
_.bindAll( control, 'updatePreviewLink' );
if ( ! control.setting ) {
control.setting = new api.Value();
control.previewElements = {};
control.container.find( '.preview-control-element' ).each( function() {
component = node.data( 'component' );
element = new api.Element( node );
control.previewElements[ component ] = element;
control.elements.push( element );
url = control.previewElements.url;
input = control.previewElements.input;
button = control.previewElements.button;
input.link( control.setting );
url.link( control.setting );
url.bind( function( value ) {
url.element.parent().attr( {
target: api.settings.changeset.uuid
api.bind( 'ready', control.updatePreviewLink );
api.state( 'saved' ).bind( control.updatePreviewLink );
api.state( 'changesetStatus' ).bind( control.updatePreviewLink );
api.state( 'activated' ).bind( control.updatePreviewLink );
api.previewer.previewUrl.bind( control.updatePreviewLink );
button.element.on( 'click', function( event ) {
if ( control.setting() ) {
document.execCommand( 'copy' );
button( button.element.data( 'copied-text' ) );
url.element.parent().on( 'click', function( event ) {
if ( $( this ).hasClass( 'disabled' ) ) {