Make WordPress Core

Ticket #9757: 9757.1.patch

File 9757.1.patch, 23.5 KB (added by imath, 7 years ago)
  • src/wp-admin/css/themes.css

    diff --git src/wp-admin/css/themes.css src/wp-admin/css/themes.css
    index eece216a98..7865cae62f 100644
    body.folded .theme-browser ~ .theme-overlay .theme-wrap { 
    10541054        margin: 30px auto;
    10551055        max-width: 380px;
    10561056}
     1057.upload-plugin .wp-upload-form #existing-plugin-info h3 {
     1058        margin-top: 8px;
     1059}
     1060.upload-plugin .wp-upload-form .plugin-card-top {
     1061        padding-top: 10px;
     1062}
     1063.upload-plugin .wp-upload-form.has-existing-plugin #install-plugin-submit,
     1064.upload-plugin .wp-upload-form.has-existing-plugin .install-plugin-reset {
     1065        display: inline-block;
     1066        margin-top: 20px;
     1067        margin-right: 10px;
     1068}
     1069
    10571070.upload-theme .install-help,
    10581071.upload-plugin .install-help {
    10591072        color: #555d66; /* #f1f1f1 background */
  • src/wp-admin/includes/class-plugin-upgrader.php

    diff --git src/wp-admin/includes/class-plugin-upgrader.php src/wp-admin/includes/class-plugin-upgrader.php
    index 3c05353bc1..e6abed1f3d 100644
    class Plugin_Upgrader extends WP_Upgrader { 
    5353                $this->strings['remove_old_failed'] = __('Could not remove the old plugin.');
    5454                $this->strings['process_failed'] = __('Plugin update failed.');
    5555                $this->strings['process_success'] = __('Plugin updated successfully.');
     56                $this->strings['overwrite_success'] = __('Plugin replaced successfully.');
    5657                $this->strings['process_bulk_success'] = __('Plugins updated successfully.');
    5758        }
    5859
    class Plugin_Upgrader extends WP_Upgrader { 
    136137         * @param array  $args {
    137138         *     Optional. Other arguments for upgrading a plugin package. Default empty array.
    138139         *
    139          *     @type bool $clear_update_cache Whether to clear the plugin updates cache if successful.
    140          *                                    Default true.
     140         *     @type bool   $clear_update_cache Whether to clear the plugin updates cache if successful.
     141         *                                      Default true.
     142         *     @type string $package            Path to the package to replace an existing plugin with.
     143         *                                      Default ''.
    141144         * }
    142145         * @return bool|WP_Error True if the upgrade was successful, false or a WP_Error object otherwise.
    143146         */
    class Plugin_Upgrader extends WP_Upgrader { 
    145148
    146149                $defaults = array(
    147150                        'clear_update_cache' => true,
     151                        'package'            => '',
    148152                );
    149153                $parsed_args = wp_parse_args( $args, $defaults );
    150154
    151155                $this->init();
    152156                $this->upgrade_strings();
    153157
    154                 $current = get_site_transient( 'update_plugins' );
    155                 if ( !isset( $current->response[ $plugin ] ) ) {
    156                         $this->skin->before();
    157                         $this->skin->set_result(false);
    158                         $this->skin->error('up_to_date');
    159                         $this->skin->after();
    160                         return false;
    161                 }
     158                $package = $parsed_args['package'];
     159                if ( ! $package ) {
     160                        $current = get_site_transient( 'update_plugins' );
     161                        if ( !isset( $current->response[ $plugin ] ) ) {
     162                                $this->skin->before();
     163                                $this->skin->set_result(false);
     164                                $this->skin->error('up_to_date');
     165                                $this->skin->after();
     166                                return false;
     167                        }
    162168
    163                 // Get the URL to the zip file
    164                 $r = $current->response[ $plugin ];
     169                        // Get the URL to the zip file
     170                        $r = $current->response[ $plugin ];
     171                        $package = $r->package;
     172                        $action  = 'update';
     173                } else {
     174                        $action  = 'overwrite';
     175                }
    165176
    166177                add_filter('upgrader_pre_install', array($this, 'deactivate_plugin_before_upgrade'), 10, 2);
    167178                add_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'), 10, 4);
    class Plugin_Upgrader extends WP_Upgrader { 
    172183                }
    173184
    174185                $this->run( array(
    175                         'package' => $r->package,
     186                        'package' => $package,
    176187                        'destination' => WP_PLUGIN_DIR,
    177188                        'clear_destination' => true,
    178189                        'clear_working' => true,
    179190                        'hook_extra' => array(
    180191                                'plugin' => $plugin,
    181192                                'type' => 'plugin',
    182                                 'action' => 'update',
     193                                'action' => $action,
    183194                        ),
    184195                ) );
    185196
  • src/wp-admin/includes/class-wp-upgrader.php

    diff --git src/wp-admin/includes/class-wp-upgrader.php src/wp-admin/includes/class-wp-upgrader.php
    index fd70ba469e..7f8469c046 100644
    class WP_Upgrader { 
    157157
    158158                $this->strings['download_failed'] = __('Download failed.');
    159159                $this->strings['installing_package'] = __('Installing the latest version…');
     160                $this->strings['package_overwriting'] = __('Replacing the installed version…');
    160161                $this->strings['no_files'] = __('The package contains no files.');
    161162                $this->strings['folder_exists'] = __('Destination folder already exists.');
    162163                $this->strings['mkdir_failed'] = __('Could not create directory.');
    class WP_Upgrader { 
    451452                if ( empty( $source ) || empty( $destination ) ) {
    452453                        return new WP_Error( 'bad_request', $this->strings['bad_request'] );
    453454                }
    454                 $this->skin->feedback( 'installing_package' );
     455
     456                if ( isset( $args['hook_extra']['action'] ) && 'overwrite' === $args['hook_extra']['action'] ) {
     457                        // Overwriting the existing plugin
     458                        $this->skin->feedback( 'package_overwriting' );
     459                } else {
     460                        $this->skin->feedback( 'installing_package' );
     461                }
    455462
    456463                /**
    457464                 * Filters the install response before the installation has started.
    class WP_Upgrader { 
    751758                        $this->skin->error($result);
    752759                        $this->skin->feedback('process_failed');
    753760                } else {
    754                         // Installation succeeded.
    755                         $this->skin->feedback('process_success');
     761
     762                        if ( isset( $options['hook_extra']['action'] ) && 'overwrite' === $options['hook_extra']['action'] ) {
     763                                // Overwrite succeeded.
     764                                $this->skin->feedback( 'overwrite_success' );
     765                        } else {
     766                                // Installation succeeded.
     767                                $this->skin->feedback('process_success');
     768                        }
    756769                }
    757770
    758771                $this->skin->after();
  • src/wp-admin/includes/plugin-install.php

    diff --git src/wp-admin/includes/plugin-install.php src/wp-admin/includes/plugin-install.php
    index 1714acda71..c4087f4945 100644
    function install_plugins_upload() { 
    310310                <?php submit_button( __( 'Install Now' ), '', 'install-plugin-submit', false ); ?>
    311311        </form>
    312312</div>
     313<script type="text/html" id="tmpl-existing-plugin-data">
     314        <div id="existing-plugin-info">
     315                <p class="attention">{{{data.warnOverwrite}}}</p>
     316                <div class="plugin-card-top">
     317                        <h3>{{data.pluginDetailsIntro}}</h3>
     318                        <div class="desc">
     319                                <h4>{{data.name}} {{data.version}}</h4>
     320                                <p class="description">{{{data.description}}}</p>
     321                        </div>
     322                </div>
     323                <input type="hidden" name="overwrite" value="{{data.id}}"/>
     324        </div>
     325</script>
    313326<?php
    314327}
    315328
  • src/wp-admin/js/common.js

    diff --git src/wp-admin/js/common.js src/wp-admin/js/common.js
    index e57af5d682..e40015c8a5 100644
    $document.ready( function() { 
    687687                        e.target.scrollIntoView(false);
    688688        });
    689689
    690         // Disable upload buttons until files are selected
    691         (function(){
    692                 var button, input, form = $('form.wp-upload-form');
    693                 if ( ! form.length )
    694                         return;
    695                 button = form.find('input[type="submit"]');
    696                 input = form.find('input[type="file"]');
    697 
    698                 function toggleUploadButton() {
    699                         button.prop('disabled', '' === input.map( function() {
    700                                 return $(this).val();
    701                         }).get().join(''));
    702                 }
    703                 toggleUploadButton();
    704                 input.on('change', toggleUploadButton);
    705         })();
    706 
    707690        function pinMenu( event ) {
    708691                var windowPos = $window.scrollTop(),
    709692                        resizing = ! event || event.type !== 'scroll';
  • src/wp-admin/js/plugin-install.js

    diff --git src/wp-admin/js/plugin-install.js src/wp-admin/js/plugin-install.js
    index 9fa218fe34..9faade166a 100644
    jQuery( document ).ready( function( $ ) { 
    1212                $firstTabbable,
    1313                $lastTabbable,
    1414                $focusedBefore = $(),
    15                 $uploadViewToggle = $( '.upload-view-toggle' ),
    16                 $wrap = $ ( '.wrap' ),
    1715                $body = $( document.body );
    1816
    1917        tb_position = function() {
    jQuery( document ).ready( function( $ ) { 
    191189                $( '#section-holder div.section' ).hide(); // Hide 'em all.
    192190                $( '#section-' + tab ).show();
    193191        });
    194 
    195         /*
    196          * When a user presses the "Upload Plugin" button, show the upload form in place
    197          * rather than sending them to the devoted upload plugin page.
    198          * The `?tab=upload` page still exists for no-js support and for plugins that
    199          * might access it directly. When we're in this page, let the link behave
    200          * like a link. Otherwise we're in the normal plugin installer pages and the
    201          * link should behave like a toggle button.
    202          */
    203         if ( ! $wrap.hasClass( 'plugin-install-tab-upload' ) ) {
    204                 $uploadViewToggle
    205                         .attr({
    206                                 role: 'button',
    207                                 'aria-expanded': 'false'
    208                         })
    209                         .on( 'click', function( event ) {
    210                                 event.preventDefault();
    211                                 $body.toggleClass( 'show-upload-view' );
    212                                 $uploadViewToggle.attr( 'aria-expanded', $body.hasClass( 'show-upload-view' ) );
    213                         });
    214         }
    215192});
  • src/wp-admin/js/plugin-upload.js

    diff --git src/wp-admin/js/plugin-upload.js src/wp-admin/js/plugin-upload.js
    index e69de29bb2..54656dd110 100644
     
     1window.wp = window.wp || {};
     2
     3( function( $, _, wp ){
     4
     5        window.toggleUploadButton = function() {
     6                var button, input, form = $('form.wp-upload-form');
     7
     8                if ( ! form.length ) {
     9                        return;
     10                }
     11
     12                button = form.find('input[type="submit"]');
     13                input  = form.find('input[type="file"]');
     14
     15                button.prop('disabled', '' === input.map( function() {
     16                        return $(this).val();
     17                } ).get().join( '' ) );
     18        }
     19        toggleUploadButton();
     20
     21        $( 'form.wp-upload-form' ).on( 'change', 'input[type="file"]', function( event ) {
     22                if ( ! event.currentTarget.files || 'undefined' === typeof pluginUploadL10n ) {
     23                        toggleUploadButton();
     24                        return event;
     25                }
     26
     27                var file     = _.first( event.currentTarget.files ), form = $( event.delegateTarget )
     28                    template = wp.template( 'existing-plugin-data' ), pluginData = {};
     29
     30                if ( file.name ) {
     31                        // Checks if there is an existing plugin.
     32                        wp.apiRequest( {
     33                                path: 'wp/v2/plugins/' + file.name.replace( '.zip', '' ),
     34                                type: 'GET',
     35                                dataType: 'json'
     36                        } ).done( function( response ) {
     37                                // The plugin exists, display a warning and informations about the Plugin.
     38                                if ( response && response.id ) {
     39                                        form.addClass( 'has-existing-plugin' );
     40                                        window.toggleUploadButton();
     41
     42                                        $( '#install-plugin-submit' ).after(
     43                                                $( '<input></input' )
     44                                                        .prop( 'type', 'reset' )
     45                                                        .val( pluginUploadL10n.cancel )
     46                                                        .addClass( 'button button-secondary install-plugin-reset' )
     47                                        );
     48
     49                                        pluginData = _.extend( response, _.pick( pluginUploadL10n, [ 'warnOverwrite','pluginDetailsIntro' ] ) );
     50
     51                                        $( '#pluginzip' ).after( template( pluginData ) );
     52                                }
     53
     54                        // The plugin is not installed yet.
     55                        } ).fail( function() {
     56                                window.toggleUploadButton();
     57                        } );
     58                }
     59        } );
     60
     61        // Resets the Upload form when 'Cancel' is clicked.
     62        $( 'form.wp-upload-form' ).on( 'reset', function( event ) {
     63                $( '#existing-plugin-info' ).remove();
     64                $( event.currentTarget ).removeClass( 'has-existing-plugin' );
     65                $( event.currentTarget ).find( 'input[type="file"]' ).val( '' );
     66                $( event.currentTarget ).find( '.install-plugin-reset' ).remove();
     67                window.toggleUploadButton();
     68        } );
     69
     70        /*
     71         * When a user presses the "Upload Plugin" button, show the upload form in place
     72         * rather than sending them to the devoted upload plugin page.
     73         * The `?tab=upload` page still exists for no-js support and for plugins that
     74         * might access it directly. When we're in this page, let the link behave
     75         * like a link. Otherwise we're in the normal plugin installer pages and the
     76         * link should behave like a toggle button.
     77         */
     78        if ( ! $( '.wrap' ).hasClass( 'plugin-install-tab-upload' ) ) {
     79                $( '.upload-view-toggle' )
     80                        .attr({
     81                                role: 'button',
     82                                'aria-expanded': 'false'
     83                        } )
     84                        .on( 'click', function( event ) {
     85                                var $body = $( document.body ), $uploadViewToggle = $( event.currentTarget );
     86                                event.preventDefault();
     87                                $body.toggleClass( 'show-upload-view' );
     88                                $uploadViewToggle.attr( 'aria-expanded', $body.hasClass( 'show-upload-view' ) );
     89                        } );
     90        }
     91
     92}( jQuery, _, window.wp ) );
  • src/wp-admin/plugin-install.php

    diff --git src/wp-admin/plugin-install.php src/wp-admin/plugin-install.php
    index 855beb613d..e59e266316 100644
    if ( 'plugin-information' != $tab ) 
    5555$body_id = $tab;
    5656
    5757wp_enqueue_script( 'updates' );
     58wp_enqueue_script( 'plugin-upload' );
    5859
    5960/**
    6061 * Fires before each tab on the Install Plugins screen is loaded.
  • src/wp-admin/update.php

    diff --git src/wp-admin/update.php src/wp-admin/update.php
    index 12b8f565e2..0573a3aec0 100644
    if ( isset($_GET['action']) ) { 
    159159                $type = 'upload'; //Install plugin type, From Web or an Upload.
    160160
    161161                $upgrader = new Plugin_Upgrader( new Plugin_Installer_Skin( compact('type', 'title', 'nonce', 'url') ) );
    162                 $result = $upgrader->install( $file_upload->package );
     162
     163                // Overwrite an existing plugin.
     164                if ( isset( $_POST['overwrite'] ) && $_POST['overwrite'] ) {
     165                        $result = $upgrader->upgrade( wp_unslash( $_POST['overwrite'] ), array( 'package' => $file_upload->package ) );
     166
     167                // Install a new plugin.
     168                } else {
     169                        $result = $upgrader->install( $file_upload->package );
     170                }
    163171
    164172                if ( $result || is_wp_error($result) )
    165173                        $file_upload->cleanup();
  • src/wp-includes/rest-api.php

    diff --git src/wp-includes/rest-api.php src/wp-includes/rest-api.php
    index 697a7cc64b..656362beb3 100644
    function create_initial_rest_routes() { 
    233233        // Settings.
    234234        $controller = new WP_REST_Settings_Controller;
    235235        $controller->register_routes();
     236
     237        // Plugins.
     238        $controller = new WP_REST_Plugins_Controller;
     239        $controller->register_routes();
    236240}
    237241
    238242/**
  • src/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php

    diff --git src/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php src/wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php
    index e69de29bb2..183b8becf5 100644
     
     1<?php
     2/**
     3 * REST API: WP_REST_Plugins_Controller class
     4 *
     5 * @package WordPress
     6 * @subpackage REST_API
     7 * @since 5.0.0
     8 */
     9
     10/**
     11 * Core class used to get a site's plugins via the REST API.
     12 *
     13 * @since 5.0.0
     14 *
     15 * @see WP_REST_Controller
     16 */
     17class WP_REST_Plugins_Controller extends WP_REST_Controller {
     18        /**
     19         * List of installed plugins.
     20         *
     21         * @var array
     22         */
     23        protected $plugins;
     24
     25        /**
     26         * Constructor.
     27         *
     28         * @since 5.0.0
     29         */
     30        public function __construct() {
     31                $this->namespace = 'wp/v2';
     32                $this->rest_base = 'plugins';
     33        }
     34
     35        /**
     36         * Registers the routes for the objects of the controller.
     37         *
     38         * @since 5.0.0
     39         *
     40         * @see register_rest_route()
     41         */
     42        public function register_routes() {
     43
     44                register_rest_route( $this->namespace, '/' . $this->rest_base, array(
     45                        array(
     46                                'methods'             => WP_REST_Server::READABLE,
     47                                'callback'            => array( $this, 'get_items' ),
     48                                'args'                => array(),
     49                                'permission_callback' => array( $this, 'get_items_permissions_check' ),
     50                        ),
     51                        'schema' => array( $this, 'get_public_item_schema' ),
     52                ) );
     53
     54                register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<slug>[\w-]+)', array(
     55                        'args' => array(
     56                                'slug' => array(
     57                                        'description' => __( 'An alphanumeric identifier for the slug of the plugin.' ),
     58                                        'type'        => 'string',
     59                                ),
     60                        ),
     61                        array(
     62                                'methods'             => WP_REST_Server::READABLE,
     63                                'callback'            => array( $this, 'get_item' ),
     64                                'permission_callback' => array( $this, 'get_item_permissions_check' ),
     65                                'args'                => array(
     66                                        'context' => $this->get_context_param( array( 'default' => 'view' ) ),
     67                                ),
     68                        ),
     69                        'schema' => array( $this, 'get_public_item_schema' ),
     70                ) );
     71        }
     72
     73        /**
     74         * Checks if a given request has access to read plugins.
     75         *
     76         * @since 5.0.0
     77         *
     78         * @param WP_REST_Request $request Full details about the request.
     79         * @return bool True if the request has read access for the item, otherwise false.
     80         */
     81        public function get_items_permissions_check( $request ) {
     82                return current_user_can( 'update_plugins' );
     83        }
     84
     85        /**
     86         * Retrieves the collection of plugins.
     87         *
     88         * @since 5.0.0
     89         *
     90         * @param WP_REST_Request $request Full details about the request.
     91         * @return WP_REST_Response Response object.
     92         */
     93        public function get_items( $request ) {
     94                $plugins  = $this->get_installed_plugins();
     95                $response = array();
     96
     97                foreach ( $plugins as $id => $plugin ) {
     98                        $response[]  = $this->prepare_item_for_response( $plugin, $request );
     99                }
     100
     101                return rest_ensure_response( $response );
     102        }
     103
     104        /**
     105         * Checks if a given request has access to read the plugin.
     106         *
     107         * @since 5.0.0
     108         *
     109         * @param WP_REST_Request $request Full details about the request.
     110         * @return bool True if the request has read access for the item, otherwise false.
     111         */
     112        public function get_item_permissions_check( $request ) {
     113                return current_user_can( 'update_plugins' );
     114        }
     115
     116        /**
     117         * Retrieves the requested plugin.
     118         *
     119         * @since 5.0.0
     120         *
     121         * @param WP_REST_Request $request Full details about the request.
     122         * @return array|WP_Error Array on success, or WP_Error object on failure.
     123         * @return WP_REST_Response Response object.
     124         */
     125        public function get_item( $request ) {
     126                $plugins = $this->get_installed_plugins();
     127                $error   = new WP_Error( 'rest_plugin_not_found', __( 'The plugin is not installed' ), array( 'status' => 404 ) );
     128
     129                $slug = $request->get_param( 'slug' );
     130
     131                if ( ! $slug ) {
     132                        return $error;
     133                }
     134
     135                $plugin = wp_list_filter( $plugins, array( 'slug' => $slug ) );
     136
     137                if ( ! $plugin ) {
     138                        return $error;
     139                }
     140
     141                $response = $this->prepare_item_for_response( $plugin, $request );
     142
     143                return rest_ensure_response( $response );
     144        }
     145
     146        /**
     147         * Prepares a single plugin output for response.
     148         *
     149         * @since 5.0.0
     150         *
     151         * @param array           $plugin  Array of Plugin header tags.
     152         * @param WP_REST_Request $request Request object.
     153         * @return array Array of plugin header tags.
     154         */
     155        public function prepare_item_for_response( $plugin, $request ) {
     156                if ( ! is_array( $plugin ) ) {
     157                        return array();
     158                }
     159
     160                if ( 'id' !== key( $plugin ) ) {
     161                        $plugin = reset( $plugin );
     162                }
     163
     164                $schema     = $this->get_item_schema();
     165                $properties = wp_list_filter( $schema['properties'], array( 'type' => 'string' ) );
     166                $data       = array();
     167                $attributes = array();
     168
     169                // Set plugin data to translate.
     170                foreach ( $properties as $property => $params ) {
     171                        if ( 'id' === $property || 'slug' === $property ) {
     172                                continue;
     173                        }
     174
     175                        $data[ $params['description'] ] = $plugin[ $property ];
     176                        $attributes[ $property ]        = $params['description'];
     177                }
     178
     179                $data = _get_plugin_data_markup_translate( $plugin['id'], $data, false, true );
     180                $data = array_map( 'strip_tags', $data );
     181
     182                // Update plugin with translated attributes
     183                foreach ( $attributes as $p => $t ) {
     184                        if ( ! isset( $data[ $t ] ) ) {
     185                                continue;
     186                        }
     187
     188                        $plugin[ $p ] = $data[ $t ];
     189                }
     190
     191                $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
     192                $plugin = $this->filter_response_by_context( $plugin, $context );
     193
     194                return $plugin;
     195        }
     196
     197        /**
     198         * Retrieves all of the installed plugins or the Rest additionnal shema.
     199         *
     200         * @since 5.0.0
     201         *
     202         * @param  boolean $schema True to get the additionnal schema.
     203         * @return array Array of registered options.
     204         */
     205        protected function get_installed_plugins( $schema = false ) {
     206                $rest_plugins = array();
     207
     208                if ( ! $this->plugins ) {
     209                        require_once ABSPATH . 'wp-admin/includes/plugin.php';
     210
     211                        $this->plugins = get_plugins();
     212                }
     213
     214                foreach ( $this->plugins as $id => $args ) {
     215                        if ( $schema ) {
     216                                $rest_args = array();
     217
     218                                $default_schema = array(
     219                                        'type'        => 'string',
     220                                        'description' => '',
     221                                        'context'     => array( 'view', 'edit', 'embed' ),
     222                                );
     223
     224                        } else {
     225                                $rest_args = array( 'id' => $id );
     226                                $slug      = dirname( $id );
     227
     228                                if ( ! $slug ) {
     229                                        continue;
     230                                }
     231
     232                                if ( '.' === $slug ) {
     233                                        $slug = wp_basename( $id, '.php' );
     234                                }
     235
     236                                $rest_args['slug'] = $slug;
     237                        }
     238
     239                        $header_keys = array_keys( $args );
     240
     241                        foreach ( array_map( 'sanitize_key', $header_keys ) as $tag_id => $header_tag ) {
     242                                if ( $schema && ! isset( $rest_plugins[ $header_tag ] ) ) {
     243                                        $rest_args['id']     = $header_tag;
     244                                        $rest_args['schema'] = wp_parse_args( array(
     245                                                'description' => $header_keys[ $tag_id ],
     246                                                'type'        => is_bool( $args[ $header_keys[ $tag_id ] ] ) ? 'boolean' : 'string',
     247                                        ), $default_schema );
     248
     249                                        $rest_plugins[ $rest_args['id'] ] = $rest_args;
     250
     251                                } else {
     252                                        $rest_args[ $header_tag ] = $args[ $header_keys[ $tag_id ] ];
     253                                }
     254                        }
     255
     256                        if ( ! $schema ) {
     257                                $rest_plugins[ $rest_args['id'] ] = $rest_args;
     258                        }
     259                }
     260
     261                return $rest_plugins;
     262        }
     263
     264        /**
     265         * Retrieves the plugins schema, conforming to JSON Schema.
     266         *
     267         * @since 5.0.0
     268         *
     269         * @return array Item schema data.
     270         */
     271        public function get_item_schema() {
     272                $plugins = $this->get_installed_plugins( true );
     273
     274                $schema = array(
     275                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
     276                        'title'      => 'plugins',
     277                        'type'       => 'object',
     278                        'properties' => array(
     279                                'id'            => array(
     280                                        'description' => __( 'An alphanumeric identifier for the object unique to its directory and main php file.' ),
     281                                        'type'        => 'string',
     282                                        'context'     => array( 'view', 'edit', 'embed' ),
     283                                ),
     284                                'slug'            => array(
     285                                        'description' => __( 'An alphanumeric identifier for the object unique to its slug.' ),
     286                                        'type'        => 'string',
     287                                        'context'     => array( 'view', 'edit', 'embed' ),
     288                                ),
     289                        ),
     290                );
     291
     292                foreach ( $plugins as $property_name => $property ) {
     293                        $schema['properties'][ $property_name ] = $property['schema'];
     294                }
     295
     296                return $this->add_additional_fields_schema( $schema );
     297        }
     298}
  • src/wp-includes/script-loader.php

    diff --git src/wp-includes/script-loader.php src/wp-includes/script-loader.php
    index e6a9ef1e06..22941b31df 100644
    function wp_default_scripts( &$scripts ) { 
    832832                        ),
    833833                ) );
    834834
     835                $scripts->add( 'plugin-upload', "/wp-admin/js/plugin-upload$suffix.js", array( 'wp-util', 'wp-api-request' ), false, 1 );
     836                did_action( 'init' ) && $scripts->localize( 'plugin-upload', 'pluginUploadL10n', array(
     837                        'warnOverwrite'      => __( "You are about to replace an existing plugin.\nThis action cannot be undone.\nPress 'Cancel' to stop or 'Install' to confirm." ),
     838                        'cancel'             => __( 'Cancel' ),
     839                        'pluginDetailsIntro' => __( 'About the existing plugin:' ),
     840                ) );
     841
    835842                $scripts->add( 'farbtastic', '/wp-admin/js/farbtastic.js', array('jquery'), '1.2' );
    836843
    837844                $scripts->add( 'iris', '/wp-admin/js/iris.min.js', array( 'jquery-ui-draggable', 'jquery-ui-slider', 'jquery-touch-punch' ), '1.0.7', 1 );
  • src/wp-settings.php

    diff --git src/wp-settings.php src/wp-settings.php
    index bacf4cfddd..73c9cbcee5 100644
    require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-terms-controller.p 
    234234require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-users-controller.php' );
    235235require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-comments-controller.php' );
    236236require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-settings-controller.php' );
     237require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-plugins-controller.php' );
    237238require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-meta-fields.php' );
    238239require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-comment-meta-fields.php' );
    239240require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-post-meta-fields.php' );