WordPress.org

Make WordPress Core

Ticket #41914: 41914.0.diff

File 41914.0.diff, 30.6 KB (added by westonruter, 9 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})();