WordPress.org

Make WordPress Core

Ticket #41914: 41914.0.diff

File 41914.0.diff, 30.6 KB (added by westonruter, 2 months ago)

https://github.com/xwp/wordpress-develop/pull/265

  • src/wp-admin/css/widgets.css

    diff --git src/wp-admin/css/widgets.css src/wp-admin/css/widgets.css
    index f31b63c881..99ebbdec10 100644
     
    162162        margin: 1em 0; 
    163163} 
    164164 
     165.media-widget-gallery-preview { 
     166        display: flex; 
     167        justify-content: flex-start; 
     168        flex-wrap: wrap; 
     169} 
     170 
     171.media-widget-preview.gallery .placeholder, 
     172.media-widget-gallery-preview { 
     173        cursor: pointer; 
     174} 
     175 
     176.media-widget-gallery-preview .gallery-item { 
     177        box-sizing: border-box; 
     178        width: 50%; 
     179        margin: 0; 
     180        padding: 1.79104477%; 
     181} 
     182 
     183/* 
     184 * Use targeted nth-last-child selectors to control the size of each image 
     185 * based on how many gallery items are present in the grid. 
     186 * See: https://alistapart.com/article/quantity-queries-for-css 
     187 */ 
     188.media-widget-gallery-preview .gallery-item:nth-last-child(3):first-child, 
     189.media-widget-gallery-preview .gallery-item:nth-last-child(3):first-child ~ .gallery-item, 
     190.media-widget-gallery-preview .gallery-item:nth-last-child(n+5), 
     191.media-widget-gallery-preview .gallery-item:nth-last-child(n+5) ~ .gallery-item, 
     192.media-widget-gallery-preview .gallery-item:nth-last-child(n+6), 
     193.media-widget-gallery-preview .gallery-item:nth-last-child(n+6) ~ .gallery-item { 
     194        max-width: 33.33%; 
     195} 
     196 
     197.media-widget-gallery-preview .gallery-item img { 
     198        height: auto; 
     199        vertical-align: bottom; 
     200} 
     201 
     202.media-widget-gallery-preview .gallery-icon { 
     203        position: relative; 
     204} 
     205 
     206.media-widget-gallery-preview .gallery-icon-placeholder { 
     207        position: absolute; 
     208        top: 0; 
     209        bottom: 0; 
     210        width: 100%; 
     211        box-sizing: border-box; 
     212        display: flex; 
     213        align-items: center; 
     214        justify-content: center; 
     215        background-color: rgba( 0, 0, 0, .5 ); 
     216} 
     217 
     218.media-widget-gallery-preview .gallery-icon-placeholder-text { 
     219        font-weight: 600; 
     220        font-size: 2em; 
     221        color: white; 
     222} 
     223 
     224 
    165225/* Widget Dragging Helpers */ 
    166226.widget.ui-draggable-dragging { 
    167227        min-width: 100%; 
  • new file src/wp-admin/js/widgets/media-gallery-widget.js

    diff --git src/wp-admin/js/widgets/media-gallery-widget.js src/wp-admin/js/widgets/media-gallery-widget.js
    new file mode 100644
    index 0000000000..3a569eda81
    - +  
     1/* eslint consistent-this: [ "error", "control" ] */ 
     2(function( component, $ ) { 
     3        'use strict'; 
     4 
     5        var GalleryWidgetModel, GalleryWidgetControl, GalleryDetailsMediaFrame; 
     6 
     7        /** 
     8         * Custom gallery details frame. 
     9         * 
     10         * @class GalleryDetailsMediaFrame 
     11         * @constructor 
     12         */ 
     13        GalleryDetailsMediaFrame = wp.media.view.MediaFrame.Post.extend( { 
     14 
     15                /** 
     16                 * Create the default states. 
     17                 * 
     18                 * @returns {void} 
     19                 */ 
     20                createStates: function createStates() { 
     21                        this.states.add([ 
     22                                new wp.media.controller.Library({ 
     23                                        id:         'gallery', 
     24                                        title:      wp.media.view.l10n.createGalleryTitle, 
     25                                        priority:   40, 
     26                                        toolbar:    'main-gallery', 
     27                                        filterable: 'uploaded', 
     28                                        multiple:   'add', 
     29                                        editable:   true, 
     30 
     31                                        library:  wp.media.query( _.defaults({ 
     32                                                type: 'image' 
     33                                        }, this.options.library ) ) 
     34                                }), 
     35 
     36                                // Gallery states. 
     37                                new wp.media.controller.GalleryEdit({ 
     38                                        library: this.options.selection, 
     39                                        editing: this.options.editing, 
     40                                        menu:    'gallery' 
     41                                }), 
     42 
     43                                new wp.media.controller.GalleryAdd() 
     44                        ]); 
     45                } 
     46        } ); 
     47 
     48        /** 
     49         * Gallery widget model. 
     50         * 
     51         * See WP_Widget_Gallery::enqueue_admin_scripts() for amending prototype from PHP exports. 
     52         * 
     53         * @class GalleryWidgetModel 
     54         * @constructor 
     55         */ 
     56        GalleryWidgetModel = component.MediaWidgetModel.extend( {} ); 
     57 
     58        /** 
     59         * Gallery widget control. 
     60         * 
     61         * See WP_Widget_Gallery::enqueue_admin_scripts() for amending prototype from PHP exports. 
     62         * 
     63         * @class GalleryWidgetControl 
     64         * @constructor 
     65         */ 
     66        GalleryWidgetControl = component.MediaWidgetControl.extend( { 
     67 
     68                events: _.extend( {}, component.MediaWidgetControl.prototype.events, { 
     69                        'click .media-widget-preview': 'editMedia' 
     70                } ), 
     71 
     72                /** 
     73                 * Initialize. 
     74                 * 
     75                 * @param {Object}         options - Options. 
     76                 * @param {Backbone.Model} options.model - Model. 
     77                 * @param {jQuery}         options.el - Control field container element. 
     78                 * @param {jQuery}         options.syncContainer - Container element where fields are synced for the server. 
     79                 * @returns {void} 
     80                 */ 
     81                initialize: function initialize( options ) { 
     82                        var control = this; 
     83 
     84                        component.MediaWidgetControl.prototype.initialize.call( control, options ); 
     85 
     86                        _.bindAll( control, 'updateSelectedAttachments', 'handleAttachmentDestroy' ); 
     87                        control.selectedAttachments = new wp.media.model.Attachments(); 
     88                        control.model.on( 'change:ids', control.updateSelectedAttachments ); 
     89                        control.selectedAttachments.on( 'change', control.renderPreview ); 
     90                        control.selectedAttachments.on( 'reset', control.renderPreview ); 
     91                        control.updateSelectedAttachments(); 
     92                }, 
     93 
     94                /** 
     95                 * Update the selected attachments if necessary. 
     96                 * 
     97                 * @returns {void} 
     98                 */ 
     99                updateSelectedAttachments: function updateSelectedAttachments() { 
     100                        var control = this, newIds, oldIds, removedIds, addedIds, addedQuery; 
     101 
     102                        newIds = control.parseIdList( control.model.get( 'ids' ) ); 
     103                        oldIds = _.pluck( control.selectedAttachments.models, 'id' ); 
     104 
     105                        removedIds = _.difference( oldIds, newIds ); 
     106                        _.each( removedIds, function( removedId ) { 
     107                                control.selectedAttachments.remove( control.selectedAttachments.get( removedId ) ); 
     108                        }); 
     109 
     110                        addedIds = _.difference( newIds, oldIds ); 
     111                        if ( addedIds.length ) { 
     112                                addedQuery = wp.media.query({ 
     113                                        order: 'ASC', 
     114                                        orderby: 'post__in', 
     115                                        perPage: -1, 
     116                                        post__in: newIds, 
     117                                        query: true, 
     118                                        type: 'image' 
     119                                }); 
     120                                addedQuery.more().done( function() { 
     121                                        control.selectedAttachments.reset( addedQuery.models ); 
     122                                }); 
     123                        } 
     124                }, 
     125 
     126                /** 
     127                 * Parse ID list. 
     128                 * 
     129                 * @param {Array|string} ids - ID list. 
     130                 * @returns {Array} Valid integers. 
     131                 */ 
     132                parseIdList: function( ids ) { 
     133                        var parsedIds; 
     134                        if ( 'string' === typeof ids ) { 
     135                                parsedIds = ids.split( ',' ); 
     136                        } else { 
     137                                parsedIds = ids; 
     138                        } 
     139                        return _.filter( 
     140                                _.map( parsedIds, function( id ) { 
     141                                        return parseInt( id, 10 ); 
     142                                }, 
     143                                function( id ) { 
     144                                        return 'number' === typeof id; 
     145                                } 
     146                        ) ); 
     147                }, 
     148 
     149                /** 
     150                 * Render preview. 
     151                 * 
     152                 * @returns {void} 
     153                 */ 
     154                renderPreview: function renderPreview() { 
     155                        var control = this, previewContainer, previewTemplate, data; 
     156 
     157                        previewContainer = control.$el.find( '.media-widget-preview' ); 
     158                        previewTemplate = wp.template( 'wp-media-widget-gallery-preview' ); 
     159 
     160                        data = control.previewTemplateProps.toJSON(); 
     161                        data.ids = control.parseIdList( data.ids ); 
     162                        data.attachments = {}; 
     163                        control.selectedAttachments.each( function( attachment ) { 
     164                                data.attachments[ attachment.id ] = attachment.toJSON(); 
     165                        } ); 
     166 
     167                        previewContainer.html( previewTemplate( data ) ); 
     168                        previewContainer.addClass( 'gallery' ); 
     169                }, 
     170 
     171                /** 
     172                 * Determine whether there are selected attachments. 
     173                 * 
     174                 * @returns {boolean} Selected. 
     175                 */ 
     176                isSelected: function isSelected() { 
     177                        var control = this, ids; 
     178 
     179                        if ( control.model.get( 'error' ) ) { 
     180                                return false; 
     181                        } 
     182 
     183                        ids = control.parseIdList( control.model.get( 'ids' ) ); 
     184                        return ids.length > 0; 
     185                }, 
     186 
     187                /** 
     188                 * Sync the model attributes to the hidden inputs, and update previewTemplateProps. 
     189                 * 
     190                 * @todo Patch this in core to eliminate need for override. 
     191                 * @returns {void} 
     192                 */ 
     193                syncModelToInputs: function syncModelToInputs() { 
     194                        var control = this; 
     195                        control.syncContainer.find( '.media-widget-instance-property' ).each( function() { 
     196                                var input = $( this ), value, propertyName = input.data( 'property' ); 
     197                                value = control.model.get( propertyName ); 
     198                                if ( _.isUndefined( value ) ) { 
     199                                        return; 
     200                                } 
     201 
     202                                // @todo Support comma-separated ID list arrays? This will depend on WP_Widget_Media::form() being updated to support serializing array to form field. 
     203                                if ( 'boolean' === control.model.schema[ propertyName ].type ) { 
     204                                        value = value ? '1' : ''; // Because in PHP, strval( true ) === '1' && strval( false ) === ''. 
     205                                } else { 
     206                                        value = String( value ); 
     207                                } 
     208 
     209                                if ( input.val() !== value ) { 
     210                                        input.val( value ); 
     211                                        input.trigger( 'change' ); 
     212                                } 
     213                        }); 
     214                }, 
     215 
     216                /** 
     217                 * Open the media select frame to edit images. 
     218                 * 
     219                 * @returns {void} 
     220                 */ 
     221                editMedia: function editMedia() { 
     222                        var control = this, selection, mediaFrame, mediaFrameProps; 
     223 
     224                        // If no images are selected, open the select frame instead. 
     225                        if ( ! control.isSelected() ) { 
     226                                control.selectMedia(); 
     227                                return; 
     228                        } 
     229 
     230                        selection = new wp.media.model.Selection( control.selectedAttachments.models, { 
     231                                multiple: true 
     232                        }); 
     233 
     234                        mediaFrameProps = control.mapModelToMediaFrameProps( control.model.toJSON() ); 
     235                        selection.gallery = new Backbone.Model( _.pick( mediaFrameProps, 'columns', 'link', 'size', '_orderbyRandom' ) ); 
     236                        if ( mediaFrameProps.size ) { 
     237                                control.displaySettings.set( 'size', mediaFrameProps.size ); 
     238                        } 
     239                        mediaFrame = new GalleryDetailsMediaFrame({ 
     240                                frame: 'manage', 
     241                                text: control.l10n.add_to_widget, 
     242                                selection: selection, 
     243                                mimeType: control.mime_type, 
     244                                selectedDisplaySettings: control.displaySettings, 
     245                                showDisplaySettings: control.showDisplaySettings, 
     246                                metadata: mediaFrameProps, 
     247                                editing:   true, 
     248                                multiple:  true, 
     249                                state: 'gallery-edit' 
     250                        }); 
     251                        wp.media.frame = mediaFrame; // See wp.media(). 
     252 
     253                        // Handle selection of a media item. 
     254                        mediaFrame.on( 'update', function onUpdate( newSelection ) { 
     255                                var state = mediaFrame.state(), resultSelection; 
     256 
     257                                resultSelection = newSelection || state.get( 'selection' ); 
     258                                if ( ! resultSelection ) { 
     259                                        return; 
     260                                } 
     261 
     262                                // Copy orderby_random from gallery state. 
     263                                if ( resultSelection.gallery ) { 
     264                                        control.model.set( control.mapMediaToModelProps( resultSelection.gallery.toJSON() ) ); 
     265                                } 
     266 
     267                                // Directly update selectedAttachments to prevent needing to do additional request. 
     268                                control.selectedAttachments.reset( resultSelection.models ); 
     269 
     270                                // Update models in the widget instance. 
     271                                control.model.set( { 
     272                                        ids: _.pluck( resultSelection.models, 'id' ).join( ',' ) // @todo Array. 
     273                                } ); 
     274                        } ); 
     275 
     276                        mediaFrame.$el.addClass( 'media-widget' ); 
     277                        mediaFrame.open(); 
     278 
     279                        if ( selection ) { 
     280                                selection.on( 'destroy', control.handleAttachmentDestroy ); 
     281                        } 
     282                }, 
     283 
     284                /** 
     285                 * Open the media select frame to chose an item. 
     286                 * 
     287                 * @returns {void} 
     288                 */ 
     289                selectMedia: function selectMedia() { 
     290                        var control = this, selection, mediaFrame, mediaFrameProps; 
     291                        selection = new wp.media.model.Selection( control.selectedAttachments.models, { 
     292                                multiple: true 
     293                        }); 
     294 
     295                        mediaFrameProps = control.mapModelToMediaFrameProps( control.model.toJSON() ); 
     296                        if ( mediaFrameProps.size ) { 
     297                                control.displaySettings.set( 'size', mediaFrameProps.size ); 
     298                        } 
     299                        mediaFrame = new GalleryDetailsMediaFrame({ 
     300                                frame: 'select', 
     301                                text: control.l10n.add_to_widget, 
     302                                selection: selection, 
     303                                mimeType: control.mime_type, 
     304                                selectedDisplaySettings: control.displaySettings, 
     305                                showDisplaySettings: control.showDisplaySettings, 
     306                                metadata: mediaFrameProps, 
     307                                state: 'gallery' 
     308                        }); 
     309                        wp.media.frame = mediaFrame; // See wp.media(). 
     310 
     311                        // Handle selection of a media item. 
     312                        mediaFrame.on( 'update', function onUpdate( newSelection ) { 
     313                                var state = mediaFrame.state(), resultSelection; 
     314 
     315                                resultSelection = newSelection || state.get( 'selection' ); 
     316                                if ( ! resultSelection ) { 
     317                                        return; 
     318                                } 
     319 
     320                                // Copy orderby_random from gallery state. 
     321                                if ( resultSelection.gallery ) { 
     322                                        control.model.set( control.mapMediaToModelProps( resultSelection.gallery.toJSON() ) ); 
     323                                } 
     324 
     325                                // Directly update selectedAttachments to prevent needing to do additional request. 
     326                                control.selectedAttachments.reset( resultSelection.models ); 
     327 
     328                                // Update widget instance. 
     329                                control.model.set( { 
     330                                        ids: _.pluck( resultSelection.models, 'id' ).join( ',' ) // @todo Allow array. 
     331                                } ); 
     332                        } ); 
     333 
     334                        mediaFrame.$el.addClass( 'media-widget' ); 
     335                        mediaFrame.open(); 
     336 
     337                        if ( selection ) { 
     338                                selection.on( 'destroy', control.handleAttachmentDestroy ); 
     339                        } 
     340 
     341                        /* 
     342                         * Make sure focus is set inside of modal so that hitting Esc will close 
     343                         * the modal and not inadvertently cause the widget to collapse in the customizer. 
     344                         */ 
     345                        mediaFrame.$el.find( ':focusable:first' ).focus(); 
     346                }, 
     347 
     348                /** 
     349                 * Clear the selected attachment when it is deleted in the media select frame. 
     350                 * 
     351                 * @param {wp.media.models.Attachment} attachment - Attachment. 
     352                 * @returns {void} 
     353                 */ 
     354                handleAttachmentDestroy: function handleAttachmentDestroy( attachment ) { 
     355                        var control = this; 
     356                        control.model.set( { 
     357                                ids: _.difference( 
     358                                        control.parseIdList( control.model.get( 'ids' ) ), 
     359                                        [ attachment.id ] 
     360                                ).join( ',' ) // @todo Array. 
     361                        } ); 
     362                } 
     363        } ); 
     364 
     365        // Exports. 
     366        component.controlConstructors.media_gallery = GalleryWidgetControl; 
     367        component.modelConstructors.media_gallery = GalleryWidgetModel; 
     368 
     369})( wp.mediaWidgets, jQuery ); 
  • src/wp-includes/default-widgets.php

    diff --git src/wp-includes/default-widgets.php src/wp-includes/default-widgets.php
    index 7c8a903c56..767002b642 100644
    require_once( ABSPATH . WPINC . '/widgets/class-wp-widget-media-image.php' ); 
    3131/** WP_Widget_Media_Video class */ 
    3232require_once( ABSPATH . WPINC . '/widgets/class-wp-widget-media-video.php' ); 
    3333 
     34/** WP_Widget_Media_Gallery class */ 
     35require_once( ABSPATH . WPINC . '/widgets/class-wp-widget-media-gallery.php' ); 
     36 
    3437/** WP_Widget_Meta class */ 
    3538require_once( ABSPATH . WPINC . '/widgets/class-wp-widget-meta.php' ); 
    3639 
  • src/wp-includes/script-loader.php

    diff --git src/wp-includes/script-loader.php src/wp-includes/script-loader.php
    index c85822c807..fa1ada70fb 100644
    function wp_default_scripts( &$scripts ) { 
    699699 
    700700                $scripts->add( 'media-audio-widget', "/wp-admin/js/widgets/media-audio-widget$suffix.js", array( 'media-widgets', 'media-audiovideo' ) ); 
    701701                $scripts->add( 'media-image-widget', "/wp-admin/js/widgets/media-image-widget$suffix.js", array( 'media-widgets' ) ); 
     702                $scripts->add( 'media-gallery-widget', "/wp-admin/js/widgets/media-gallery-widget$suffix.js", array( 'media-widgets' ) ); 
    702703                $scripts->add( 'media-video-widget', "/wp-admin/js/widgets/media-video-widget$suffix.js", array( 'media-widgets', 'media-audiovideo', 'wp-api-request' ) ); 
    703704                $scripts->add( 'text-widgets', "/wp-admin/js/widgets/text-widgets$suffix.js", array( 'jquery', 'backbone', 'editor', 'wp-util', 'wp-a11y' ) ); 
    704705                $scripts->add( 'custom-html-widgets', "/wp-admin/js/widgets/custom-html-widgets$suffix.js", array( 'code-editor', 'jquery', 'backbone', 'wp-util', 'jquery-ui-core', 'wp-a11y' ) ); 
  • src/wp-includes/widgets.php

    diff --git src/wp-includes/widgets.php src/wp-includes/widgets.php
    index fe0e058d9a..e4fcc527b7 100644
    function wp_widgets_init() { 
    16091609 
    16101610        register_widget( 'WP_Widget_Media_Image' ); 
    16111611 
     1612        register_widget( 'WP_Widget_Media_Gallery' ); 
     1613 
    16121614        register_widget( 'WP_Widget_Media_Video' ); 
    16131615 
    16141616        register_widget( 'WP_Widget_Meta' ); 
  • new file src/wp-includes/widgets/class-wp-widget-media-gallery.php

    diff --git src/wp-includes/widgets/class-wp-widget-media-gallery.php src/wp-includes/widgets/class-wp-widget-media-gallery.php
    new file mode 100644
    index 0000000000..591b046d50
    - +  
     1<?php 
     2/** 
     3 * Widget API: WP_Widget_Media_Gallery class 
     4 * 
     5 * @package WordPress 
     6 * @subpackage Widgets 
     7 * @since 4.9.0 
     8 */ 
     9 
     10/** 
     11 * Core class that implements a gallery widget. 
     12 * 
     13 * @since 4.9.0 
     14 * 
     15 * @see WP_Widget 
     16 */ 
     17class WP_Widget_Media_Gallery extends WP_Widget_Media { 
     18 
     19        /** 
     20         * Constructor. 
     21         * 
     22         * @since  4.9.0 
     23         */ 
     24        public function __construct() { 
     25                parent::__construct( 'media_gallery', __( 'Gallery' ), array( 
     26                        'description' => __( 'Displays an image gallery.' ), 
     27                        'mime_type'   => 'image', 
     28                ) ); 
     29 
     30                $this->l10n = array_merge( $this->l10n, array( 
     31                        'no_media_selected' => __( 'No images selected' ), 
     32                        'select_media' => _x( 'Select Images', 'label for button in the gallery widget; should not be longer than ~13 characters long' ), 
     33                        'replace_media' => _x( 'Replace Gallery', 'label for button in the gallery widget; should not be longer than ~13 characters long' ), 
     34                        'change_media' => _x( 'Add Image', 'label for button in the gallery widget; should not be longer than ~13 characters long' ), 
     35                        'edit_media' => _x( 'Edit Gallery', 'label for button in the gallery widget; should not be longer than ~13 characters long' ), 
     36                ) ); 
     37        } 
     38 
     39        /** 
     40         * Get schema for properties of a widget instance (item). 
     41         * 
     42         * @since  4.9.0 
     43         * 
     44         * @see WP_REST_Controller::get_item_schema() 
     45         * @see WP_REST_Controller::get_additional_fields() 
     46         * @link https://core.trac.wordpress.org/ticket/35574 
     47         * @return array Schema for properties. 
     48         */ 
     49        public function get_instance_schema() { 
     50                return array( 
     51                        'title' => array( 
     52                                'type' => 'string', 
     53                                'default' => '', 
     54                                'sanitize_callback' => 'sanitize_text_field', 
     55                                'description' => __( 'Title for the widget' ), 
     56                                'should_preview_update' => false, 
     57                        ), 
     58                        'ids' => array( 
     59                                'type' => 'string', 
     60                                'default' => '', 
     61                        ), 
     62                        'columns' => array( 
     63                                'type' => 'integer', 
     64                                'default' => 3, 
     65                        ), 
     66                        'size' => array( 
     67                                'type' => 'string', 
     68                                'enum' => array_merge( get_intermediate_image_sizes(), array( 'full', 'custom' ) ), 
     69                                'default' => 'thumbnail', 
     70                        ), 
     71                        'link_type' => array( 
     72                                'type' => 'string', 
     73                                'enum' => array( 'none', 'file', 'post' ), 
     74                                'default' => 'none', 
     75                                'media_prop' => 'link', 
     76                                'should_preview_update' => false, 
     77                        ), 
     78                        'orderby_random' => array( 
     79                                'type'                  => 'boolean', 
     80                                'default'               => false, 
     81                                'media_prop'            => '_orderbyRandom', 
     82                                'should_preview_update' => false, 
     83                        ), 
     84                ); 
     85        } 
     86 
     87        /** 
     88         * Sanitizes the widget form values as they are saved. 
     89         * 
     90         * @since 4.9.0 
     91         * 
     92         * @param array $new_instance Values just sent to be saved. 
     93         * @param array $instance Previously saved values from database. 
     94         * @return array Updated safe values to be saved. 
     95         */ 
     96        public function update( $new_instance, $instance ) { 
     97 
     98                // Workaround for rest_validate_value_from_schema() due to the fact that rest_is_boolean( '' ) === false, while rest_is_boolean( '1' ) is true. 
     99                if ( isset( $new_instance['orderby_random'] ) && '' === $new_instance['orderby_random'] ) { 
     100                        $new_instance['orderby_random'] = false; 
     101                } 
     102 
     103                return parent::update( $new_instance, $instance ); 
     104        } 
     105 
     106        /** 
     107         * Render the media on the frontend. 
     108         * 
     109         * @since  4.9.0 
     110         * 
     111         * @param array $instance Widget instance props. 
     112         * @return void 
     113         */ 
     114        public function render_media( $instance ) { 
     115                $instance = array_merge( wp_list_pluck( $this->get_instance_schema(), 'default' ), $instance ); 
     116 
     117                $shortcode_atts = array( 
     118                        'ids'     => $instance['ids'], 
     119                        'columns' => $instance['columns'], 
     120                        'link'    => $instance['link_type'], 
     121                        'size'    => $instance['size'], 
     122                ); 
     123 
     124                // @codingStandardsIgnoreStart 
     125                if ( $instance['orderby_random'] ) { 
     126                        $shortcode_atts['orderby'] = 'rand'; 
     127                } 
     128                // @codingStandardsIgnoreEnd 
     129 
     130                echo gallery_shortcode( $shortcode_atts ); 
     131        } 
     132 
     133        /** 
     134         * Loads the required media files for the media manager and scripts for media widgets. 
     135         * 
     136         * @since 4.9.0 
     137         */ 
     138        public function enqueue_admin_scripts() { 
     139                parent::enqueue_admin_scripts(); 
     140 
     141                $handle = 'media-gallery-widget'; 
     142                wp_enqueue_script( $handle ); 
     143 
     144                $exported_schema = array(); 
     145                foreach ( $this->get_instance_schema() as $field => $field_schema ) { 
     146                        $exported_schema[ $field ] = wp_array_slice_assoc( $field_schema, array( 'type', 'default', 'enum', 'minimum', 'format', 'media_prop', 'should_preview_update' ) ); 
     147                } 
     148                wp_add_inline_script( 
     149                        $handle, 
     150                        sprintf( 
     151                                'wp.mediaWidgets.modelConstructors[ %s ].prototype.schema = %s;', 
     152                                wp_json_encode( $this->id_base ), 
     153                                wp_json_encode( $exported_schema ) 
     154                        ) 
     155                ); 
     156 
     157                wp_add_inline_script( 
     158                        $handle, 
     159                        sprintf( 
     160                                ' 
     161                                        wp.mediaWidgets.controlConstructors[ %1$s ].prototype.mime_type = %2$s; 
     162                                        _.extend( wp.mediaWidgets.controlConstructors[ %1$s ].prototype.l10n, %3$s ); 
     163                                ', 
     164                                wp_json_encode( $this->id_base ), 
     165                                wp_json_encode( $this->widget_options['mime_type'] ), 
     166                                wp_json_encode( $this->l10n ) 
     167                        ) 
     168                ); 
     169        } 
     170 
     171        /** 
     172         * Render form template scripts. 
     173         * 
     174         * @since 4.9.0 
     175         */ 
     176        public function render_control_template_scripts() { 
     177                parent::render_control_template_scripts(); 
     178                ?> 
     179                <script type="text/html" id="tmpl-wp-media-widget-gallery-preview"> 
     180                        <# var describedById = 'describedBy-' + String( Math.random() ); #> 
     181                        <# if ( data.ids.length ) { #> 
     182                                <div class="gallery media-widget-gallery-preview"> 
     183                                        <# _.each( data.ids, function( id, index ) { #> 
     184                                                <# 
     185                                                var attachment = data.attachments[ id ]; 
     186                                                if ( ! attachment ) { 
     187                                                        return; 
     188                                                } 
     189                                                #> 
     190                                                <# if ( index < 6 ) { #> 
     191                                                        <dl class="gallery-item"> 
     192                                                                <dt class="gallery-icon"> 
     193                                                                <# if ( attachment.sizes.thumbnail ) { #> 
     194                                                                        <img src="{{ attachment.sizes.thumbnail.url }}" width="{{ attachment.sizes.thumbnail.width }}" height="{{ attachment.sizes.thumbnail.height }}" alt="" /> 
     195                                                                <# } else { #> 
     196                                                                        <img src="{{ attachment.url }}" alt="" /> 
     197                                                                <# } #> 
     198                                                                <# if ( index === 5 && data.ids.length > 6 ) { #> 
     199                                                                        <div class="gallery-icon-placeholder"> 
     200                                                                                <p class="gallery-icon-placeholder-text">+{{ data.ids.length - 5 }}</p> 
     201                                                                        </div> 
     202                                                                <# } #> 
     203                                                                </dt> 
     204                                                        </dl> 
     205                                                <# } #> 
     206                                        <# } ); #> 
     207                                </div> 
     208                        <# } else { #> 
     209                                <div class="attachment-media-view"> 
     210                                        <p class="placeholder"><?php echo esc_html( $this->l10n['no_media_selected'] ); ?></p> 
     211                                </div> 
     212                        <# } #> 
     213                </script> 
     214                <?php 
     215        } 
     216 
     217        /** 
     218         * Whether the widget has content to show. 
     219         * 
     220         * @since 4.9.0 
     221         * @access protected 
     222         * 
     223         * @param array $instance Widget instance props. 
     224         * @return bool Whether widget has content. 
     225         */ 
     226        protected function has_content( $instance ) { 
     227 
     228                if ( ! empty( $instance['ids'] ) ) { 
     229 
     230                        $attachments = explode( ',', $instance['ids'] ); 
     231                        foreach ( $attachments as $attachment ) { 
     232                                if ( 'attachment' !== get_post_type( $attachment ) ) { 
     233 
     234                                        return false; 
     235                                } 
     236                        } 
     237                        return true; 
     238                } else { 
     239 
     240                        return false; 
     241                } 
     242        } 
     243} 
  • new file tests/phpunit/tests/widgets/media-gallery-widget.php

    diff --git tests/phpunit/tests/widgets/media-gallery-widget.php tests/phpunit/tests/widgets/media-gallery-widget.php
    new file mode 100644
    index 0000000000..37f8fc541d
    - +  
     1<?php 
     2/** 
     3 * Unit tests covering WP_Widget_Media_Gallery functionality. 
     4 * 
     5 * @package    WordPress 
     6 * @subpackage widgets 
     7 */ 
     8 
     9/** 
     10 * Test wp-includes/widgets/class-wp-widget-gallery.php 
     11 * 
     12 * @group widgets 
     13 */ 
     14class Test_WP_Widget_Media_Gallery extends WP_UnitTestCase { 
     15 
     16        /** 
     17         * Test get_instance_schema method. 
     18         * 
     19         * @covers WP_Widget_Media_Gallery::get_instance_schema() 
     20         */ 
     21        public function test_get_instance_schema() { 
     22                $widget = new WP_Widget_Media_Gallery(); 
     23                $schema = $widget->get_instance_schema(); 
     24 
     25                $this->assertEqualSets( 
     26                        array( 
     27                                'title', 
     28                                'ids', 
     29                                'columns', 
     30                                'size', 
     31                                'link_type', 
     32                                'orderby_random', 
     33                        ), 
     34                        array_keys( $schema ) 
     35                ); 
     36        } 
     37 
     38} 
  • tests/qunit/index.html

    diff --git tests/qunit/index.html tests/qunit/index.html
    index 52f8f8d789..d096aeeec7 100644
     
    119119                        wp.mediaWidgets.controlConstructors[ "media_audio" ].prototype.mime_type = "audio"; 
    120120                        _.extend( wp.mediaWidgets.controlConstructors[ "media_audio" ].prototype.l10n, {"no_media_selected":"No audio selected","select_media":"Select File","change_media":"Change Audio","edit_media":"Edit Audio","add_to_widget":"Add to Widget","missing_attachment":"We can&#8217;t find that audio file. Check your <a href=\"http:\/\/src.wordpress-develop.dev\/wp-admin\/upload.php\">media library<\/a> and make sure it wasn&#8217;t deleted.","media_library_state_multi":{"0":"Audio Widget (%d)","1":"Audio Widget (%d)","singular":"Audio Widget (%d)","plural":"Audio Widget (%d)","context":null,"domain":null},"media_library_state_single":"Audio Widget"} ); 
    121121                </script> 
     122                <script type='text/javascript' src='../../src/wp-admin/js/widgets/media-gallery-widget.js'></script> 
     123                <script type='text/javascript'> 
     124                        wp.mediaWidgets.modelConstructors[ "media_gallery" ].prototype.schema = {"title":{"type":"string","default":"","should_preview_update":false},"ids":{"type":"string","default":""},"columns":{"type":"integer","default":3},"size":{"type":"string","default":"thumbnail","enum":["thumbnail","medium","medium_large","large","post-thumbnail","full","custom"]},"link_type":{"type":"string","default":"none","enum":["none","file","post"],"media_prop":"link","should_preview_update":false},"orderby_random":{"type":"boolean","default":false,"media_prop":"_orderbyRandom","should_preview_update":false},"attachments":{"type":"string","default":""}}; 
     125                        wp.mediaWidgets.controlConstructors[ "media_gallery" ].prototype.mime_type = "image"; 
     126 
     127                        _.extend( wp.mediaWidgets.controlConstructors[ "media_gallery" ].prototype.l10n, {"no_media_selected":"No images selected","add_media":"Add Media","replace_media":"Replace Media","edit_media":"Edit Gallery","add_to_widget":"Add to Widget","missing_attachment":"We can&#8217;t find that gallery. Check your <a href=\"http:\/\/src.wordpress-develop.dev\/wp-admin\/upload.php\">media library<\/a> and make sure it wasn&#8217;t deleted.","media_library_state_multi":"","media_library_state_single":"","unsupported_file_type":"Looks like this isn&#8217;t the correct kind of file. Please link to an appropriate file instead.","select_media":"Select Images","change_media":"Add Image"} ); 
     128                </script> 
    122129 
    123130                <!-- Unit tests --> 
    124131                <script src="wp-admin/js/password-strength-meter.js"></script> 
     
    136143                <script src="wp-admin/js/nav-menu.js"></script> 
    137144                <script src="wp-admin/js/widgets/test-media-widgets.js"></script> 
    138145                <script src="wp-admin/js/widgets/test-media-image-widget.js"></script> 
     146                <script src="wp-admin/js/widgets/test-media-gallery-widget.js"></script> 
    139147                <script src="wp-admin/js/widgets/test-media-video-widget.js"></script> 
    140148 
    141149                <!-- Customizer templates for sections --> 
     
    569577                                        </div><!-- #available-widgets-list --> 
    570578                                </div><!-- #available-widgets --> 
    571579                        </div><!-- #widgets-left --> 
    572                          
     580 
    573581                        <script type="text/html" id="tmpl-widget-media-media_image-control"> 
    574582                        <# var elementIdPrefix = 'el' + String( Math.random() ) + '_' #> 
    575583                        <p> 
     
    811819                <div class="media-frame-uploader"></div> 
    812820        </script> 
    813821 
     822        <script type="text/html" id="tmpl-widget-media-media_gallery-control"> 
     823                <# var elementIdPrefix = 'el' + String( Math.random() ) + '_' #> 
     824                <p> 
     825                        <label for="{{ elementIdPrefix }}title">Title:</label> 
     826                        <input id="{{ elementIdPrefix }}title" type="text" class="widefat title"> 
     827                </p> 
     828                <div class="media-widget-preview"> 
     829                        <div class="attachment-media-view"> 
     830                                <div class="placeholder">No images selected</div> 
     831                        </div> 
     832                        </div> 
     833                        <p class="media-widget-buttons"> 
     834                                <button type="button" class="button edit-media selected"> 
     835                                        Edit Gallery                            </button> 
     836                                <button type="button" class="button change-media select-media selected"> 
     837                                        Replace Media                           </button> 
     838                                <button type="button" class="button select-media not-selected"> 
     839                                        Add Media                               </button> 
     840                        </p> 
     841                <div class="media-widget-fields"> 
     842                </div> 
     843        </script> 
     844                                <script type="text/html" id="tmpl-wp-media-widget-gallery-preview"> 
     845                        <# var describedById = 'describedBy-' + String( Math.random() ); #> 
     846                        <# data.attachments = data.attachments ? JSON.parse(data.attachments) : ''; #> 
     847                        <# if ( Array.isArray( data.attachments ) && data.attachments.length ) { #> 
     848                                <div class="gallery gallery-columns-{{ data.columns }}"> 
     849                                        <# _.each( data.attachments, function( attachment, index ) { #> 
     850                                                <dl class="gallery-item"> 
     851                                                        <dt class="gallery-icon"> 
     852                                                        <# if ( attachment.sizes.thumbnail ) { #> 
     853                                                                <img src="{{ attachment.sizes.thumbnail.url }}" width="{{ attachment.sizes.thumbnail.width }}" height="{{ attachment.sizes.thumbnail.height }}" alt="" /> 
     854                                                        <# } else { #> 
     855                                                                <img src="{{ attachment.url }}" alt="" /> 
     856                                                        <# } #> 
     857                                                        </dt> 
     858                                                        <# if ( attachment.caption ) { #> 
     859                                                                <dd class="wp-caption-text gallery-caption"> 
     860                                                                        {{{ data.verifyHTML( attachment.caption ) }}} 
     861                                                                </dd> 
     862                                                        <# } #> 
     863                                                </dl> 
     864                                                <# if ( index % data.columns === data.columns - 1 ) { #> 
     865                                                        <br style="clear: both;"> 
     866                                                <# } #> 
     867                                        <# } ); #> 
     868                                </div> 
     869                        <# } else { #> 
     870                                <div class="attachment-media-view"> 
     871                                        <p class="placeholder">No images selected</p> 
     872                                </div> 
     873                        <# } #> 
     874                </script> 
     875 
    814876        <script type="text/html" id="tmpl-media-modal"> 
    815877                <div class="media-modal wp-core-ui"> 
    816878                        <button type="button" class="media-modal-close"><span class="media-modal-icon"><span class="screen-reader-text">Close media panel</span></span></button> 
  • new file tests/qunit/wp-admin/js/widgets/test-media-gallery-widget.js

    diff --git tests/qunit/wp-admin/js/widgets/test-media-gallery-widget.js tests/qunit/wp-admin/js/widgets/test-media-gallery-widget.js
    new file mode 100644
    index 0000000000..8154e49335
    - +  
     1/* jshint qunit: true */ 
     2/* eslint-env qunit */ 
     3/* eslint-disable no-magic-numbers */ 
     4 
     5( function() { 
     6        'use strict'; 
     7 
     8        module( 'Gallery Media Widget' ); 
     9 
     10        test( 'gallery widget control', function() { 
     11                var GalleryWidgetControl; 
     12                equal( typeof wp.mediaWidgets.controlConstructors.media_gallery, 'function', 'wp.mediaWidgets.controlConstructors.media_gallery is a function' ); 
     13                GalleryWidgetControl = wp.mediaWidgets.controlConstructors.media_gallery; 
     14                ok( GalleryWidgetControl.prototype instanceof wp.mediaWidgets.MediaWidgetControl, 'wp.mediaWidgets.controlConstructors.media_gallery subclasses wp.mediaWidgets.MediaWidgetControl' ); 
     15 
     16                // TODO more tests here. 
     17        }); 
     18 
     19        // TODO PREVIEW TESTS. 
     20 
     21        test( 'gallery media model', function() { 
     22                var GalleryWidgetModel, galleryWidgetModelInstance; 
     23                equal( typeof wp.mediaWidgets.modelConstructors.media_gallery, 'function', 'wp.mediaWidgets.modelConstructors.media_gallery is a function' ); 
     24                GalleryWidgetModel = wp.mediaWidgets.modelConstructors.media_gallery; 
     25                ok( GalleryWidgetModel.prototype instanceof wp.mediaWidgets.MediaWidgetModel, 'wp.mediaWidgets.modelConstructors.media_gallery subclasses wp.mediaWidgets.MediaWidgetModel' ); 
     26 
     27                galleryWidgetModelInstance = new GalleryWidgetModel(); 
     28                _.each( galleryWidgetModelInstance.attributes, function( value, key ) { 
     29                        equal( value, GalleryWidgetModel.prototype.schema[ key ][ 'default' ], 'Should properly set default for ' + key ); 
     30                }); 
     31        }); 
     32 
     33})();