WordPress.org

Make WordPress Core

Ticket #50052: 50052.2.diff

File 50052.2.diff, 65.7 KB (added by pbiron, 10 months ago)

refreshes against trunk and adds a few changes from the forthcoming version 0.8.1 of the feature plugin

  • src/js/_enqueues/wp/updates.js

    From 012f3b1aef4caed57276951769de09ca6390827c Mon Sep 17 00:00:00 2001
    From: Paul Biron <paul@sparrowhawkcomputing.com>
    Date: Mon, 18 May 2020 11:05:50 -0600
    Subject: [PATCH] Refresh against trunk and add a few changes that will be in
     version 0.8.1 of the feature plugin.
    
    ---
     src/js/_enqueues/wp/updates.js                | 156 ++++++++++++++
     src/wp-admin/admin-ajax.php                   |   1 +
     src/wp-admin/css/common.css                   |   4 +-
     src/wp-admin/css/list-tables.css              |   4 +
     src/wp-admin/css/themes.css                   |   3 +-
     src/wp-admin/includes/ajax-actions.php        |  73 +++++++
     .../includes/class-wp-automatic-updater.php   | 192 ++++++++++++++++++
     src/wp-admin/includes/class-wp-debug-data.php |  60 +++++-
     .../class-wp-ms-themes-list-table.php         | 117 ++++++++++-
     .../includes/class-wp-plugins-list-table.php  | 106 +++++++++-
     src/wp-admin/includes/theme.php               |  15 +-
     src/wp-admin/includes/update.php              |  80 +++++++-
     src/wp-admin/network/themes.php               | 116 ++++++++++-
     src/wp-admin/plugins.php                      | 136 ++++++++++++-
     src/wp-admin/themes.php                       |  71 +++++++
     src/wp-admin/update-core.php                  |  20 ++
     src/wp-includes/script-loader.php             |   7 +
     17 files changed, 1145 insertions(+), 16 deletions(-)
    
    diff --git a/src/js/_enqueues/wp/updates.js b/src/js/_enqueues/wp/updates.js
    index d7e0de1c39..1ab81b3fc3 100644
    a b  
    409409         *
    410410         * @since 4.2.0
    411411         * @since 4.6.0 More accurately named `updatePluginSuccess`.
     412         * @since 5.5.0 Auto-update "time to next update" text cleared.
    412413         *
    413414         * @param {object} response            Response from the server.
    414415         * @param {string} response.slug       Slug of the plugin to be updated.
     
    431432                        // Update the version number in the row.
    432433                        newText = $pluginRow.find( '.plugin-version-author-uri' ).html().replace( response.oldVersion, response.newVersion );
    433434                        $pluginRow.find( '.plugin-version-author-uri' ).html( newText );
     435
     436                        // Clear the "time to next auto-update" text.
     437                        $pluginRow.find( '.auto-update-time' ).empty();
    434438                } else if ( 'plugin-install' === pagenow || 'plugin-install-network' === pagenow ) {
    435439                        $updateMessage = $( '.plugin-card-' + response.slug ).find( '.update-now' )
    436440                                .removeClass( 'updating-message' )
     
    969973         * Updates the UI appropriately after a successful theme update.
    970974         *
    971975         * @since 4.6.0
     976         * @since 5.5.0 Auto-update "time to next update" text cleared.
    972977         *
    973978         * @param {object} response
    974979         * @param {string} response.slug       Slug of the theme to be updated.
     
    10021007                        // Update the version number in the row.
    10031008                        newText = $theme.find( '.theme-version-author-uri' ).html().replace( response.oldVersion, response.newVersion );
    10041009                        $theme.find( '.theme-version-author-uri' ).html( newText );
     1010
     1011                        // Clear the "time to next auto-update" text.
     1012                        $theme.find( '.auto-update-time' ).empty();
    10051013                } else {
    10061014                        $notice = $( '.theme-info .notice' ).add( $theme.find( '.update-message' ) );
    10071015
    10081016                        // Focus on Customize button after updating.
    10091017                        if ( isModalOpen ) {
    10101018                                $( '.load-customize:visible' ).focus();
     1019                                $( '.theme-info .theme-autoupdate' ).find( '.auto-update-time' ).empty();
    10111020                        } else {
    10121021                                $theme.find( '.load-customize' ).focus();
    10131022                        }
     
    24622471                 */
    24632472                $( window ).on( 'beforeunload', wp.updates.beforeunload );
    24642473        } );
     2474
     2475        /**
     2476         * Click handler for enabling and disabling plugin and theme auto-updates.
     2477         *
     2478         * @since 5.5.0
     2479         *
     2480         * TODO: this needs to be refactored to be like the remainder of the handlers
     2481         *       in this file.
     2482         */
     2483        $( document ).ready( function() {
     2484                $( $document ).on( 'click', '.column-auto-updates a.toggle-auto-update, .theme-overlay a.toggle-auto-update', function( event ) {
     2485                        var data, asset, type, $parent;
     2486                        var $anchor = $( this ),
     2487                                action = $anchor.attr( 'data-wp-action' ),
     2488                                $label = $anchor.find( '.label' );
     2489
     2490                        if ( 'themes' !== pagenow ) {
     2491                                $parent = $anchor.closest( '.column-auto-updates' );
     2492                        } else {
     2493                                $parent = $anchor.closest( '.theme-autoupdate' );
     2494                        }
     2495
     2496                        event.preventDefault();
     2497
     2498                        // Prevent multiple simultaneous requests.
     2499                        if ( $anchor.attr( 'data-doing-ajax' ) === 'yes' ) {
     2500                                return;
     2501                        }
     2502
     2503                        $anchor.attr( 'data-doing-ajax', 'yes' );
     2504
     2505                        switch ( pagenow ) {
     2506                                case 'plugins':
     2507                                case 'plugins-network':
     2508                                        type = 'plugin';
     2509                                        asset = $anchor.closest( 'tr' ).attr( 'data-plugin' );
     2510                                        break;
     2511                                case 'themes-network':
     2512                                        type = 'theme';
     2513                                        asset = $anchor.closest( 'tr' ).attr( 'data-slug' );
     2514                                        break;
     2515                                case 'themes':
     2516                                        type = 'theme';
     2517                                        asset = $anchor.attr( 'data-slug' );
     2518                                        break;
     2519                        }
     2520
     2521                        // Clear any previous errors.
     2522                        $parent.find( '.notice.error' ).addClass( 'hidden' );
     2523
     2524                        // Show loading status.
     2525                        // TODO: make it readable when network is fast, or possibly remove the interim text change.
     2526                        if ( 'enable' === action ) {
     2527                                $label.text( wp.updates.l10n.autoUpdatesEnabling );
     2528                        } else {
     2529                                $label.text( wp.updates.l10n.autoUpdatesDisabling );
     2530                        }
     2531
     2532                        // TODO: Needs design review - the link text jumps under the mouse (part may get selected).
     2533                        $anchor.find( '.dashicons-update' ).removeClass( 'hidden' );
     2534
     2535                        data = {
     2536                                action: 'toggle-auto-updates',
     2537                                _ajax_nonce: settings.ajax_nonce,
     2538                                state: action,
     2539                                type: type,
     2540                                asset: asset,
     2541                        };
     2542
     2543                        $.post( window.ajaxurl, data )
     2544                                .done( function( response ) {
     2545                                        var $enabled, $disabled, enabledNumber, disabledNumber, errorMessage;
     2546                                        var href = $anchor.attr( 'href' );
     2547
     2548                                        if ( ! response.success ) {
     2549                                                // if WP returns 0 for response (which can happen in a few cases
     2550                                                // that aren't quite failures), output the general error message,
     2551                                                // since we won't have response.data.error.
     2552                                                if ( response.data && response.data.error ) {
     2553                                                        errorMessage = response.data.error;
     2554                                                } else {
     2555                                                        errorMessage = wp.updates.l10n.autoUpdatesError;
     2556                                                }
     2557
     2558                                                $parent.find( '.notice.error' ).removeClass( 'hidden' ).find( 'p' ).text( errorMessage );
     2559                                                wp.a11y.speak( errorMessage, 'polite' );
     2560                                                return;
     2561                                        }
     2562
     2563                                        // Update the counts in the enabled/disabled views if on a screen
     2564                                        // with a list table.
     2565                                        if ( 'themes' !== pagenow ) {
     2566                                                $enabled       = $( '.auto-update-enabled span' );
     2567                                                $disabled      = $( '.auto-update-disabled span' );
     2568                                                enabledNumber  = parseInt( $enabled.text().replace( /[^\d]+/g, '' ) ) || 0;
     2569                                                disabledNumber = parseInt( $disabled.text().replace( /[^\d]+/g, '' ) ) || 0;
     2570
     2571                                                switch ( action ) {
     2572                                                        case 'enable':
     2573                                                                ++enabledNumber;
     2574                                                                --disabledNumber;
     2575                                                                break;
     2576                                                        case 'disable':
     2577                                                                --enabledNumber;
     2578                                                                ++disabledNumber;
     2579                                                                break;
     2580                                                }
     2581
     2582                                                enabledNumber = Math.max( 0, enabledNumber );
     2583                                                disabledNumber = Math.max( 0, disabledNumber );
     2584
     2585                                                $enabled.text( '(' + enabledNumber + ')' );
     2586                                                $disabled.text( '(' + disabledNumber + ')' );
     2587                                        }
     2588
     2589                                        if ( 'enable' === action ) {
     2590                                                href = href.replace( 'action=enable-auto-update', 'action=disable-auto-update' );
     2591                                                $anchor.attr( {
     2592                                                        'data-wp-action': 'disable',
     2593                                                        href: href,
     2594                                                } );
     2595
     2596                                                $label.text( wp.updates.l10n.autoUpdatesDisable );
     2597                                                $parent.find( '.auto-update-time' ).removeClass( 'hidden' );
     2598                                                wp.a11y.speak( wp.updates.l10n.autoUpdatesEnabled, 'polite' );
     2599                                        } else {
     2600                                                href = href.replace( 'action=disable-auto-update', 'action=enable-auto-update' );
     2601                                                $anchor.attr( {
     2602                                                        'data-wp-action': 'enable',
     2603                                                        href: href,
     2604                                                } );
     2605
     2606                                                $label.text( wp.updates.l10n.autoUpdatesEnable );
     2607                                                $parent.find( '.auto-update-time' ).addClass( 'hidden' );
     2608                                                wp.a11y.speak( wp.updates.l10n.autoUpdatesDisabled, 'polite' );
     2609                                        }
     2610                                } )
     2611                                .fail( function() {
     2612                                        $parent.find( '.notice.error' ).removeClass( 'hidden' ).find( 'p' ).text( wp.updates.l10n.autoUpdatesError );
     2613                                        wp.a11y.speak( wp.updates.l10n.autoUpdatesError, 'polite' );
     2614                                } )
     2615                                .always( function() {
     2616                                        $anchor.removeAttr( 'data-doing-ajax' ).find( '.dashicons-update' ).addClass( 'hidden' );
     2617                                } );
     2618                        }
     2619                );
     2620        } );
    24652621})( jQuery, window.wp, window._wpUpdatesSettings );
  • src/wp-admin/admin-ajax.php

    diff --git a/src/wp-admin/admin-ajax.php b/src/wp-admin/admin-ajax.php
    index 4f41a31bd6..28caf3d227 100644
    a b $core_actions_post = array( 
    139139        'health-check-background-updates',
    140140        'health-check-loopback-requests',
    141141        'health-check-get-sizes',
     142        'toggle-auto-updates',
    142143);
    143144
    144145// Deprecated.
  • src/wp-admin/css/common.css

    diff --git a/src/wp-admin/css/common.css b/src/wp-admin/css/common.css
    index 3d0dacf277..fbf69fe767 100644
    a b div.error { 
    15241524.updating-message p:before,
    15251525.import-php .updating-message:before,
    15261526.button.updating-message:before,
    1527 .button.installing:before {
     1527.button.installing:before,
     1528.plugins .column-auto-updates .dashicons-update.spin,
     1529.theme-overlay .theme-autoupdate .dashicons-update.spin {
    15281530        animation: rotation 2s infinite linear;
    15291531}
    15301532
  • src/wp-admin/css/list-tables.css

    diff --git a/src/wp-admin/css/list-tables.css b/src/wp-admin/css/list-tables.css
    index f621aef2ab..9aaaefebe0 100644
    a b ul.cat-checklist { 
    12361236        width: 85px;
    12371237}
    12381238
     1239.plugins .column-auto-updates {
     1240        width: 14.2em;
     1241}
     1242
    12391243.plugins .inactive .plugin-title strong {
    12401244        font-weight: 400;
    12411245}
  • src/wp-admin/css/themes.css

    diff --git a/src/wp-admin/css/themes.css b/src/wp-admin/css/themes.css
    index a5ef219f77..7d87a06aeb 100644
    a b body.folded .theme-browser ~ .theme-overlay .theme-wrap { 
    679679        line-height: inherit;
    680680}
    681681
    682 .theme-overlay .theme-author a {
     682.theme-overlay .theme-author a,
     683.theme-overlay .theme-autoupdate a {
    683684        text-decoration: none;
    684685}
    685686
  • src/wp-admin/includes/ajax-actions.php

    diff --git a/src/wp-admin/includes/ajax-actions.php b/src/wp-admin/includes/ajax-actions.php
    index 5b740af44c..9b74b9f4ba 100644
    a b function wp_ajax_delete_plugin() { 
    45674567function wp_ajax_search_plugins() {
    45684568        check_ajax_referer( 'updates' );
    45694569
     4570        // Ensure after_plugin_row_{$plugin_file} gets hooked.
     4571        wp_plugin_update_rows();
     4572
    45704573        $pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
    45714574        if ( 'plugins-network' === $pagenow || 'plugins' === $pagenow ) {
    45724575                set_current_screen( $pagenow );
    function wp_ajax_health_check_get_sizes() { 
    52675270function wp_ajax_rest_nonce() {
    52685271        exit( wp_create_nonce( 'wp_rest' ) );
    52695272}
     5273
     5274/**
     5275 * Ajax handler to enable or disable plugin and theme auto-updates.
     5276 *
     5277 * @since 5.5.0
     5278 */
     5279function wp_ajax_toggle_auto_updates() {
     5280        check_ajax_referer( 'updates' );
     5281
     5282        if ( empty( $_POST['type'] ) || empty( $_POST['asset'] ) || empty( $_POST['state'] ) ) {
     5283                wp_send_json_error( array( 'error' => __( 'Invalid data. No selected item.' ) ) );
     5284        }
     5285
     5286        $asset = sanitize_text_field( urldecode( $_POST['asset'] ) );
     5287
     5288        if ( 'enable' !== $_POST['state'] && 'disable' !== $_POST['state'] ) {
     5289                wp_send_json_error( array( 'error' => __( 'Invalid data. Unknown state.' ) ) );
     5290        }
     5291        $state = $_POST['state'];
     5292
     5293        if ( 'plugin' !== $_POST['type'] && 'theme' !== $_POST['type'] ) {
     5294                wp_send_json_error( array( 'error' => __( 'Invalid data. Unknown type.' ) ) );
     5295        }
     5296        $type = $_POST['type'];
     5297
     5298        switch ( $type ) {
     5299                case 'plugin':
     5300                        if ( ! current_user_can( 'update_plugins' ) ) {
     5301                                $error_message = __( 'You do not have permission to modify plugins.' );
     5302                                wp_send_json_error( array( 'error' => $error_message ) );
     5303                        }
     5304
     5305                        $option = 'auto_update_plugins';
     5306                        /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
     5307                        $all_items = apply_filters( 'all_plugins', get_plugins() );
     5308                        break;
     5309                case 'theme':
     5310                        if ( ! current_user_can( 'update_themes' ) ) {
     5311                                $error_message = __( 'You do not have permission to modify themes.' );
     5312                                wp_send_json_error( array( 'error' => $error_message ) );
     5313                        }
     5314
     5315                        $option    = 'auto_update_themes';
     5316                        $all_items = wp_get_themes();
     5317                        break;
     5318                default:
     5319                        wp_send_json_error( array( 'error' => __( 'Invalid data. Unknown type.' ) ) );
     5320        }
     5321
     5322        if ( ! array_key_exists( $asset, $all_items ) ) {
     5323                $error_message = __( 'Invalid data. The item does not exist.' );
     5324                wp_send_json_error( array( 'error' => $error_message ) );
     5325        }
     5326
     5327        $auto_updates = (array) get_site_option( $option, array() );
     5328
     5329        if ( 'disable' === $state ) {
     5330                $auto_updates = array_diff( $auto_updates, array( $asset ) );
     5331        } else {
     5332                $auto_updates[] = $asset;
     5333                $auto_updates   = array_unique( $auto_updates );
     5334        }
     5335
     5336        // Remove items that have been deleted since the site option was last updated.
     5337        $auto_updates = array_intersect( $auto_updates, array_keys( $all_items ) );
     5338
     5339        update_site_option( $option, $auto_updates );
     5340
     5341        wp_send_json_success();
     5342}
  • src/wp-admin/includes/class-wp-automatic-updater.php

    diff --git a/src/wp-admin/includes/class-wp-automatic-updater.php b/src/wp-admin/includes/class-wp-automatic-updater.php
    index 3780512efa..4978230ad6 100644
    a b class WP_Automatic_Updater { 
    158158                // Next up, is this an item we can update?
    159159                if ( 'core' === $type ) {
    160160                        $update = Core_Upgrader::should_update_to_version( $item->current );
     161                } elseif ( 'plugin' === $type || 'theme' === $type ) {
     162                        $update = false;
     163                        if ( wp_is_auto_update_enabled_for_type( $type ) ) {
     164                                $auto_updates = (array) get_site_option( "auto_update_{$type}s", array() );
     165                                $update      = in_array( $item->{$type}, $auto_updates, true ) || ! empty( $item->autoupdate );
     166                        }
    161167                } else {
    162168                        $update = ! empty( $item->autoupdate );
    163169                }
    class WP_Automatic_Updater { 
    501507
    502508                        if ( ! empty( $this->update_results['core'] ) ) {
    503509                                $this->after_core_update( $this->update_results['core'][0] );
     510                        } elseif ( ! empty( $this->update_results['plugin'] ) || ! empty( $this->update_results['theme'] ) ) {
     511                                $this->after_plugin_theme_update( $this->update_results );
    504512                        }
    505513
    506514                        /**
    class WP_Automatic_Updater { 
    854862                wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
    855863        }
    856864
     865
     866        /**
     867         * If we tried to perform plugin or theme updates, check if we should send an email.
     868         *
     869         * @since 5.5.0
     870         *
     871         * @param object $results The result of updates tasks.
     872         */
     873        protected function after_plugin_theme_update( $update_results ) {
     874                $successful_updates = array();
     875                $failed_updates     = array();
     876
     877                /**
     878                 * Filters whether to send an email following an automatic background plugin update.
     879                 *
     880                 * @since 5.5.0
     881                 *
     882                 * @param bool $enabled True if plugins notifications are enabled, false otherwise.
     883                 */
     884                $notifications_enabled = apply_filters( 'auto_plugin_update_send_email', true );
     885
     886                if ( ! empty( $update_results['plugin'] ) && $notifications_enabled ) {
     887                        foreach ( $update_results['plugin'] as $update_result ) {
     888                                if ( true === $update_result->result ) {
     889                                        $successful_updates['plugin'][] = $update_result;
     890                                } else {
     891                                        $failed_updates['plugin'][] = $update_result;
     892                                }
     893                        }
     894                }
     895
     896                /**
     897                 * Filters whether to send an email following an automatic background theme update.
     898                 *
     899                 * @since 5.5.0
     900                 *
     901                 * @param bool $enabled True if notifications are enabled, false otherwise.
     902                 */
     903                $notifications_enabled = apply_filters( 'send_theme_auto_update_email', true );
     904
     905                if ( ! empty( $update_results['theme'] ) && $notifications_enabled ) {
     906                        foreach ( $update_results['theme'] as $update_result ) {
     907                                if ( true === $update_result->result ) {
     908                                        $successful_updates['theme'][] = $update_result;
     909                                } else {
     910                                        $failed_updates['theme'][] = $update_result;
     911                                }
     912                        }
     913                }
     914
     915                if ( empty( $successful_updates ) && empty( $failed_updates ) ) {
     916                        return;
     917                }
     918
     919                if ( empty( $failed_updates ) ) {
     920                        $this->send_plugin_theme_email( 'success', $successful_updates, $failed_updates );
     921                } elseif ( empty( $successful_updates ) ) {
     922                        $this->send_plugin_theme_email( 'fail', $successful_updates, $failed_updates );
     923                } else {
     924                        $this->send_plugin_theme_email( 'mixed', $successful_updates, $failed_updates );
     925                }
     926        }
     927
     928        /**
     929         * Sends an email upon the completion or failure of a plugin or theme background update.
     930         *
     931         * @since 5.5.0
     932         *
     933         * @param string $type               The type of email to send. Can be one of 'success', 'failure', 'mixed'.
     934         * @param array  $successful_updates A list of updates that succeeded.
     935         * @param array  $failed_updates     A list of updates that failed.
     936         */
     937        protected function send_plugin_theme_email( $type, $successful_updates, $failed_updates ) {
     938                // No updates were attempted.
     939                if ( empty( $successful_updates ) && empty( $failed_updates ) ) {
     940                        return;
     941                }
     942                $body = array();
     943
     944                switch ( $type ) {
     945                        case 'success':
     946                                /* translators: %s: Site title. */
     947                                $subject = __( '[%s] Some plugins or themes were automatically updated' );
     948                                break;
     949                        case 'fail':
     950                                /* translators: %s: Site title. */
     951                                $subject = __( '[%s] Some plugins or themes have failed to update' );
     952                                $body[]  = sprintf(
     953                                        /* translators: %s: Home URL. */
     954                                        __( 'Howdy! Failures occurred when attempting to update plugins/themes on your site at %s.' ),
     955                                        home_url()
     956                                );
     957                                $body[] = "\n";
     958                                $body[] = __( 'Please check out your site now. It’s possible that everything is working. If it says you need to update, you should do so.' );
     959                                break;
     960                        case 'mixed':
     961                                /* translators: %s: Site title. */
     962                                $subject = __( '[%s] Some plugins or themes were automatically updated' );
     963                                $body[] = sprintf(
     964                                        /* translators: %s: Home URL. */
     965                                        __( 'Howdy! Failures occurred when attempting to update plugins/themes on your site at %s.' ),
     966                                        home_url()
     967                                );
     968                                $body[] = "\n";
     969                                $body[] = __( 'Please check out your site now. It’s possible that everything is working. If it says you need to update, you should do so.' );
     970                                $body[] = "\n";
     971                                break;
     972                }
     973
     974                // Get failed plugin updates.
     975                if ( in_array( $type, array( 'fail', 'mixed' ), true ) && ! empty( $failed_updates['plugin'] ) ) {
     976                        $body[] = __( 'The following plugins failed to update:' );
     977                        // List failed updates.
     978                        foreach ( $failed_updates['plugin'] as $item ) {
     979                                /* translators: %s: Name of the related plugin. */
     980                                $body[] = ' ' . sprintf( __( '- %s' ), $item->name );
     981                        }
     982                        $body[] = "\n";
     983                }
     984                // Get failed theme updates.
     985                if ( in_array( $type, array( 'fail', 'mixed' ), true ) && ! empty( $failed_updates['theme'] ) ) {
     986                        $body[] = __( 'The following themes failed to update:' );
     987                        // List failed updates.
     988                        foreach ( $failed_updates['theme'] as $item ) {
     989                                /* translators: %s: Name of the related plugin. */
     990                                $body[] = ' ' . sprintf( __( '- %s' ), $item->name );
     991                        }
     992                        $body[] = "\n";
     993                }
     994                // Get successful plugin updates.
     995                if ( in_array( $type, array( 'success', 'mixed' ), true ) && ! empty( $successful_updates['plugin'] ) ) {
     996                        $body[] = __( 'The following plugins were successfully updated:' );
     997                        // List successful updates.
     998                        foreach ( $successful_updates['plugin'] as $item ) {
     999                                /* translators: %s: Name of the related plugin. */
     1000                                $body[] = ' ' . sprintf( __( '- %s' ), $item->name );
     1001                        }
     1002                        $body[] = "\n";
     1003                }
     1004                // Get successful theme updates.
     1005                if ( in_array( $type, array( 'success', 'mixed' ), true ) && ! empty( $successful_updates['theme'] ) ) {
     1006                        $body[] = __( 'The following themes were successfully updated:' );
     1007                        // List successful updates.
     1008                        foreach ( $successful_updates['theme'] as $item ) {
     1009                                /* translators: %s: Name of the related plugin. */
     1010                                $body[] = ' ' . sprintf( __( '- %s' ), $item->name );
     1011                        }
     1012                        $body[] = "\n";
     1013                }
     1014                $body[] = "\n";
     1015
     1016                // Add a note about the support forums.
     1017                $body[] = __( 'If you experience any issues or need support, the volunteers in the WordPress.org support forums may be able to help.' );
     1018                $body[] = __( 'https://wordpress.org/support/forums/' );
     1019                $body[] = "\n" . __( 'The WordPress Team' );
     1020
     1021                $body    = implode( "\n", $body );
     1022                $to      = get_site_option( 'admin_email' );
     1023                $subject = sprintf( $subject, wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ) );
     1024                $headers = '';
     1025
     1026                $email = compact( 'to', 'subject', 'body', 'headers' );
     1027
     1028                /**
     1029                 * Filters the email sent following an automatic background plugin update.
     1030                 *
     1031                 * @param array $email {
     1032                 *     Array of email arguments that will be passed to wp_mail().
     1033                 *
     1034                 *     @type string $to      The email recipient. An array of emails
     1035                 *                           can be returned, as handled by wp_mail().
     1036                 *     @type string $subject The email's subject.
     1037                 *     @type string $body    The email message body.
     1038                 *     @type string $headers Any email headers, defaults to no headers.
     1039                 * }
     1040                 * @param string $type               The type of email being sent. Can be one of
     1041                 *                                   'success', 'fail', 'mixed'.
     1042                 * @param object $successful_updates The updates that succeeded.
     1043                 * @param object $failed_updates     The updates that failed.
     1044                 */
     1045                $email = apply_filters( 'auto_plugin_theme_update_email', $email, $type, $successful_updates, $failed_updates );
     1046                wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] );
     1047        }
     1048
    8571049        /**
    8581050         * Prepares and sends an email of a full log of background update results, useful for debugging and geekery.
    8591051         *
  • src/wp-admin/includes/class-wp-debug-data.php

    diff --git a/src/wp-admin/includes/class-wp-debug-data.php b/src/wp-admin/includes/class-wp-debug-data.php
    index 476bf55d59..b9854839fb 100644
    a b class WP_Debug_Data { 
    856856                }
    857857
    858858                // List all available plugins.
    859                 $plugins        = get_plugins();
    860                 $plugin_updates = get_plugin_updates();
     859                $plugins              = get_plugins();
     860                $plugin_updates       = get_plugin_updates();
     861                $auto_updates         = array();
     862                $auto_updates_enabled = wp_is_auto_update_enabled_for_type( 'plugin' );
     863                if ( $auto_updates_enabled ) {
     864                        $auto_updates = (array) get_site_option( 'auto_update_plugins', array() );
     865                }
    861866
    862867                foreach ( $plugins as $plugin_path => $plugin ) {
    863868                        $plugin_part = ( is_plugin_active( $plugin_path ) ) ? 'wp-plugins-active' : 'wp-plugins-inactive';
    class WP_Debug_Data { 
    892897                                $plugin_version_string_debug .= sprintf( ' (latest version: %s)', $plugin_updates[ $plugin_path ]->update->new_version );
    893898                        }
    894899
     900                        if ( $auto_updates_enabled ) {
     901                                if ( in_array( $plugin_path, $auto_updates, true ) ) {
     902                                        $plugin_version_string       .= ' | ' . __( 'Auto-updates enabled' );
     903                                        $plugin_version_string_debug .= ', ' . __( 'Auto-updates enabled' );
     904                                } else {
     905                                        $plugin_version_string       .= ' | ' . __( 'Auto-updates disabled' );
     906                                        $plugin_version_string_debug .= ', ' . __( 'Auto-updates disabled' );
     907                                }
     908                        }
     909
    895910                        $info[ $plugin_part ]['fields'][ sanitize_text_field( $plugin['Name'] ) ] = array(
    896911                                'label' => $plugin['Name'],
    897912                                'value' => $plugin_version_string,
    class WP_Debug_Data { 
    915930                $active_theme_version       = $active_theme->version;
    916931                $active_theme_version_debug = $active_theme_version;
    917932
     933                $auto_updates         = array();
     934                $auto_updates_enabled = wp_is_auto_update_enabled_for_type( 'theme' );
     935                if ( $auto_updates_enabled ) {
     936                        $auto_updates = (array) get_site_option( 'auto_update_themes', array() );
     937                }
     938
    918939                if ( array_key_exists( $active_theme->stylesheet, $theme_updates ) ) {
    919940                        $theme_update_new_version = $theme_updates[ $active_theme->stylesheet ]->update['new_version'];
    920941
    class WP_Debug_Data { 
    9801001                                'value' => get_stylesheet_directory(),
    9811002                        ),
    9821003                );
     1004                if ( $auto_updates_enabled ) {
     1005                        if ( in_array( $active_theme->stylesheet, $auto_updates ) ) {
     1006                                $theme_auto_update_string = __( 'Enabled' );
     1007                        } else {
     1008                                $theme_auto_update_string = __( 'Disabled' );
     1009                        }
    9831010
     1011                        $info['wp-active-theme']['fields']['auto_update'] = array(
     1012                                'label' => __( 'Auto-update' ),
     1013                                'value' => $theme_auto_update_string,
     1014                                'debug' => $theme_auto_update_string,
     1015                        );
     1016                }
    9841017                $parent_theme = $active_theme->parent();
    9851018
    9861019                if ( $parent_theme ) {
    class WP_Debug_Data { 
    10261059                                        'value' => get_template_directory(),
    10271060                                ),
    10281061                        );
     1062                        if ( $auto_updates_enabled ) {
     1063                                if ( in_array( $parent_theme->stylesheet, $auto_updates ) ) {
     1064                                        $parent_theme_auto_update_string = __( 'Enabled' );
     1065                                } else {
     1066                                        $parent_theme_auto_update_string = __( 'Disabled' );
     1067                                }
     1068
     1069                                $info['wp-parent-theme']['fields']['auto_update'] = array(
     1070                                        'label' => __( 'Auto-update' ),
     1071                                        'value' => $parent_theme_auto_update_string,
     1072                                        'debug' => $parent_theme_auto_update_string,
     1073                                );
     1074                        }
    10291075                }
    10301076
    10311077                // Populate a list of all themes available in the install.
    class WP_Debug_Data { 
    10751121                                $theme_version_string_debug .= sprintf( ' (latest version: %s)', $theme_updates[ $theme_slug ]->update['new_version'] );
    10761122                        }
    10771123
     1124                        if ( $auto_updates_enabled ) {
     1125                                if ( in_array( $theme_slug, $auto_updates ) ) {
     1126                                        $theme_version_string       .= ' | ' . __( 'Auto-updates enabled' );
     1127                                        $theme_version_string_debug .= ',' . __( 'Auto-updates enabled' );
     1128                                } else {
     1129                                        $theme_version_string       .= ' | ' . __( 'Auto-updates disabled' );
     1130                                        $theme_version_string_debug .= ', ' . __( 'Auto-updates disabled' );
     1131                                }
     1132                        }
     1133
    10781134                        $info['wp-themes-inactive']['fields'][ sanitize_text_field( $theme->name ) ] = array(
    10791135                                'label' => sprintf(
    10801136                                        /* translators: 1: Theme name. 2: Theme slug. */
  • src/wp-admin/includes/class-wp-ms-themes-list-table.php

    diff --git a/src/wp-admin/includes/class-wp-ms-themes-list-table.php b/src/wp-admin/includes/class-wp-ms-themes-list-table.php
    index 169f783ec8..5d3aa51b92 100644
    a b class WP_MS_Themes_List_Table extends WP_List_Table { 
    2222
    2323        private $has_items;
    2424
     25        /**
     26         * Whether to show the auto-updates UI.
     27         *
     28         * @since 5.5.0
     29         *
     30         * @var bool True if auto-updates UI is to be shown, false otherwise.
     31         */
     32        protected $show_autoupdates = true;
     33
    2534        /**
    2635         * Constructor.
    2736         *
    class WP_MS_Themes_List_Table extends WP_List_Table { 
    4554                );
    4655
    4756                $status = isset( $_REQUEST['theme_status'] ) ? $_REQUEST['theme_status'] : 'all';
    48                 if ( ! in_array( $status, array( 'all', 'enabled', 'disabled', 'upgrade', 'search', 'broken' ), true ) ) {
     57                if ( ! in_array( $status, array( 'all', 'enabled', 'disabled', 'upgrade', 'search', 'broken', 'auto-update-enabled', 'auto-update-disabled' ), true ) ) {
    4958                        $status = 'all';
    5059                }
    5160
    class WP_MS_Themes_List_Table extends WP_List_Table { 
    5665                if ( $this->is_site_themes ) {
    5766                        $this->site_id = isset( $_REQUEST['id'] ) ? intval( $_REQUEST['id'] ) : 0;
    5867                }
     68
     69                $this->show_autoupdates = wp_is_auto_update_enabled_for_type( 'theme' ) &&
     70                        ! $this->is_site_themes && current_user_can( 'update_themes' );
    5971        }
    6072
    6173        /**
    class WP_MS_Themes_List_Table extends WP_List_Table { 
    106118                        'upgrade'  => array(),
    107119                        'broken'   => $this->is_site_themes ? array() : wp_get_themes( array( 'errors' => true ) ),
    108120                );
     121                if ( $this->show_autoupdates ) {
     122                        $auto_updates                   = (array) get_site_option( 'auto_update_themes', array() );
     123
     124                        $themes['auto-update-enabled']  = array();
     125                        $themes['auto-update-disabled'] = array();
     126                }
    109127
    110128                if ( $this->is_site_themes ) {
    111129                        $themes_per_page = $this->get_items_per_page( 'site_themes_network_per_page' );
    class WP_MS_Themes_List_Table extends WP_List_Table { 
    131149
    132150                        $filter                    = $theme->is_allowed( $allowed_where, $this->site_id ) ? 'enabled' : 'disabled';
    133151                        $themes[ $filter ][ $key ] = $themes['all'][ $key ];
     152
     153                        if ( $this->show_autoupdates ) {
     154                                if ( in_array( $key, $auto_updates, true ) ) {
     155                                        $themes['auto-update-enabled'][ $key ]  = $themes['all'][ $key ];
     156                                } else {
     157                                        $themes['auto-update-disabled'][ $key ] = $themes['all'][ $key ];
     158                                }
     159                        }
    134160                }
    135161
    136162                if ( $s ) {
    class WP_MS_Themes_List_Table extends WP_List_Table { 
    257283         * @return array
    258284         */
    259285        public function get_columns() {
    260                 return array(
     286                $columns = array(
    261287                        'cb'          => '<input type="checkbox" />',
    262288                        'name'        => __( 'Theme' ),
    263289                        'description' => __( 'Description' ),
    264290                );
     291                if ( $this->show_autoupdates ) {
     292                        $columns['auto-updates'] = __( 'Automatic Updates' );
     293                }
     294                return $columns;
    265295        }
    266296
    267297        /**
    class WP_MS_Themes_List_Table extends WP_List_Table { 
    344374                                                'themes'
    345375                                        );
    346376                                        break;
     377                                case 'auto-update-enabled':
     378                                        /* translators: %s: Number of themes. */
     379                                        $text = _n(
     380                                                'Auto-updates Enabled <span class="count">(%s)</span>',
     381                                                'Auto-updates Enabled <span class="count">(%s)</span>',
     382                                                $count
     383                                        );
     384                                        break;
     385                                case 'auto-update-disabled':
     386                                        /* translators: %s: Number of themes. */
     387                                        $text = _n(
     388                                                'Auto-updates Disabled <span class="count">(%s)</span>',
     389                                                'Auto-updates Disabled <span class="count">(%s)</span>',
     390                                                $count
     391                                        );
     392                                        break;
    347393                        }
    348394
    349395                        if ( $this->is_site_themes ) {
    class WP_MS_Themes_List_Table extends WP_List_Table { 
    388434                                $actions['delete-selected'] = __( 'Delete' );
    389435                        }
    390436                }
     437                if ( $this->show_autoupdates ) {
     438                        if ( 'auto-update-enabled' !== $status ) {
     439                                $actions['enable-auto-update-selected']  = __( 'Enable Auto-updates' );
     440                        }
     441                        if ( 'auto-update-disabled' !== $status ) {
     442                                $actions['disable-auto-update-selected'] = __( 'Disable Auto-updates' );
     443                        }
     444                }
    391445                return $actions;
    392446        }
    393447
    class WP_MS_Themes_List_Table extends WP_List_Table { 
    639693                echo '</div>';
    640694        }
    641695
     696        /**
     697         * Handles the auto-updates column output.
     698         *
     699         * @since 5.5.0
     700         *
     701         * @global string $status
     702         * @global int  $page
     703         *
     704         * @param WP_Theme $theme The current WP_Theme object.
     705         */
     706        function column_autoupdates( $theme ) {
     707                global $status, $page;
     708
     709                static $auto_updates, $available_updates;
     710
     711                if ( ! $auto_updates ) {
     712                        $auto_updates = (array) get_site_option( 'auto_update_themes', array() );
     713                }
     714                if ( ! $available_updates ) {
     715                        $available_updates = get_site_transient( 'update_themes' );
     716                }
     717
     718                $stylesheet = $theme->get_stylesheet();
     719
     720                if ( in_array( $stylesheet, $auto_updates, true ) ) {
     721                        $text                   = __( 'Disable auto-updates', 'wp-autoupdates' );
     722                        $auto_update_time_class = '';
     723                        $action                 = 'disable';
     724                } else {
     725                        $text                   = __( 'Enable auto-updates', 'wp-autoupdates' );
     726                        $action                 = 'enable';
     727                        $auto_update_time_class = ' hidden';
     728                }
     729
     730                printf(
     731                        '<a href="%s" class="toggle-auto-update" data-wp-action="%s"><span class="dashicons dashicons-update spin hidden"></span><span class="label">%s</span></a>',
     732                        wp_nonce_url( 'themes.php?action=' . $action . '-auto-update&amp;theme=' . urlencode( $stylesheet ) . '&amp;paged=' . $page . '&amp;theme_status=' . $status, 'updates' ),
     733                        $action,
     734                        $text
     735                );
     736
     737                $available_updates = get_site_transient( 'update_themes' );
     738                if ( isset( $available_updates->response[ $stylesheet ] ) ) {
     739                        printf(
     740                                '<div class="auto-update-time%s">%s</div>',
     741                                $auto_update_time_class,
     742                                wp_get_auto_update_message()
     743                        );
     744                }
     745                echo '<div class="auto-updates-error inline notice error hidden"><p></p></div>';
     746        }
     747
    642748        /**
    643749         * Handles default column output.
    644750         *
    class WP_MS_Themes_List_Table extends WP_List_Table { 
    721827                                        echo '</td>';
    722828                                        break;
    723829
     830                                case 'auto-updates':
     831                                        echo "<td class='column-auto-updates{$extra_classes}'>";
     832
     833                                        $this->column_autoupdates( $item );
     834
     835                                        echo '</td>';
     836                                        break;
    724837                                default:
    725838                                        echo "<td class='$column_name column-$column_name{$extra_classes}'>";
    726839
  • src/wp-admin/includes/class-wp-plugins-list-table.php

    diff --git a/src/wp-admin/includes/class-wp-plugins-list-table.php b/src/wp-admin/includes/class-wp-plugins-list-table.php
    index b67d75c4b0..1376c17398 100644
    a b  
    1616 * @see WP_List_Table
    1717 */
    1818class WP_Plugins_List_Table extends WP_List_Table {
     19        /**
     20         * Whether to show the auto-updates UI.
     21         *
     22         * @since 5.5.0
     23         *
     24         * @var bool True if auto-updates UI is to be shown, false otherwise.
     25         */
     26        protected $show_autoupdates = true;
    1927
    2028        /**
    2129         * Constructor.
    class WP_Plugins_List_Table extends WP_List_Table { 
    3947                        )
    4048                );
    4149
    42                 $status_whitelist = array( 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search', 'paused' );
     50                $status_whitelist = array( 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search', 'paused', 'auto-update-enabled', 'auto-update-disabled' );
    4351
    4452                $status = 'all';
    4553                if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], $status_whitelist, true ) ) {
    class WP_Plugins_List_Table extends WP_List_Table { 
    5159                }
    5260
    5361                $page = $this->get_pagenum();
     62
     63                $this->show_autoupdates = wp_is_auto_update_enabled_for_type( 'plugin' ) &&
     64                        current_user_can( 'update_plugins' ) &&
     65                        ( ! is_multisite() || $this->screen->in_admin( 'network' ) );
    5466        }
    5567
    5668        /**
    class WP_Plugins_List_Table extends WP_List_Table { 
    103115                        'dropins'            => array(),
    104116                        'paused'             => array(),
    105117                );
     118                if ( $this->show_autoupdates ) {
     119                        $auto_updates                    = (array) get_site_option( 'auto_update_plugins', array() );
     120
     121                        $plugins['auto-update-enabled']  = array();
     122                        $plugins['auto-update-disabled'] = array();
     123                }
    106124
    107125                $screen = $this->screen;
    108126
    class WP_Plugins_List_Table extends WP_List_Table { 
    233251                                // Populate the inactive list with plugins that aren't activated.
    234252                                $plugins['inactive'][ $plugin_file ] = $plugin_data;
    235253                        }
     254
     255                        if ( $this->show_autoupdates ) {
     256                                if ( in_array( $plugin_file, $auto_updates, true ) ) {
     257                                        $plugins['auto-update-enabled'][ $plugin_file]  = $plugins['all'][ $plugin_file ];
     258                                } else {
     259                                        $plugins['auto-update-disabled'][ $plugin_file] = $plugins['all'][ $plugin_file ];
     260                                }
     261                        }
    236262                }
    237263
    238264                if ( strlen( $s ) ) {
    class WP_Plugins_List_Table extends WP_List_Table { 
    399425        public function get_columns() {
    400426                global $status;
    401427
    402                 return array(
     428                $columns = array(
    403429                        'cb'          => ! in_array( $status, array( 'mustuse', 'dropins' ), true ) ? '<input type="checkbox" />' : '',
    404430                        'name'        => __( 'Plugin' ),
    405431                        'description' => __( 'Description' ),
    406432                );
     433                if ( $this->show_autoupdates ) {
     434                        $columns['auto-updates'] = __( 'Automatic Updates' );
     435                }
     436
     437                return $columns;
    407438        }
    408439
    409440        /**
    class WP_Plugins_List_Table extends WP_List_Table { 
    493524                                                $count
    494525                                        );
    495526                                        break;
     527                                case 'auto-update-enabled':
     528                                        /* translators: %s: Number of plugins. */
     529                                        $text = _n(
     530                                                'Auto-updates Enabled <span class="count">(%s)</span>',
     531                                                'Auto-updates Enabled <span class="count">(%s)</span>',
     532                                                $count
     533                                        );
     534                                        break;
     535                                case 'auto-update-disabled':
     536                                        /* translators: %s: Number of plugins. */
     537                                        $text = _n(
     538                                                'Auto-updates Disabled <span class="count">(%s)</span>',
     539                                                'Auto-updates Disabled <span class="count">(%s)</span>',
     540                                                $count
     541                                        );
     542                                        break;
    496543                        }
    497544
    498545                        if ( 'search' !== $type ) {
    class WP_Plugins_List_Table extends WP_List_Table { 
    533580                        if ( current_user_can( 'delete_plugins' ) && ( 'active' !== $status ) ) {
    534581                                $actions['delete-selected'] = __( 'Delete' );
    535582                        }
     583
     584                        if ( $this->show_autoupdates ) {
     585                                if ( 'auto-update-enabled' !== $status ) {
     586                                        $actions['enable-auto-update-selected']  = __( 'Enable Auto-updates' );
     587                                }
     588                                if ( 'auto-update-disabled' !== $status ) {
     589                                        $actions['disable-auto-update-selected'] = __( 'Disable Auto-updates' );
     590                                }
     591                        }
    536592                }
    537593
    538594                return $actions;
    class WP_Plugins_List_Table extends WP_List_Table { 
    882938
    883939                list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info();
    884940
     941                $auto_updates      = (array) get_site_option( 'auto_update_plugins', array() );
     942                $available_updates = get_site_transient( 'update_plugins' );
     943
    885944                foreach ( $columns as $column_name => $column_display_name ) {
    886945                        $extra_classes = '';
    887946                        if ( in_array( $column_name, $hidden, true ) ) {
    class WP_Plugins_List_Table extends WP_List_Table { 
    9721031                                                }
    9731032                                        }
    9741033
     1034                                        echo '</td>';
     1035                                        break;
     1036                                case 'auto-updates':
     1037                                        if ( ! $this->show_autoupdates ) {
     1038                                                break;
     1039                                        }
     1040
     1041                                        echo "<td class='column-auto-updates{$extra_classes}'>";
     1042
     1043                                        if ( in_array( $plugin_file, $auto_updates, true ) ) {
     1044                                                $text                   = __( 'Disable auto-updates', 'wp-autoupdates' );
     1045                                                $action                 = 'disable';
     1046                                                $auto_update_time_class = '';
     1047                                        } else {
     1048                                                $text                   = __( 'Enable auto-updates', 'wp-autoupdates' );
     1049                                                $action                 = 'enable';
     1050                                                $auto_update_time_class = ' hidden';
     1051                                        }
     1052
     1053                                        printf(
     1054                                                '<a href="%s" class="toggle-auto-update" data-wp-action="%s"><span class="dashicons dashicons-update spin hidden"></span><span class="label">%s</span></a>',
     1055                                                wp_nonce_url( 'plugins.php?action=' . $action . '-auto-update&amp;plugin=' . urlencode( $plugin_file ) . '&amp;paged=' . $page . '&amp;plugin_status=' . $status, 'updates' ),
     1056                                                $action,
     1057                                                $text
     1058                                        );
     1059
     1060                                        $available_updates = get_site_transient( 'update_plugins' );
     1061                                        if ( isset( $available_updates->response[ $plugin_file ] ) ) {
     1062                                                printf(
     1063                                                        '<div class="auto-update-time%s">%s</div>',
     1064                                                        $auto_update_time_class,
     1065                                                        wp_get_auto_update_message()
     1066                                                );
     1067                                        }
     1068                                        echo '<div class="inline notice error hidden"><p></p></div>';
    9751069                                        echo '</td>';
    9761070                                        break;
    9771071                                default:
    class WP_Plugins_List_Table extends WP_List_Table { 
    10001094                 * Fires after each row in the Plugins list table.
    10011095                 *
    10021096                 * @since 2.3.0
     1097                 * @since 5.5.0 Added 'Auto-updates Enabled' and 'Auto-updates Disabled' `$status`.
    10031098                 *
    10041099                 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
    10051100                 * @param array  $plugin_data An array of plugin data.
    10061101                 * @param string $status      Status of the plugin. Defaults are 'All', 'Active',
    10071102                 *                            'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
    1008                  *                            'Drop-ins', 'Search', 'Paused'.
     1103                 *                            'Drop-ins', 'Search', 'Paused', 'Auto-updates Enabled',
     1104                 *                            'Auto-updates Disabled'.
    10091105                 */
    10101106                do_action( 'after_plugin_row', $plugin_file, $plugin_data, $status );
    10111107
    class WP_Plugins_List_Table extends WP_List_Table { 
    10161112                 * to the plugin file, relative to the plugins directory.
    10171113                 *
    10181114                 * @since 2.7.0
     1115                 * @since 5.5.0 Added 'Auto-updates Enabled' and 'Auto-updates Disabled' `$status`.
    10191116                 *
    10201117                 * @param string $plugin_file Path to the plugin file relative to the plugins directory.
    10211118                 * @param array  $plugin_data An array of plugin data.
    10221119                 * @param string $status      Status of the plugin. Defaults are 'All', 'Active',
    10231120                 *                            'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
    1024                  *                            'Drop-ins', 'Search', 'Paused'.
     1121                 *                            'Drop-ins', 'Search', 'Paused', 'Auto-updates Enabled',
     1122                 *                            'Auto-updates Disabled'.
    10251123                 */
    10261124                do_action( "after_plugin_row_{$plugin_file}", $plugin_file, $plugin_data, $status );
    10271125        }
  • src/wp-admin/includes/theme.php

    diff --git a/src/wp-admin/includes/theme.php b/src/wp-admin/includes/theme.php
    index 5c5afee52a..0c917c52d9 100644
    a b function wp_prepare_themes_for_js( $themes = null ) { 
    660660
    661661        $parents = array();
    662662
     663        $auto_updates = (array) get_site_option( 'auto_update_themes', array() );
     664
    663665        foreach ( $themes as $theme ) {
    664666                $slug         = $theme->get_stylesheet();
    665667                $encoded_slug = urlencode( $slug );
    function wp_prepare_themes_for_js( $themes = null ) { 
    683685                        );
    684686                }
    685687
     688                $auto_update        = in_array( $slug, $auto_updates, true );
     689                $auto_update_action = $auto_update ? 'disable-auto-update' : 'enable-auto-update';
     690
    686691                $prepared_themes[ $slug ] = array(
    687692                        'id'            => $slug,
    688693                        'name'          => $theme->display( 'Name' ),
    function wp_prepare_themes_for_js( $themes = null ) { 
    699704                        'hasUpdate'     => isset( $updates[ $slug ] ),
    700705                        'hasPackage'    => isset( $updates[ $slug ] ) && ! empty( $updates[ $slug ]['package'] ),
    701706                        'update'        => get_theme_update_available( $theme ),
     707                        'autoupdate'    => $auto_update,
    702708                        'actions'       => array(
    703                                 'activate'  => current_user_can( 'switch_themes' ) ? wp_nonce_url( admin_url( 'themes.php?action=activate&amp;stylesheet=' . $encoded_slug ), 'switch-theme_' . $slug ) : null,
    704                                 'customize' => $customize_action,
    705                                 'delete'    => current_user_can( 'delete_themes' ) ? wp_nonce_url( admin_url( 'themes.php?action=delete&amp;stylesheet=' . $encoded_slug ), 'delete-theme_' . $slug ) : null,
     709                                'activate'   => current_user_can( 'switch_themes' ) ? wp_nonce_url( admin_url( 'themes.php?action=activate&amp;stylesheet=' . $encoded_slug ), 'switch-theme_' . $slug ) : null,
     710                                'customize'  => $customize_action,
     711                                'delete'     => current_user_can( 'delete_themes' ) ? wp_nonce_url( admin_url( 'themes.php?action=delete&amp;stylesheet=' . $encoded_slug ), 'delete-theme_' . $slug ) : null,
     712                                'autoupdate' => wp_is_auto_update_enabled_for_type( 'theme' ) && ! is_multisite() && current_user_can( 'update_themes' )
     713                                        ? wp_nonce_url( admin_url( 'themes.php?action=' . $auto_update_action . '&amp;stylesheet=' . $encoded_slug ), 'updates' )
     714                                        : null,
    706715                        ),
    707716                );
    708717        }
  • src/wp-admin/includes/update.php

    diff --git a/src/wp-admin/includes/update.php b/src/wp-admin/includes/update.php
    index 085aa00f9e..3bc42e8fc1 100644
    a b function wp_plugin_update_row( $file, $plugin_data ) { 
    435435        $details_url = self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $response->slug . '&section=changelog&TB_iframe=true&width=600&height=800' );
    436436
    437437        /** @var WP_Plugins_List_Table $wp_list_table */
    438         $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
     438        $wp_list_table = _get_list_table(
     439                'WP_Plugins_List_Table',
     440                array(
     441                        'screen' => get_current_screen(),
     442                )
     443        );
    439444
    440445        if ( is_network_admin() || ! is_multisite() ) {
    441446                if ( is_network_admin() ) {
    function wp_recovery_mode_nag() { 
    933938        </div>
    934939        <?php
    935940}
     941
     942/**
     943 * Checks whether auto-updates are enabled.
     944 *
     945 * @since 5.5.0
     946 *
     947 * @param string $type    The type of update being checked: 'theme' or 'plugin'.
     948 * @return bool True if auto-updates are enabled for `$type`, false otherwise.
     949 */
     950function wp_is_auto_update_enabled_for_type( $type ) {
     951        switch ( $type ) {
     952                case 'plugin':
     953                        /**
     954                         * Filters whether plugins manual auto-update is enabled.
     955                         *
     956                         * @since 5.5.0
     957                         *
     958                         * @param bool $enabled True if plugins auto-update is enabled, false otherwise.
     959                         */
     960                        return apply_filters( 'wp_plugins_auto_update_enabled', true );
     961                case 'theme':
     962                        /**
     963                         * Filters whether plugins manual auto-update is enabled.
     964                         *
     965                         * @since 5.5.0
     966                         *
     967                         * @param bool True if themes auto-update is enabled, false otherwise.
     968                         */
     969                        return apply_filters( 'wp_themes_auto_update_enabled', true );
     970        }
     971
     972        return false;
     973}
     974
     975/**
     976 * Determines the appropriate update message to be displayed.
     977 *
     978 * @since 5.5.0
     979 *
     980 * @return string The update message to be shown.
     981 */
     982function wp_get_auto_update_message() {
     983        $next_update_time = wp_next_scheduled( 'wp_version_check' );
     984
     985        // Check if event exists.
     986        if ( false === $next_update_time ) {
     987                return __( 'There may be a problem with WP-Cron. Automatic update not scheduled.' );
     988        }
     989
     990        // See if cron is disabled
     991        $cron_disabled = defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON;
     992        if ( $cron_disabled ) {
     993                return __( 'WP-Cron is disabled. Automatic updates not available.' );
     994        }
     995
     996        $time_to_next_update = human_time_diff( intval( $next_update_time ) );
     997
     998        // See if cron is overdue.
     999        $overdue = ( time() - $next_update_time ) > 0;
     1000        if ( $overdue ) {
     1001                return sprintf(
     1002                        /* translators: Duration that WP-Cron has been overdue. */
     1003                        __( 'There may be a problem with WP-Cron. Automatic update overdue by %s.' ),
     1004                        $time_to_next_update
     1005                );
     1006        } else {
     1007                return sprintf(
     1008                        /* translators: Time until the next update. */
     1009                        __( 'Auto-update scheduled in %s.' ),
     1010                        $time_to_next_update
     1011                );
     1012        }
     1013}
  • src/wp-admin/network/themes.php

    diff --git a/src/wp-admin/network/themes.php b/src/wp-admin/network/themes.php
    index e8027b939c..8d269044f8 100644
    a b $action = $wp_list_table->current_action(); 
    2222$s = isset( $_REQUEST['s'] ) ? $_REQUEST['s'] : '';
    2323
    2424// Clean up request URI from temporary args for screen options/paging uri's to work as expected.
    25 $temp_args              = array( 'enabled', 'disabled', 'deleted', 'error' );
     25$temp_args              = array( 'enabled', 'disabled', 'deleted', 'error', 'enabled-auto-update', 'disabled-auto-update' );
    2626$_SERVER['REQUEST_URI'] = remove_query_arg( $temp_args, $_SERVER['REQUEST_URI'] );
    2727$referer                = remove_query_arg( $temp_args, wp_get_referer() );
    2828
    if ( $action ) { 
    208208                                )
    209209                        );
    210210                        exit;
     211                case 'enable-auto-update':
     212                        if ( ! ( current_user_can( 'update_themes' ) && wp_is_auto_update_enabled_for_type( 'theme' ) ) ) {
     213                                wp_die( __( 'Sorry, you are not allowed to enable themes automatic updates.' ) );
     214                        }
     215
     216                        check_admin_referer( 'updates' );
     217
     218                        $all_items      = wp_get_themes();
     219                        $auto_updates   = (array) get_site_option( 'auto_update_themes', array() );
     220
     221                        $auto_updates[] = $_GET['theme'];
     222                        $auto_updates   = array_unique( $auto_updates );
     223                        // Remove themes that have been deleted since the site option was last updated.
     224                        $auto_updates   = array_intersect( $auto_updates, array_keys( $all_items ) );
     225
     226                        update_site_option( 'auto_update_themes', $auto_updates );
     227
     228                        wp_safe_redirect( add_query_arg( 'enabled-auto-update', 1, $referer ) );
     229                        exit;
     230                case 'disable-auto-update':
     231                        if ( ! ( current_user_can( 'update_themes' ) && wp_is_auto_update_enabled_for_type( 'theme' ) ) ) {
     232                                wp_die( __( 'Sorry, you are not allowed to disable themes automatic updates.' ) );
     233                        }
     234
     235                        check_admin_referer( 'updates' );
     236
     237                        $all_items    = wp_get_themes();
     238                        $auto_updates = (array) get_site_option( 'auto_update_themes', array() );
     239
     240                        $auto_updates = array_diff( $auto_updates, array( $_GET['theme'] ) );
     241                        // Remove themes that have been deleted since the site option was last updated.
     242                        $auto_updates = array_intersect( $auto_updates, array_keys( $all_items ) );
     243
     244                        update_site_option( 'auto_update_themes', $auto_updates );
     245
     246                        wp_safe_redirect( add_query_arg( 'disabled-auto-update', 1, $referer ) );
     247                        exit;
     248                case 'enable-auto-update-selected':
     249                        if ( ! ( current_user_can( 'update_themes' ) && wp_is_auto_update_enabled_for_type( 'theme' ) ) ) {
     250                                wp_die( __( 'Sorry, you are not allowed to enable themes automatic updates.' ) );
     251                        }
     252
     253                        check_admin_referer( 'bulk-themes' );
     254
     255                        $themes = isset( $_POST['checked'] ) ? (array) wp_unslash( $_POST['checked'] ) : array();
     256
     257                        $all_items        = wp_get_themes();
     258                        $auto_updates     = (array) get_site_option( 'auto_update_themes', array() );
     259
     260                        $new_auto_updates = array_merge( $auto_updates, $themes );
     261                        $new_auto_updates = array_unique( $new_auto_updates );
     262                        // Remove themes that have been deleted since the site option was last updated.
     263                        $new_auto_updates = array_intersect( $new_auto_updates, array_keys( $all_items ) );
     264
     265                        update_site_option( 'auto_update_themes', $new_auto_updates );
     266
     267                        wp_safe_redirect( add_query_arg( 'enabled-auto-update', count( $themes ), $referer ) );
     268                        exit;
     269                case 'disable-auto-update-selected':
     270                        if ( ! ( current_user_can( 'update_themes' ) && wp_is_auto_update_enabled_for_type( 'theme' ) ) ) {
     271                                wp_die( __( 'Sorry, you are not allowed to disable themes automatic updates.' ) );
     272                        }
     273
     274                        check_admin_referer( 'bulk-themes' );
     275
     276                        $themes = isset( $_POST['checked'] ) ? (array) wp_unslash( $_POST['checked'] ) : array();
     277
     278                        $all_items        = wp_get_themes();
     279                        $auto_updates     = (array) get_site_option( 'auto_update_themes', array() );
     280
     281                        $new_auto_updates = array_diff( $auto_updates, $themes );
     282
     283                        if ( $new_auto_updates == $auto_updates ) {
     284                                if ( false === strpos( $referer, '/network/themes.php' ) ) {
     285                                        wp_redirect( network_admin_url( 'themes.php?disabled-auto-update=1' ) );
     286                                } else {
     287                                        wp_safe_redirect( $referer );
     288                                }
     289                                exit;
     290                        }
     291
     292                        // Remove themes that have been deleted since the site option was last updated.
     293                        $new_auto_updates = array_intersect( $new_auto_updates, array_keys( $all_items ) );
     294
     295                        update_site_option( 'auto_update_themes', $new_auto_updates );
     296
     297                        wp_safe_redirect( add_query_arg( 'disabled-auto-update', count( $themes ), $referer ) );
     298                        exit;
    211299                default:
    212300                        $themes = isset( $_POST['checked'] ) ? (array) $_POST['checked'] : array();
    213301                        if ( empty( $themes ) ) {
    if ( isset( $_GET['enabled'] ) ) { 
    309397                $message = _n( '%s theme deleted.', '%s themes deleted.', $deleted );
    310398        }
    311399        echo '<div id="message" class="updated notice is-dismissible"><p>' . sprintf( $message, number_format_i18n( $deleted ) ) . '</p></div>';
     400} elseif ( isset( $_GET['enabled-auto-update'] ) ) {
     401        $enabled = absint( $_GET['enabled-auto-update'] );
     402        if ( 1 == $enabled ) {
     403                $message = __( 'Theme will be auto-updated.' );
     404        } else {
     405                /* translators: %s: Number of themes. */
     406                $message = _n( '%s theme will be auto-updated.', '%s themes will be auto-updated.', $enabled );
     407        }
     408        echo '<div id="message" class="updated notice is-dismissible"><p>' . sprintf( $message, number_format_i18n( $enabled ) ) . '</p></div>';
     409} elseif ( isset( $_GET['disabled-auto-update'] ) ) {
     410        $disabled = absint( $_GET['disabled-auto-update'] );
     411        if ( 1 == $disabled ) {
     412                $message = __( 'Theme will no longer be auto-updated.' );
     413        } else {
     414                /* translators: %s: Number of themes. */
     415                $message = _n( '%s theme will no longer be auto-updated.', '%s themes will no longer be auto-updated.', $disabled );
     416        }
     417        echo '<div id="message" class="updated notice is-dismissible"><p>' . sprintf( $message, number_format_i18n( $disabled ) ) . '</p></div>';
    312418} elseif ( isset( $_GET['error'] ) && 'none' == $_GET['error'] ) {
    313419        echo '<div id="message" class="error notice is-dismissible"><p>' . __( 'No theme selected.' ) . '</p></div>';
    314420} elseif ( isset( $_GET['error'] ) && 'main' == $_GET['error'] ) {
    315421        echo '<div class="error notice is-dismissible"><p>' . __( 'You cannot delete a theme while it is active on the main site.' ) . '</p></div>';
     422} elseif ( isset( $_GET['error'] ) && 'none' == $_GET['error'] ) {
     423        echo '<div id="message" class="error notice is-dismissible"><p>' . __( 'No theme selected.' ) . '</p></div>';
     424} elseif ( isset( $_GET['disable-auto-update'] ) ) {
     425        echo '<div id="message" class="updated notice is-dismissible"><p>' . __( 'Theme will no longer be auto-updated.' ) . '</p></div>';
     426} elseif ( isset( $_GET['enable-autoupdate-multi'] ) ) {
     427        echo '<div id="message" class="updated notice is-dismissible"><p>' . __( 'Themes will be auto-updated.' ) . '</p></div>';
     428} elseif ( isset( $_GET['disable-autoupdate-multi'] ) ) {
     429        echo '<div id="message" class="updated notice is-dismissible"><p>' . __( 'Themes will no longer be auto-updated.' ) . '</p></div>';
    316430}
    317431
    318432?>
  • src/wp-admin/plugins.php

    diff --git a/src/wp-admin/plugins.php b/src/wp-admin/plugins.php
    index 8352e207ab..3534417cfe 100644
    a b $plugin = isset( $_REQUEST['plugin'] ) ? wp_unslash( $_REQUEST['plugin'] ) : ''; 
    2222$s      = isset( $_REQUEST['s'] ) ? urlencode( wp_unslash( $_REQUEST['s'] ) ) : '';
    2323
    2424// Clean up request URI from temporary args for screen options/paging uri's to work as expected.
    25 $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'error', 'deleted', 'activate', 'activate-multi', 'deactivate', 'deactivate-multi', '_error_nonce' ), $_SERVER['REQUEST_URI'] );
     25$_SERVER['REQUEST_URI'] = remove_query_arg( array( 'error', 'deleted', 'activate', 'activate-multi', 'deactivate', 'deactivate-multi', 'enable-auto-update', 'disable-auto-update', 'enable-auto-update-multi', 'disable-auto-update-multi', '_error_nonce' ), $_SERVER['REQUEST_URI'] );
    2626
    2727wp_enqueue_script( 'updates' );
    2828
    if ( $action ) { 
    414414                        wp_redirect( self_admin_url( "plugins.php?resume=true&plugin_status=$status&paged=$page&s=$s" ) );
    415415                        exit;
    416416
     417                case 'enable-auto-update':
     418                        if ( ! current_user_can( 'update_plugins' ) || ! wp_is_auto_update_enabled_for_type( 'plugin' ) ) {
     419                                wp_die( __( 'Sorry, you are not allowed to enable plugins automatic updates.' ) );
     420                        }
     421
     422                        if ( is_multisite() && ! is_network_admin() ) {
     423                                wp_die( __( 'Please connect to your network admin to manage plugins automatic updates.' ) );
     424                        }
     425
     426                        check_admin_referer( 'updates' );
     427
     428                        if ( empty( $plugin ) ) {
     429                                wp_redirect( self_admin_url( "plugins.php?plugin_status=$status&paged=$page&s=$s" ) );
     430                                exit;
     431                        }
     432
     433                        /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
     434                        $all_items    = apply_filters( 'all_plugins', get_plugins() );
     435                        $auto_updates = (array) get_site_option( 'auto_update_plugins', array() );
     436
     437                        $auto_updates[] = $plugin;
     438                        $auto_updates   = array_unique( $auto_updates );
     439                        // Remove plugins that have been deleted since the site option was last updated.
     440                        $auto_updates   = array_intersect( $auto_updates, array_keys( $all_items ) );
     441
     442                        update_site_option( 'auto_update_plugins', $auto_updates );
     443
     444                        wp_redirect( self_admin_url( "plugins.php?enable-auto-update=true&plugin_status=$status&paged=$page&s=$s" ) );
     445                        exit;
     446
     447                case 'disable-auto-update':
     448                        if ( ! current_user_can( 'update_plugins' ) || ! wp_is_auto_update_enabled_for_type( 'plugin' ) ) {
     449                                wp_die( __( 'Sorry, you are not allowed to disable plugins automatic updates.' ) );
     450                        }
     451
     452                        if ( is_multisite() && ! is_network_admin() ) {
     453                                wp_die( __( 'Please connect to your network admin to manage plugins automatic updates.' ) );
     454                        }
     455
     456                        check_admin_referer( 'updates' );
     457
     458                        if ( empty( $plugin ) ) {
     459                                wp_redirect( self_admin_url( "plugins.php?plugin_status=$status&paged=$page&s=$s" ) );
     460                                exit;
     461                        }
     462
     463                        /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
     464                        $all_items    = apply_filters( 'all_plugins', get_plugins() );
     465                        $auto_updates = (array) get_site_option( 'auto_update_plugins', array() );
     466
     467                        $auto_updates = array_diff( $auto_updates, array( $plugin ) );
     468                        // Remove plugins that have been deleted since the site option was last updated.
     469                        $auto_updates = array_intersect( $auto_updates, array_keys( $all_items ) );
     470
     471                        update_site_option( 'auto_update_plugins', $auto_updates );
     472
     473                        wp_redirect( self_admin_url( "plugins.php?disable-auto-update=true&plugin_status=$status&paged=$page&s=$s" ) );
     474                        exit;
     475
     476                case 'enable-auto-update-selected':
     477                        if ( ! ( current_user_can( 'update_plugins' ) && wp_is_auto_update_enabled_for_type( 'plugin' ) ) ) {
     478                                wp_die( __( 'Sorry, you are not allowed to enable plugins automatic updates.' ) );
     479                        }
     480
     481                        if ( is_multisite() && ! is_network_admin() ) {
     482                                wp_die( __( 'Please connect to your network admin to manage plugins automatic updates.' ) );
     483                        }
     484
     485                        check_admin_referer( 'bulk-plugins' );
     486
     487                        $plugins = isset( $_POST['checked'] ) ? (array) wp_unslash( $_POST['checked'] ) : array();
     488
     489                        $auto_updates     = (array) get_site_option( 'auto_update_plugins', array() );
     490
     491                        $new_auto_updates = array_merge( $auto_updates, $plugins );
     492                        $new_auto_updates = array_unique( $new_auto_updates );
     493
     494                        // Return early if all selected plugins already have auto-updates enabled.
     495                        // Must use non-strict comparison, so that array order is not treated as significant.
     496                        if ( $new_auto_updates == $auto_updates ) {
     497                                wp_redirect( self_admin_url( "plugins.php?plugin_status=$status&paged=$page&s=$s" ) );
     498                                exit;
     499                        }
     500
     501                        /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
     502                        $all_items    = apply_filters( 'all_plugins', get_plugins() );
     503                        // Remove plugins that have been deleted since the site option was last updated.
     504                        $auto_updates = array_intersect( $new_auto_updates, array_keys( $all_items ) );
     505
     506                        update_site_option( 'auto_update_plugins', $new_auto_updates );
     507
     508                        wp_redirect( self_admin_url( "plugins.php?enable-auto-update-multi=true&plugin_status=$status&paged=$page&s=$s" ) );
     509                        exit;
     510                case 'disable-auto-update-selected':
     511                        if ( ! ( current_user_can( 'update_plugins' ) && wp_is_auto_update_enabled_for_type( 'plugin' ) ) ) {
     512                                wp_die( __( 'Sorry, you are not allowed to disable plugins automatic updates.' ) );
     513                        }
     514
     515                        if ( is_multisite() && ! is_network_admin() ) {
     516                                wp_die( __( 'Please connect to your network admin to manage plugins automatic updates.' ) );
     517                        }
     518
     519                        check_admin_referer( 'bulk-plugins' );
     520
     521                        $plugins = isset( $_POST['checked'] ) ? (array) wp_unslash( $_POST['checked'] ) : array();
     522
     523                        $auto_updates     = (array) get_site_option( 'auto_update_plugins', array() );
     524                        $new_auto_updates = array_diff( $auto_updates, $plugins );
     525                        $new_auto_updates = array_unique( $new_auto_updates );
     526
     527                        // Return early if all selected plugins already have auto-updates enabled.
     528                        // Must use non-strict comparison, so that array order is not treated as significant.
     529                        if ( $new_auto_updates == $auto_updates ) {
     530                                wp_redirect( self_admin_url( "plugins.php?plugin_status=$status&paged=$page&s=$s" ) );
     531                                exit;
     532                        }
     533
     534                        /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
     535                        $all_items    = apply_filters( 'all_plugins', get_plugins() );
     536                        // Remove plugins that have been deleted since the site option was last updated.
     537                        $auto_updates = array_intersect( $new_auto_updates, array_keys( $all_items ) );
     538
     539                        update_site_option( 'auto_update_plugins', $new_auto_updates );
     540
     541                        wp_redirect( self_admin_url( "plugins.php?disable-auto-update-multi=true&plugin_status=$status&paged=$page&s=$s" ) );
     542                        exit;
    417543                default:
    418544                        if ( isset( $_POST['checked'] ) ) {
    419545                                check_admin_referer( 'bulk-plugins' );
    elseif ( isset( $_GET['deleted'] ) ) : 
    583709        <div id="message" class="updated notice is-dismissible"><p><?php _e( 'All selected plugins are up to date.' ); ?></p></div>
    584710<?php elseif ( isset( $_GET['resume'] ) ) : ?>
    585711        <div id="message" class="updated notice is-dismissible"><p><?php _e( 'Plugin resumed.' ); ?></p></div>
     712<?php elseif ( isset( $_GET['enable-auto-update'] ) ) : ?>
     713        <div id="message" class="updated notice is-dismissible"><p><?php _e( 'Plugin will be auto-updated.' ); ?></p></div>
     714<?php elseif ( isset( $_GET['disable-auto-update'] ) ) : ?>
     715        <div id="message" class="updated notice is-dismissible"><p><?php _e( 'Plugin will no longer be auto-updated.' ); ?></p></div>
     716<?php elseif ( isset( $_GET['enable-auto-update-multi'] ) ) : ?>
     717        <div id="message" class="updated notice is-dismissible"><p><?php _e( 'Selected plugins will be auto-updated.' ); ?></p></div>
     718<?php elseif ( isset( $_GET['disable-auto-update-multi'] ) ) : ?>
     719        <div id="message" class="updated notice is-dismissible"><p><?php _e( 'Selected plugins will no longer be auto-updated.' ); ?></p></div>
    586720<?php endif; ?>
    587721
    588722<div class="wrap">
  • src/wp-admin/themes.php

    diff --git a/src/wp-admin/themes.php b/src/wp-admin/themes.php
    index b9df71b70f..452209d6d9 100644
    a b if ( current_user_can( 'switch_themes' ) && isset( $_GET['action'] ) ) { 
    8080                        delete_theme( $_GET['stylesheet'] );
    8181                        wp_redirect( admin_url( 'themes.php?deleted=true' ) );
    8282                }
     83                exit;
     84        } elseif ( 'enable-auto-update' === $_GET['action'] ) {
     85                if ( ! ( current_user_can( 'update_themes' ) && wp_is_auto_update_enabled_for_type( 'theme' ) ) ) {
     86                        wp_die( __( 'Sorry, you are not allowed to enable themes automatic updates.' ) );
     87                }
     88
     89                check_admin_referer( 'updates' );
     90
     91                $all_items    = wp_get_themes();
     92                $auto_updates = (array) get_site_option( 'auto_update_themes', array() );
     93
     94                $auto_updates[] = $_GET['stylesheet'];
     95                $auto_updates   = array_unique( $auto_updates );
     96                // Remove themes that have been deleted since the site option was last updated.
     97                $auto_updates   = array_intersect( $auto_updates, array_keys( $all_items ) );
     98
     99                update_site_option( 'auto_update_themes', $auto_updates );
     100
     101                wp_redirect( admin_url( "themes.php?enabled-auto-update=true" ) );
     102
     103                exit;
     104        } elseif ( 'disable-auto-update' === $_GET['action'] ) {
     105                if ( ! ( current_user_can( 'update_themes' ) && wp_is_auto_update_enabled_for_type( 'theme' ) ) ) {
     106                        wp_die( __( 'Sorry, you are not allowed to disable themes automatic updates.' ) );
     107                }
     108
     109                check_admin_referer( 'updates' );
     110
     111                $all_items    = wp_get_themes();
     112                $auto_updates = (array) get_site_option( 'auto_update_themes', array() );
     113
     114                $auto_updates = array_diff( $auto_updates, array( $_GET['stylesheet'] ) );
     115                // Remove themes that have been deleted since the site option was last updated.
     116                $auto_updates = array_intersect( $auto_updates, array_keys( $all_items ) );
     117
     118                update_site_option( 'auto_update_themes', $auto_updates );
     119
     120                wp_redirect( admin_url( "themes.php?disabled-auto-update=true" ) );
     121
    83122                exit;
    84123        }
    85124}
    if ( ! validate_current_theme() || isset( $_GET['broken'] ) ) { 
    228267        ?>
    229268        <div id="message6" class="error"><p><?php _e( 'Theme could not be resumed because it triggered a <strong>fatal error</strong>.' ); ?></p></div>
    230269        <?php
     270} elseif ( isset( $_GET['enabled-auto-update'] ) ) {
     271        ?>
     272        <div id="message7" class="updated notice is-dismissible"><p><?php _e( 'Theme will be auto-updated.' ); ?></p></div>
     273        <?php
     274} elseif ( isset( $_GET['disabled-auto-update'] ) ) {
     275        ?>
     276        <div id="message8" class="updated notice is-dismissible"><p><?php _e( 'Theme will no longer be auto-updated.' ); ?></p></div>
     277        <?php
    231278}
    232279
    233280$ct = wp_get_theme();
    if ( ! is_multisite() && $broken_themes ) { 
    581628                                        ?>
    582629                                </p>
    583630
     631                                <# if ( data.actions.autoupdate ) { #>
     632                                <p class="theme-autoupdate">
     633                                <# if ( data.autoupdate ) { #>
     634                                        <a href="{{{ data.actions.autoupdate }}}" class="toggle-auto-update" data-slug="{{ data.id }}" data-wp-action="disable">
     635                                                <span class="dashicons dashicons-update spin hidden"></span>
     636                                                <span class="label"><?php _e( 'Disable auto-updates' ) ?></span>
     637                                        </a>
     638                                <# } else { #>
     639                                        <a href="{{{ data.actions.autoupdate }}}" class="toggle-auto-update" data-slug="{{ data.id }}" data-wp-action="enable">
     640                                                <span class="dashicons dashicons-update spin hidden"></span>
     641                                                <span class="label"><?php _e( 'Enable auto-updates' ) ?></span>
     642                                        </a>
     643                                <# } #>
     644                                <# if ( data.hasUpdate ) { #>
     645                                        <# if ( data.autoupdate) { #>
     646                                        <span class="auto-update-time"><br /><?php echo wp_get_auto_update_message() ?></span>
     647                                        <# } else { #>
     648                                        <span class="auto-update-time hidden"><br /><?php echo wp_get_auto_update_message() ?></span>
     649                                        <# } #>
     650                                <# } #>
     651                                        <span class="auto-updates-error hidden"><p></p></span>
     652                                </p>
     653                                <# } #>
     654
    584655                                <# if ( data.hasUpdate ) { #>
    585656                                <div class="notice notice-warning notice-alt notice-large">
    586657                                        <h3 class="notice-title"><?php _e( 'Update Available' ); ?></h3>
  • src/wp-admin/update-core.php

    diff --git a/src/wp-admin/update-core.php b/src/wp-admin/update-core.php
    index c74e5c5546..471505efcd 100644
    a b function list_plugin_updates() { 
    328328
    329329        <tbody class="plugins">
    330330        <?php
     331
     332        $auto_updates = array();
     333        if ( wp_is_auto_update_enabled_for_type( 'plugin' ) ) {
     334                $auto_updates       = (array) get_site_option( 'auto_update_plugins', array() );
     335                $auto_update_notice = ' | ' . wp_get_auto_update_message();
     336        }
     337
    331338        foreach ( (array) $plugins as $plugin_file => $plugin_data ) {
    332339                $plugin_data = (object) _get_plugin_data_markup_translate( $plugin_file, (array) $plugin_data, false, true );
    333340
    function list_plugin_updates() { 
    419426                                $plugin_data->update->new_version
    420427                        );
    421428                        echo ' ' . $details . $compat . $upgrade_notice;
     429                        if ( in_array( $plugin_file, $auto_updates, true ) ) {
     430                                echo $auto_update_notice;
     431                        }
    422432                        ?>
    423433                </p></td>
    424434        </tr>
    function list_theme_updates() { 
    478488
    479489        <tbody class="plugins">
    480490        <?php
     491        $auto_updates = array();
     492        if ( wp_is_auto_update_enabled_for_type( 'theme' ) ) {
     493                $auto_updates = (array) get_site_option( 'auto_update_themes', array() );
     494                $auto_update_notice = ' | ' . wp_get_auto_update_message();
     495        }
     496
    481497        foreach ( $themes as $stylesheet => $theme ) {
    482498                $checkbox_id = 'checkbox_' . md5( $theme->get( 'Name' ) );
     499
    483500                ?>
    484501        <tr>
    485502                <td class="check-column">
    function list_theme_updates() { 
    501518                                $theme->display( 'Version' ),
    502519                                $theme->update['new_version']
    503520                        );
     521                        if ( in_array( $stylesheet, $auto_updates, true ) ) {
     522                                echo $auto_update_notice;
     523                        }
    504524                        ?>
    505525                </p></td>
    506526        </tr>
  • src/wp-includes/script-loader.php

    diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php
    index 59aed70a22..e765a03163 100644
    a b function wp_default_scripts( $scripts ) { 
    15201520                                        /* translators: %s: Number of plugins. */
    15211521                                        'pluginsFound'             => __( 'Number of plugins found: %d' ),
    15221522                                        'noPluginsFound'           => __( 'No plugins found. Try a different search.' ),
     1523                                        'autoUpdatesEnable'        => __( 'Enable auto-updates' ),
     1524                                        'autoUpdatesEnabling'      => __( 'Enabling...' ),
     1525                                        'autoUpdatesEnabled'       => __( 'Auto-updates enabled' ),
     1526                                        'autoUpdatesDisable'       => __( 'Disable auto-updates' ),
     1527                                        'autoUpdatesDisabling'     => __( 'Disabling...' ),
     1528                                        'autoUpdatesDisabled'      => __( 'Auto-updates disabled' ),
     1529                                        'autoUpdatesError'         => __( 'The request could not be completed.' ),
    15231530                                ),
    15241531                        )
    15251532                );