├── css ├── index.php └── wp-autoupdates.css ├── index.php ├── license.txt ├── readme.md ├── readme.txt └── wp-autoupdates.php /css/index.php: -------------------------------------------------------------------------------- 1 | '; 43 | $autoupdate_text .= $update_message; 44 | $autoupdate_text .= ' '; 45 | $script .= 'jQuery(".check-column input[value=\'' . $theme . '\']").closest("tr").find(".plugin-title > p").append(\'' . $autoupdate_text . '\');'; 46 | } 47 | } 48 | 49 | if ( wp_autoupdates_is_plugins_auto_update_enabled() ) { 50 | $wp_auto_update_plugins = get_site_option( 'wp_auto_update_plugins', array() ); 51 | 52 | $update_message = wp_autoupdates_get_update_message(); 53 | foreach ( $wp_auto_update_plugins as $plugin ) { 54 | $autoupdate_text = ' '; 55 | $autoupdate_text .= $update_message; 56 | $autoupdate_text .= ' '; 57 | $script .= 'jQuery(".check-column input[value=\'' . $plugin . '\']").closest("tr").find(".plugin-title > p").append(\'' . $autoupdate_text . '\');'; 58 | } 59 | } 60 | $script .= '});'; 61 | wp_add_inline_script( 'jquery', $script ); 62 | } 63 | 64 | // When manually updating a plugin the 'time until next update' text needs to be hidden. 65 | // Doesn't need to be done on the update-core.php page since that page refreshes after an update. 66 | if ( 'plugins.php' === $hook ) { 67 | $script = 'jQuery( document ).ready(function() { 68 | jQuery( ".update-link" ).click( function() { 69 | var plugin = jQuery( this ).closest("tr").data("plugin"); 70 | var plugin_row = jQuery( "tr.update[data-plugin=\'" + plugin + "\']" ); 71 | var plugin_auto_update_time_text = plugin_row.find("span.plugin-autoupdate-time"); 72 | plugin_auto_update_time_text.remove(); 73 | }); 74 | });'; 75 | wp_add_inline_script( 'jquery', $script ); 76 | } 77 | 78 | if ( 'themes.php' === $hook ) { 79 | if ( wp_autoupdates_is_themes_auto_update_enabled() ) { 80 | $script = 'jQuery( document ).ready( function() {'; 81 | 82 | /* translators: %s: Theme name. */ 83 | $aria_label_enable = sprintf( _x( 'Enable automatic update for %s', 'theme' ), '{{ data.name }}' ); 84 | $aria_label_disable = sprintf( _x( 'Disable automatic update for %s', 'theme' ), '{{ data.name }}' ); 85 | 86 | // Put the enable/disable link below the author and before the update box. 87 | $autoupdate_text = '

<# if ( data.autoupdate ) { #>'; 88 | $autoupdate_text .= ''; 89 | $autoupdate_text .= ' ' . __( 'Disable automatic updates' ) . ''; 90 | $autoupdate_text .= ''; 91 | $autoupdate_text .= '<# } else { #>'; 92 | $autoupdate_text .= ''; 93 | $autoupdate_text .= ' ' . __( 'Enable automatic updates' ) . ''; 94 | $autoupdate_text .= ''; 95 | $autoupdate_text .= '<# } #>

'; 96 | 97 | $script .= ' const theme_template_single = jQuery( "#tmpl-theme-single" ); 98 | 99 | // Pull template into new html element, manipulate, then put back. 100 | // Props https://stackoverflow.com/a/42248980. 101 | function insert_into_template(positioning_text, added_text, insert_before) { 102 | var template_text = theme_template_single.text(); 103 | var position = template_text.search(positioning_text); 104 | if ( -1 !== position ) { 105 | if ( true !== insert_before ) { 106 | position += positioning_text.length; 107 | } 108 | 109 | const new_template_text = template_text.substr(0, position) + added_text + template_text.substr(position); 110 | theme_template_single.text( new_template_text ); 111 | } 112 | } 113 | 114 | const position_beginning_of_update_box = "<# if \\\\( data.hasUpdate \\\\) { #>"; 115 | insert_into_template(position_beginning_of_update_box, "' . str_replace('"', '\"', $autoupdate_text) . '", true); 116 | '; 117 | 118 | // Put the time until next update within the data.hasUpdate block. 119 | $update_message = wp_autoupdates_get_update_message(); 120 | $autoupdate_time_text = '<# if ( data.autoupdate ) { #>'; 121 | $autoupdate_time_text .= '

' . $update_message . '

'; 122 | $autoupdate_time_text .= '<# } #>'; 123 | 124 | $script .= ' 125 | const position_data_update = "{{{ data.update }}}"; 126 | insert_into_template(position_data_update, "' . str_replace('"', '\"', $autoupdate_time_text) . '", false); 127 | '; 128 | 129 | $script .= '});'; 130 | wp_add_inline_script( 'jquery', $script ); 131 | } 132 | } 133 | } 134 | add_action( 'admin_enqueue_scripts', 'wp_autoupdates_enqueues' ); 135 | 136 | 137 | /** 138 | * Filter the themes prepared for JavaScript, for themes.php. 139 | */ 140 | function wp_autoupdates_prepare_themes_for_js( $prepared_themes ) { 141 | $wp_auto_update_themes = get_option( 'wp_auto_update_themes', array() ); 142 | foreach( $prepared_themes as $theme ) { 143 | // Set extra data for use in the template. 144 | $slug = $theme['id']; 145 | $encoded_slug = urlencode( $slug ); 146 | 147 | $theme['autoupdate'] = in_array( $slug, $wp_auto_update_themes, true ); 148 | $theme['actions']['autoupdate'] = current_user_can( 'update_themes' ) ? wp_nonce_url( admin_url( 'themes.php?action=autoupdate&theme=' . $encoded_slug ), 'autoupdate-theme_' . $slug ) : null; 149 | 150 | $prepared_themes[ $slug ] = $theme; 151 | } 152 | 153 | return $prepared_themes; 154 | } 155 | add_action( 'wp_prepare_themes_for_js', 'wp_autoupdates_prepare_themes_for_js' ); 156 | 157 | 158 | /** 159 | * Checks whether plugins manual auto-update is enabled. 160 | */ 161 | function wp_autoupdates_is_plugins_auto_update_enabled() { 162 | $enabled = ! defined( 'WP_DISABLE_PLUGINS_AUTO_UPDATE' ) || ! WP_DISABLE_PLUGINS_AUTO_UPDATE; 163 | 164 | /** 165 | * Filters whether plugins manual auto-update is enabled. 166 | * 167 | * @param bool $enabled True if plugins auto-update is enabled, false otherwise. 168 | */ 169 | return apply_filters( 'wp_plugins_auto_update_enabled', $enabled ); 170 | } 171 | 172 | 173 | /** 174 | * Checks whether themes manual auto-update is enabled. 175 | */ 176 | function wp_autoupdates_is_themes_auto_update_enabled() { 177 | $enabled = ! defined( 'WP_DISABLE_THEMES_AUTO_UPDATE' ) || ! WP_DISABLE_THEMES_AUTO_UPDATE; 178 | 179 | /** 180 | * Filters whether themes manual auto-update is enabled. 181 | * 182 | * @param bool $enabled True if themes auto-update is enabled, false otherwise. 183 | */ 184 | return apply_filters( 'wp_themes_auto_update_enabled', $enabled ); 185 | } 186 | 187 | 188 | /** 189 | * Autoupdate selected plugins. 190 | */ 191 | function wp_autoupdates_selected_plugins( $update, $item ) { 192 | $wp_auto_update_plugins = get_site_option( 'wp_auto_update_plugins', array() ); 193 | if ( in_array( $item->plugin, $wp_auto_update_plugins, true ) && wp_autoupdates_is_plugins_auto_update_enabled() ) { 194 | return true; 195 | } else { 196 | return $update; 197 | } 198 | } 199 | add_filter( 'auto_update_plugin', 'wp_autoupdates_selected_plugins', 10, 2 ); 200 | 201 | 202 | /** 203 | * Autoupdate selected themes. 204 | */ 205 | function wp_autoupdates_selected_themes( $update, $item ) { 206 | $wp_auto_update_themes = get_site_option( 'wp_auto_update_themes', array() ); 207 | if ( in_array( $item->theme, $wp_auto_update_themes, true ) && wp_autoupdates_is_themes_auto_update_enabled() ) { 208 | return true; 209 | } else { 210 | return $update; 211 | } 212 | } 213 | add_filter( 'auto_update_theme', 'wp_autoupdates_selected_themes', 10, 2 ); 214 | 215 | 216 | /** 217 | * Add autoupdate column to plugins screen. 218 | */ 219 | function wp_autoupdates_add_plugins_autoupdates_column( $columns ) { 220 | if ( ! current_user_can( 'update_plugins' ) || ! wp_autoupdates_is_plugins_auto_update_enabled() ) { 221 | return $columns; 222 | } 223 | if ( ! isset( $_GET['plugin_status'] ) || ( 'mustuse' !== $_GET['plugin_status'] && 'dropins' !== $_GET['plugin_status'] ) ) { 224 | $columns['autoupdates_column'] = __( 'Automatic updates', 'wp-autoupdates' ); 225 | } 226 | return $columns; 227 | } 228 | add_filter( is_multisite() ? 'manage_plugins-network_columns' : 'manage_plugins_columns', 'wp_autoupdates_add_plugins_autoupdates_column' ); 229 | 230 | /** 231 | * Render autoupdate column’s content. 232 | */ 233 | function wp_autoupdates_add_plugins_autoupdates_column_content( $column_name, $plugin_file, $plugin_data ) { 234 | if ( ! current_user_can( 'update_plugins' ) || ! wp_autoupdates_is_plugins_auto_update_enabled() ) { 235 | return; 236 | } 237 | if ( 'autoupdates_column' !== $column_name ) { 238 | return; 239 | } 240 | $plugins = get_plugins(); 241 | $plugins_updates = get_site_transient( 'update_plugins' ); 242 | $page = isset( $_GET['paged'] ) && ! empty( $_GET['paged'] ) ? wp_unslash( esc_html( $_GET['paged'] ) ) : ''; 243 | $plugin_status = isset( $_GET['plugin_status'] ) && ! empty( $_GET['plugin_status'] ) ? wp_unslash( esc_html( $_GET['plugin_status'] ) ) : ''; 244 | if ( wp_autoupdates_is_plugins_auto_update_enabled() ) { 245 | if ( ! isset( $plugins[ $plugin_file ] ) ) { 246 | return; 247 | } 248 | $wp_auto_update_plugins = get_site_option( 'wp_auto_update_plugins', array() ); 249 | if ( in_array( $plugin_file, $wp_auto_update_plugins, true ) ) { 250 | $aria_label = esc_attr( 251 | sprintf( 252 | /* translators: Plugin name. */ 253 | _x( 'Disable automatic updates for %s', 'plugin', 'wp-autoupdates' ), 254 | esc_html( $plugins[ $plugin_file ]['Name'] ) 255 | ) 256 | ); 257 | echo '

'; 258 | echo '' . __( 'Auto-updates enabled', 'wp-autoupdates' ) . ''; 259 | echo '
'; 260 | 261 | $update_message = wp_autoupdates_get_update_message(); 262 | if ( isset( $plugins_updates->response[$plugin_file] ) ) { 263 | echo ''; 264 | echo $update_message; 265 | echo '
'; 266 | echo '
'; 267 | } 268 | if ( current_user_can( 'update_plugins', $plugin_file ) ) { 269 | echo sprintf( 270 | '%s', 271 | wp_nonce_url( 'plugins.php?action=autoupdate&plugin=' . urlencode( $plugin_file ) . '&paged=' . $page . '&plugin_status=' . $plugin_status, 'autoupdate-plugin_' . $plugin_file ), 272 | $aria_label, 273 | __( 'Disable', 'wp-autoupdates' ) 274 | ); 275 | } 276 | echo '

'; 277 | } else { 278 | if ( current_user_can( 'update_plugins', $plugin_file ) ) { 279 | $aria_label = esc_attr( 280 | sprintf( 281 | /* translators: Plugin name. */ 282 | _x( 'Enable automatic updates for %s', 'plugin', 'wp-autoupdates' ), 283 | esc_html( $plugins[ $plugin_file ]['Name'] ) 284 | ) 285 | ); 286 | echo '

'; 287 | echo sprintf( 288 | ' %s', 289 | wp_nonce_url( 'plugins.php?action=autoupdate&plugin=' . urlencode( $plugin_file ) . '&paged=' . $page . '&plugin_status=' . $plugin_status, 'autoupdate-plugin_' . $plugin_file ), 290 | $aria_label, 291 | __( 'Enable', 'wp-autoupdates' ) 292 | ); 293 | echo '

'; 294 | } 295 | } 296 | } 297 | } 298 | add_action( 'manage_plugins_custom_column' , 'wp_autoupdates_add_plugins_autoupdates_column_content', 10, 3 ); 299 | 300 | 301 | /** 302 | * Add plugins autoupdates bulk actions 303 | */ 304 | function wp_autoupdates_plugins_bulk_actions( $actions ) { 305 | $actions['enable-autoupdate-selected'] = __( 'Enable auto-updates', 'wp-autoupdates' ); 306 | $actions['disable-autoupdate-selected'] = __( 'Disable auto-updates', 'wp-autoupdates' ); 307 | return $actions; 308 | } 309 | add_action( 'bulk_actions-plugins', 'wp_autoupdates_plugins_bulk_actions' ); 310 | add_action( 'bulk_actions-plugins-network', 'wp_autoupdates_plugins_bulk_actions' ); 311 | 312 | 313 | /** 314 | * Handles auto-updates enabling for plugins 315 | */ 316 | function wp_autoupdates_plugins_enabler() { 317 | $action = isset( $_GET['action'] ) && ! empty( esc_html( $_GET['action'] ) ) ? wp_unslash( esc_html( $_GET['action'] ) ) : ''; 318 | if ( 'autoupdate' === $action ) { 319 | if ( ! current_user_can( 'update_plugins' ) || ! wp_autoupdates_is_plugins_auto_update_enabled() ) { 320 | wp_die( __( 'Sorry, you are not allowed to enable plugins automatic updates.', 'wp-autoupdates' ) ); 321 | } 322 | 323 | if ( is_multisite() && ! is_network_admin() ) { 324 | wp_die( __( 'Please connect to your network admin to manage plugins automatic updates.', 'wp-autoupdates' ) ); 325 | } 326 | 327 | $plugin = ! empty( esc_html( $_GET['plugin'] ) ) ? wp_unslash( esc_html( $_GET['plugin'] ) ) : ''; 328 | $page = isset( $_GET['paged'] ) && ! empty( esc_html( $_GET['paged'] ) ) ? wp_unslash( esc_html( $_GET['paged'] ) ) : ''; 329 | $status = isset( $_GET['plugin_status'] ) && ! empty( esc_html( $_GET['plugin_status'] ) ) ? wp_unslash( esc_html( $_GET['plugin_status'] ) ) : ''; 330 | $s = isset( $_GET['s'] ) && ! empty( esc_html( $_GET['s'] ) ) ? wp_unslash( esc_html( $_GET['s'] ) ) : ''; 331 | 332 | if ( empty( $plugin ) ) { 333 | wp_redirect( self_admin_url( "plugins.php?plugin_status=$status&paged=$page&s=$s" ) ); 334 | exit; 335 | } 336 | 337 | check_admin_referer( 'autoupdate-plugin_' . $plugin ); 338 | $wp_auto_update_plugins = get_site_option( 'wp_auto_update_plugins', array() ); 339 | 340 | if ( in_array( $plugin, $wp_auto_update_plugins, true ) ) { 341 | $wp_auto_update_plugins = array_diff( $wp_auto_update_plugins, array( $plugin ) ); 342 | $action_type = 'disable-autoupdate=true'; 343 | } else { 344 | array_push( $wp_auto_update_plugins, $plugin ); 345 | $action_type = 'enable-autoupdate=true'; 346 | } 347 | update_site_option( 'wp_auto_update_plugins', $wp_auto_update_plugins ); 348 | wp_redirect( self_admin_url( "plugins.php?$action_type&plugin_status=$status&paged=$page&s=$s" ) ); 349 | exit; 350 | } 351 | } 352 | 353 | 354 | /** 355 | * Handles auto-updates enabling for themes 356 | */ 357 | function wp_autoupdates_themes_enabler() { 358 | $action = isset( $_GET['action'] ) && ! empty( esc_html( $_GET['action'] ) ) ? wp_unslash( esc_html( $_GET['action'] ) ) : ''; 359 | if ( 'autoupdate' === $action ) { 360 | if ( ! current_user_can( 'update_themes' ) || ! wp_autoupdates_is_themes_auto_update_enabled() ) { 361 | wp_die( __( 'Sorry, you are not allowed to enable themes automatic updates.', 'wp-autoupdates' ) ); 362 | } 363 | 364 | if ( is_multisite() && ! is_network_admin() ) { 365 | wp_die( __( 'Please connect to your network admin to manage themes automatic updates.', 'wp-autoupdates' ) ); 366 | } 367 | 368 | $theme = ! empty( esc_html( $_GET['theme'] ) ) ? wp_unslash( esc_html( $_GET['theme'] ) ) : ''; 369 | if ( empty( $theme ) ) { 370 | wp_redirect( self_admin_url( 'themes.php' ) ); 371 | exit; 372 | } 373 | 374 | check_admin_referer( 'autoupdate-theme_' . $theme ); 375 | $wp_auto_update_themes = get_site_option( 'wp_auto_update_themes', array() ); 376 | 377 | if ( in_array( $theme, $wp_auto_update_themes, true ) ) { 378 | $wp_auto_update_themes = array_diff( $wp_auto_update_themes, array( $theme ) ); 379 | $action_type = 'disable-autoupdate=true'; 380 | } else { 381 | array_push( $wp_auto_update_themes, $theme ); 382 | $action_type = 'enable-autoupdate=true'; 383 | } 384 | 385 | update_site_option( 'wp_auto_update_themes', $wp_auto_update_themes ); 386 | wp_redirect( self_admin_url( "themes.php?$action_type" ) ); 387 | exit; 388 | } 389 | } 390 | 391 | 392 | /** 393 | * Handle autoupdates enabling 394 | */ 395 | function wp_autoupdates_enabler() { 396 | $pagenow = $GLOBALS['pagenow']; 397 | if ( 'plugins.php' === $pagenow ) { 398 | wp_autoupdates_plugins_enabler(); 399 | } 400 | else if ( 'themes.php' === $pagenow ) { 401 | wp_autoupdates_themes_enabler(); 402 | } 403 | } 404 | add_action( 'admin_init', 'wp_autoupdates_enabler' ); 405 | 406 | 407 | /** 408 | * Handle plugins autoupdates bulk actions 409 | */ 410 | function wp_autoupdates_plugins_bulk_actions_handle( $redirect_to, $doaction, $items ) { 411 | if ( 'enable-autoupdate-selected' === $doaction ) { 412 | if ( ! current_user_can( 'update_plugins' ) || ! wp_autoupdates_is_plugins_auto_update_enabled() ) { 413 | wp_die( __( 'Sorry, you are not allowed to enable plugins automatic updates.', 'wp-autoupdates' ) ); 414 | } 415 | 416 | if ( is_multisite() && ! is_network_admin() ) { 417 | wp_die( __( 'Please connect to your network admin to manage plugins automatic updates.', 'wp-autoupdates' ) ); 418 | } 419 | 420 | check_admin_referer( 'bulk-plugins' ); 421 | 422 | $plugins = ! empty( $items ) ? (array) wp_unslash( $items ) : array(); 423 | $page = isset( $_GET['paged'] ) && ! empty( esc_html( $_GET['paged'] ) ) ? wp_unslash( esc_html( $_GET['paged'] ) ) : ''; 424 | $status = isset( $_GET['plugin_status'] ) && ! empty( esc_html( $_GET['plugin_status'] ) ) ? wp_unslash( esc_html( $_GET['plugin_status'] ) ) : ''; 425 | $s = isset( $_GET['s'] ) && ! empty( esc_html( $_GET['s'] ) ) ? wp_unslash( esc_html( $_GET['s'] ) ) : ''; 426 | 427 | if ( empty( $plugins ) ) { 428 | $redirect_to = self_admin_url( "plugins.php?plugin_status=$status&paged=$page&s=$s" ); 429 | return $redirect_to; 430 | } 431 | 432 | $previous_autoupdated_plugins = get_site_option( 'wp_auto_update_plugins', array() ); 433 | 434 | $new_autoupdated_plugins = array_merge( $previous_autoupdated_plugins, $plugins ); 435 | $new_autoupdated_plugins = array_unique( $new_autoupdated_plugins ); 436 | 437 | update_site_option( 'wp_auto_update_plugins', $new_autoupdated_plugins ); 438 | 439 | $redirect_to = self_admin_url( "plugins.php?enable-autoupdate=true&plugin_status=$status&paged=$page&s=$s" ); 440 | return $redirect_to; 441 | } 442 | 443 | if ( 'disable-autoupdate-selected' === $doaction ) { 444 | if ( ! current_user_can( 'update_plugins' ) || ! wp_autoupdates_is_plugins_auto_update_enabled() ) { 445 | wp_die( __( 'Sorry, you are not allowed to enable plugins automatic updates.', 'wp-autoupdates' ) ); 446 | } 447 | 448 | if ( is_multisite() && ! is_network_admin() ) { 449 | wp_die( __( 'Please connect to your network admin to manage plugins automatic updates.', 'wp-autoupdates' ) ); 450 | } 451 | 452 | check_admin_referer( 'bulk-plugins' ); 453 | 454 | $plugins = ! empty( $items ) ? (array) wp_unslash( $items ) : array(); 455 | $page = isset( $_GET['paged'] ) && ! empty( esc_html( $_GET['paged'] ) ) ? wp_unslash( esc_html( $_GET['paged'] ) ) : ''; 456 | $status = isset( $_GET['plugin_status'] ) && ! empty( esc_html( $_GET['plugin_status'] ) ) ? wp_unslash( esc_html( $_GET['plugin_status'] ) ) : ''; 457 | $s = isset( $_GET['s'] ) && ! empty( esc_html( $_GET['s'] ) ) ? wp_unslash( esc_html( $_GET['s'] ) ) : ''; 458 | 459 | if ( empty( $plugins ) ) { 460 | $redirect_to = self_admin_url( "plugins.php?plugin_status=$status&paged=$page&s=$s" ); 461 | return $redirect_to; 462 | } 463 | 464 | $previous_autoupdated_plugins = get_site_option( 'wp_auto_update_plugins', array() ); 465 | 466 | $new_autoupdated_plugins = array_diff( $previous_autoupdated_plugins, $plugins ); 467 | $new_autoupdated_plugins = array_unique( $new_autoupdated_plugins ); 468 | 469 | update_site_option( 'wp_auto_update_plugins', $new_autoupdated_plugins ); 470 | 471 | $redirect_to = self_admin_url( "plugins.php?disable-autoupdate=true&plugin_status=$status&paged=$page&s=$s" ); 472 | return $redirect_to; 473 | } 474 | 475 | } 476 | add_action( 'handle_bulk_actions-plugins', 'wp_autoupdates_plugins_bulk_actions_handle', 10, 3 ); 477 | add_action( 'handle_bulk_actions-plugins-network', 'wp_autoupdates_plugins_bulk_actions_handle', 10, 3 ); 478 | 479 | 480 | /** 481 | * Handle cleanup when plugin deleted 482 | */ 483 | function wp_autoupdates_plugin_deleted( $plugin_file, $deleted ) { 484 | // Do nothing if the plugin wasn't deleted 485 | if ( ! $deleted ) { 486 | return; 487 | } 488 | 489 | // Remove settings 490 | $wp_auto_update_plugins = get_site_option( 'wp_auto_update_plugins', array() ); 491 | if ( in_array( $plugin_file, $wp_auto_update_plugins, true ) ) { 492 | $wp_auto_update_plugins = array_diff( $wp_auto_update_plugins, array( $plugin_file ) ); 493 | update_site_option( 'wp_auto_update_plugins', $wp_auto_update_plugins ); 494 | } 495 | } 496 | add_action( 'deleted_plugin', 'wp_autoupdates_plugin_deleted', 10, 2 ); 497 | 498 | 499 | /** 500 | * Auto-update notices for plugins 501 | */ 502 | function wp_autoupdates_plugins_notices() { 503 | if ( isset( $_GET['enable-autoupdate'] ) ) { 504 | echo '

'; 505 | _e( 'The selected plugins will now update automatically.', 'wp-autoupdates' ); 506 | echo '

'; 507 | } 508 | if ( isset( $_GET['disable-autoupdate'] ) ) { 509 | echo '

'; 510 | _e( 'The selected plugins won’t automatically update anymore.', 'wp-autoupdates' ); 511 | echo '

'; 512 | } 513 | } 514 | 515 | 516 | /** 517 | * Auto-update notices for themes 518 | */ 519 | function wp_autoupdates_themes_notices() { 520 | if ( isset( $_GET['enable-autoupdate'] ) ) { 521 | echo '

'; 522 | _e( 'The selected themes will now update automatically.', 'wp-autoupdates' ); 523 | echo '

'; 524 | } 525 | if ( isset( $_GET['disable-autoupdate'] ) ) { 526 | echo '

'; 527 | _e( 'The selected themes won’t automatically update anymore.', 'wp-autoupdates' ); 528 | echo '

'; 529 | } 530 | } 531 | 532 | 533 | /** 534 | * Auto-update notices 535 | */ 536 | function wp_autoupdates_notices() { 537 | // Plugins screen 538 | $pagenow = $GLOBALS['pagenow']; 539 | if ( 'plugins.php' === $pagenow ) { 540 | wp_autoupdates_plugins_notices(); 541 | } 542 | else if ( 'themes.php' === $pagenow ) { 543 | wp_autoupdates_themes_notices(); 544 | } 545 | } 546 | add_action( 'admin_notices', 'wp_autoupdates_notices' ); 547 | 548 | /** 549 | * Add views for auto-update enabled/disabled. 550 | * 551 | * This is modeled on `WP_Plugins_List_Table::get_views()`. If this is merged into core, 552 | * then this should be encorporated there. 553 | * 554 | * @global array $totals Counts by plugin_status, set in `WP_Plugins_List_Table::prepare_items()`. 555 | */ 556 | function wp_autoupdates_plugins_status_links( $status_links ) { 557 | global $totals; 558 | 559 | if ( ! current_user_can( 'update_plugins' ) || ! wp_autoupdates_is_plugins_auto_update_enabled() ) { 560 | return $status_links; 561 | } 562 | 563 | /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */ 564 | $all_plugins = apply_filters( 'all_plugins', get_plugins() ); 565 | $wp_autoupdate_plugins = get_site_option( 'wp_auto_update_plugins', array() ); 566 | $wp_autoupdate_plugins = array_intersect( $wp_autoupdate_plugins, array_keys( $all_plugins ) ); 567 | $enabled_count = count( $wp_autoupdate_plugins ); 568 | 569 | // when merged, these counts will need to be set in WP_Plugins_List_Table::prepare_items(). 570 | $counts = array( 571 | 'autoupdate_enabled' => $enabled_count, 572 | 'autoupdate_disabled' => $totals['all'] - $enabled_count, 573 | ); 574 | 575 | // we can't use the global $status set in WP_Plugin_List_Table::__construct() because 576 | // it will be 'all' for our "custom statuses". 577 | $status = isset( $_REQUEST['plugin_status'] ) ? $_REQUEST['plugin_status'] : 'all'; 578 | 579 | foreach ( $counts as $type => $count ) { 580 | if ( 0 === $count ) { 581 | continue; 582 | } 583 | switch( $type ) { 584 | case 'autoupdate_enabled': 585 | /* translators: %s: Number of plugins. */ 586 | $text = _n( 587 | 'Auto-updates Enabled (%s)', 588 | 'Auto-updates Enabled (%s)', 589 | $count, 590 | 'wp-autoupdates' 591 | ); 592 | 593 | break; 594 | case 'autoupdate_disabled': 595 | /* translators: %s: Number of plugins. */ 596 | $text = _n( 597 | 'Auto-updates Disabled (%s)', 598 | 'Auto-updates Disabled (%s)', 599 | $count, 600 | 'wp-autoupdates' 601 | ); 602 | } 603 | 604 | $status_links[ $type ] = sprintf( 605 | "%s", 606 | add_query_arg( 'plugin_status', $type, 'plugins.php' ), 607 | ( $type === $status ) ? ' class="current" aria-current="page"' : '', 608 | sprintf( $text, number_format_i18n( $count ) ) 609 | ); 610 | } 611 | 612 | // make the 'all' status link not current if one of our "custom statuses" is current. 613 | if ( in_array( $status, array_keys( $counts ) ) ) { 614 | $status_links['all'] = str_replace( ' class="current" aria-current="page"', '', $status_links['all'] ); 615 | } 616 | 617 | return $status_links; 618 | } 619 | add_action( is_multisite() ? 'views_plugins-network' : 'views_plugins', 'wp_autoupdates_plugins_status_links' ); 620 | 621 | /** 622 | * Filter plugins shown in the list table when status is 'auto-update-enabled' or 'auto-update-disabled'. 623 | * 624 | * This is modeled on `WP_Plugins_List_Table::prepare_items()`. If this is merged into core, 625 | * then this should be encorporated there. 626 | * 627 | * This action this is hooked to is fired in `wp-admin/plugins.php`. 628 | * 629 | * @global WP_Plugins_List_Table $wp_list_table The global list table object. Set in `wp-admin/plugins.php`. 630 | * @global int $page The current page of plugins displayed. Set in WP_Plugins_List_Table::__construct(). 631 | */ 632 | function wp_autoupdates_plugins_filter_plugins_by_status( $plugins ) { 633 | global $wp_list_table, $page; 634 | 635 | $custom_statuses = array( 636 | 'autoupdate_enabled', 637 | 'autoupdate_disabled', 638 | ); 639 | 640 | if ( ! ( isset( $_REQUEST['plugin_status'] ) && 641 | in_array( $_REQUEST['plugin_status'], $custom_statuses ) ) ) { 642 | // current request is not for one of our statuses. 643 | // nothing to do, so bail. 644 | return; 645 | } 646 | 647 | $wp_auto_update_plugins = get_site_option( 'wp_auto_update_plugins', array() ); 648 | $_plugins = array(); 649 | foreach ( $plugins as $plugin_file => $plugin_data ) { 650 | switch ( $_REQUEST['plugin_status'] ) { 651 | case 'autoupdate_enabled': 652 | if ( in_array( $plugin_file, $wp_auto_update_plugins ) ) { 653 | $_plugins[ $plugin_file ] = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, false, true ); 654 | } 655 | break; 656 | case 'autoupdate_disabled': 657 | if ( ! in_array( $plugin_file, $wp_auto_update_plugins ) ) { 658 | $_plugins[ $plugin_file ] = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, false, true ); 659 | } 660 | break; 661 | } 662 | } 663 | 664 | // set the list table's items array to just those plugins with our custom status. 665 | $wp_list_table->items = $_plugins; 666 | 667 | // now, update the pagination properties of the list table accordingly. 668 | $total_this_page = count( $_plugins ); 669 | 670 | $plugins_per_page = $wp_list_table->get_items_per_page( str_replace( '-', '_', $wp_list_table->screen->id . '_per_page' ), 999 ); 671 | 672 | $start = ( $page - 1 ) * $plugins_per_page; 673 | 674 | if ( $total_this_page > $plugins_per_page ) { 675 | $wp_list_table->items = array_slice( $wp_list_table->items, $start, $plugins_per_page ); 676 | } 677 | 678 | $wp_list_table->set_pagination_args( 679 | array( 680 | 'total_items' => $total_this_page, 681 | 'per_page' => $plugins_per_page, 682 | ) 683 | ); 684 | 685 | return; 686 | } 687 | add_action( 'pre_current_active_plugins', 'wp_autoupdates_plugins_filter_plugins_by_status' ); 688 | 689 | /* 690 | * Populate site health informations 691 | */ 692 | function wp_autoupdates_debug_information( $info ) { 693 | // Plugins 694 | if ( wp_autoupdates_is_plugins_auto_update_enabled() ) { 695 | // Populate plugins informations 696 | $wp_auto_update_plugins = get_site_option( 'wp_auto_update_plugins', array() ); 697 | 698 | $plugins = get_plugins(); 699 | $plugin_updates = get_plugin_updates(); 700 | 701 | foreach ( $plugins as $plugin_path => $plugin ) { 702 | $plugin_part = ( is_plugin_active( $plugin_path ) ) ? 'wp-plugins-active' : 'wp-plugins-inactive'; 703 | 704 | $plugin_version = $plugin['Version']; 705 | $plugin_author = $plugin['Author']; 706 | 707 | $plugin_version_string = __( 'No version or author information is available.', 'wp-autoupdates' ); 708 | $plugin_version_string_debug = __( 'author: (undefined), version: (undefined)', 'wp-autoupdates' ); 709 | 710 | if ( ! empty( $plugin_version ) && ! empty( $plugin_author ) ) { 711 | /* translators: 1: Plugin version number. 2: Plugin author name. */ 712 | $plugin_version_string = sprintf( __( 'Version %1$s by %2$s', 'wp-autoupdates' ), $plugin_version, $plugin_author ); 713 | /* translators: 1: Plugin version number. 2: Plugin author name. */ 714 | $plugin_version_string_debug = sprintf( __( 'version: %1$s, author: %2$s', 'wp-autoupdates' ), $plugin_version, $plugin_author ); 715 | } else { 716 | if ( ! empty( $plugin_author ) ) { 717 | /* translators: %s: Plugin author name. */ 718 | $plugin_version_string = sprintf( __( 'By %s', 'wp-autoupdates' ), $plugin_author ); 719 | /* translators: %s: Plugin author name. */ 720 | $plugin_version_string_debug = sprintf( __( 'author: %s, version: (undefined)', 'wp-autoupdates' ), $plugin_author ); 721 | } 722 | if ( ! empty( $plugin_version ) ) { 723 | /* translators: %s: Plugin version number. */ 724 | $plugin_version_string = sprintf( __( 'Version %s', 'wp-autoupdates' ), $plugin_version ); 725 | /* translators: %s: Plugin version number. */ 726 | $plugin_version_string_debug = sprintf( __( 'author: (undefined), version: %s', 'wp-autoupdates' ), $plugin_version ); 727 | } 728 | } 729 | 730 | if ( array_key_exists( $plugin_path, $plugin_updates ) ) { 731 | /* translators: %s: Latest plugin version number. */ 732 | $plugin_version_string .= ' ' . sprintf( __( '(Latest version: %s)', 'wp-autoupdates' ), $plugin_updates[ $plugin_path ]->update->new_version ); 733 | /* translators: %s: Latest plugin version number. */ 734 | $plugin_version_string_debug .= ' ' . sprintf( __( '(latest version: %s)', 'wp-autoupdates' ), $plugin_updates[ $plugin_path ]->update->new_version ); 735 | } 736 | 737 | if ( in_array( $plugin_path, $wp_auto_update_plugins ) ) { 738 | $plugin_version_string .= ' | ' . sprintf( __( 'Auto-updates enabled', 'wp-autoupdates' ) ); 739 | $plugin_version_string_debug .= sprintf( __( 'auto-updates enabled', 'wp-autoupdates' ) ); 740 | } else { 741 | $plugin_version_string .= ' | ' . sprintf( __( 'Auto-updates disabled', 'wp-autoupdates' ) ); 742 | $plugin_version_string_debug .= sprintf( __( 'auto-updates disabled', 'wp-autoupdates' ) ); 743 | } 744 | 745 | $info[ $plugin_part ]['fields'][ sanitize_text_field( $plugin['Name'] ) ] = array( 746 | 'label' => $plugin['Name'], 747 | 'value' => $plugin_version_string, 748 | 'debug' => $plugin_version_string_debug, 749 | ); 750 | } 751 | } 752 | 753 | if ( wp_autoupdates_is_themes_auto_update_enabled() ) { 754 | // Populate themes informations 755 | $wp_auto_update_themes = get_site_option( 'wp_auto_update_themes', array() ); 756 | 757 | $themes = wp_get_themes(); 758 | $active_theme = wp_get_theme(); 759 | foreach ( $themes as $theme_path => $theme ) { 760 | $theme_version = sanitize_text_field( $theme['Version'] ); 761 | $theme_author = sanitize_text_field( $theme['Author'] ); 762 | 763 | $is_active_theme = $theme->name === $active_theme->name; 764 | if ($is_active_theme) { 765 | $theme_part = 'wp-active-theme'; 766 | 767 | if ( in_array( $theme_path, $wp_auto_update_themes ) ) { 768 | $theme_auto_update_string = sprintf( __( 'Enabled', 'wp-autoupdates' ) ); 769 | } else { 770 | $theme_auto_update_string = sprintf( __( 'Disabled', 'wp-autoupdates' ) ); 771 | } 772 | 773 | $info[ $theme_part ]['fields']['Auto-update'] = array( 774 | 'label' => __( 'Auto-update', 'wp-autoupdates' ), 775 | 'value' => $theme_auto_update_string, 776 | 'debug' => $theme_auto_update_string, 777 | ); 778 | } else { 779 | $theme_part = 'wp-themes-inactive'; 780 | 781 | $theme_version_string = __( 'No version or author information is available.', 'wp-autoupdates' ); 782 | $theme_version_string_debug = __( 'author: (undefined), version: (undefined)', 'wp-autoupdates' ); 783 | 784 | if ( ! empty( $theme_version ) && ! empty( $theme_author ) ) { 785 | /* translators: 1: Theme version number. 2: Theme author name. */ 786 | $theme_version_string = sprintf( __( 'Version %1$s by %2$s', 'wp-autoupdates' ), $theme_version, $theme_author ); 787 | /* translators: 1: Theme version number. 2: Theme author name. */ 788 | $theme_version_string_debug = sprintf( __( 'version: %1$s, author: %2$s', 'wp-autoupdates' ), $theme_version, $theme_author ); 789 | } else { 790 | if ( ! empty( $theme_author ) ) { 791 | /* translators: %s: Theme author name. */ 792 | $theme_version_string = sprintf( __( 'By %s', 'wp-autoupdates' ), $theme_author ); 793 | /* translators: %s: Theme author name. */ 794 | $theme_version_string_debug = sprintf( __( 'author: %s, version: (undefined)', 'wp-autoupdates' ), $theme_author ); 795 | } 796 | if ( ! empty( $theme_version ) ) { 797 | /* translators: %s: Theme version number. */ 798 | $theme_version_string = sprintf( __( 'Version %s', 'wp-autoupdates' ), $theme_version ); 799 | /* translators: %s: Theme version number. */ 800 | $theme_version_string_debug = sprintf( __( 'author: (undefined), version: %s', 'wp-autoupdates' ), $theme_version ); 801 | } 802 | } 803 | 804 | if ( in_array( $theme_path, $wp_auto_update_themes ) ) { 805 | $theme_version_string .= ' | ' . sprintf( __( 'Auto-updates enabled', 'wp-autoupdates' ) ); 806 | $theme_version_string_debug .= sprintf( __( 'auto-updates enabled', 'wp-autoupdates' ) ); 807 | } else { 808 | $theme_version_string .= ' | ' . sprintf( __( 'Auto-updates disabled', 'wp-autoupdates' ) ); 809 | $theme_version_string_debug .= sprintf( __( 'auto-updates disabled', 'wp-autoupdates' ) ); 810 | } 811 | 812 | $theme_name = sanitize_text_field( $theme['Name'] ); 813 | $label_name = sprintf( __( '%1$s (%2$s)', 'wp-autoupdates' ), $theme_name, $theme_path); 814 | $info[ $theme_part ]['fields'][ $theme_name ] = array( 815 | 'label' => $label_name, 816 | 'value' => $theme_version_string, 817 | 'debug' => $theme_version_string_debug, 818 | ); 819 | } 820 | } 821 | } 822 | 823 | // Populate constants informations 824 | $plugins_enabled = defined( 'WP_DISABLE_PLUGINS_AUTO_UPDATE' ) ? WP_DISABLE_PLUGINS_AUTO_UPDATE : __( 'Undefined', 'wp-autoupdates' ); 825 | $info['wp-constants']['fields']['WP_DISABLE_PLUGINS_AUTO_UPDATE'] = array( 826 | 'label' => 'WP_DISABLE_PLUGINS_AUTO_UPDATE', 827 | 'value' => $plugins_enabled, 828 | 'debug' => strtolower( $plugins_enabled ), 829 | ); 830 | 831 | $themes_enabled = defined( 'WP_DISABLE_THEMES_AUTO_UPDATE' ) ? WP_DISABLE_THEMES_AUTO_UPDATE : __( 'Undefined', 'wp-autoupdates' ); 832 | $info['wp-constants']['fields']['WP_DISABLE_THEMES_AUTO_UPDATE'] = array( 833 | 'label' => 'WP_DISABLE_THEMES_AUTO_UPDATE', 834 | 'value' => $themes_enabled, 835 | 'debug' => strtolower( $themes_enabled ), 836 | ); 837 | 838 | return $info; 839 | } 840 | add_filter( 'debug_information', 'wp_autoupdates_debug_information' ); 841 | 842 | 843 | /** 844 | * If we tried to perform plugin updates, check if we should send an email. 845 | * 846 | * @param object $results The result of the plugin updates. 847 | */ 848 | function wp_autoupdates_automatic_updates_complete_notification( $results ) { 849 | $successful_updates = array(); 850 | $failed_updates = array(); 851 | if ( isset( $results['plugin'] ) ) { 852 | foreach ( $results['plugin'] as $update_result ) { 853 | if ( true === $update_result->result ) { 854 | $successful_updates[] = $update_result; 855 | } else { 856 | $failed_updates[] = $update_result; 857 | } 858 | } 859 | if ( empty( $successful_updates ) && empty( $failed_updates ) ) { 860 | return; 861 | } 862 | if ( empty( $failed_updates ) ) { 863 | wp_autoupdates_send_email_notification( 'success', $successful_updates, $failed_updates ); 864 | } elseif ( empty( $successful_updates ) ) { 865 | wp_autoupdates_send_email_notification( 'fail', $successful_updates, $failed_updates ); 866 | } else { 867 | wp_autoupdates_send_email_notification( 'mixed', $successful_updates, $failed_updates ); 868 | } 869 | } 870 | } 871 | add_action( 'automatic_updates_complete', 'wp_autoupdates_automatic_updates_complete_notification' ); 872 | 873 | 874 | /** 875 | * Sends an email upon the completion or failure of a plugin background update. 876 | * 877 | * @param string $type The type of email to send. Can be one of 'success', 'failure', 'mixed'. 878 | * @param array $successful_updates A list of plugin updates that succeeded. 879 | * @param array $failed_updates A list of plugin updates that failed. 880 | */ 881 | function wp_autoupdates_send_email_notification( $type, $successful_updates, $failed_updates ) { 882 | // No updates were attempted. 883 | if ( empty( $successful_updates ) && empty( $failed_updates ) ) { 884 | return; 885 | } 886 | $body = array(); 887 | 888 | switch ( $type ) { 889 | case 'success': 890 | /* translators: %s: Site title. */ 891 | $subject = __( '[%s] Plugins have automatically updated', 'wp-autoupdates' ); 892 | break; 893 | case 'fail': 894 | /* translators: %s: Site title. */ 895 | $subject = __( '[%s] Plugins have failed to update', 'wp-autoupdates' ); 896 | $body[] = sprintf( 897 | /* translators: %s: Home URL. */ 898 | __( 'Howdy! Failures occurred when attempting to update plugins on your site at %s.', 'wp-autoupdates' ), 899 | home_url() 900 | ); 901 | $body[] = "\n"; 902 | $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.', 'wp-autoupdates' ); 903 | break; 904 | case 'mixed': 905 | /* translators: %s: Site title. */ 906 | $subject = __( '[%s] Some plugins have automatically updated', 'wp-autoupdates' ); 907 | $body[] = sprintf( 908 | /* translators: %s: Home URL. */ 909 | __( 'Howdy! There were some failures while attempting to update plugins on your site at %s.', 'wp-autoupdates' ), 910 | home_url() 911 | ); 912 | $body[] = "\n"; 913 | $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.', 'wp-autoupdates' ); 914 | $body[] = "\n"; 915 | break; 916 | } 917 | 918 | if ( in_array( $type, array( 'fail', 'mixed' ), true ) && ! empty( $failed_updates ) ) { 919 | $body[] = __( 'The following plugins failed to update:' ); 920 | // List failed updates. 921 | foreach ( $failed_updates as $item ) { 922 | /* translators: %s: Name of the related plugin. */ 923 | $body[] = ' ' . sprintf( __( '- %s', 'wp-autoupdates' ), $item->name ); 924 | } 925 | $body[] = "\n"; 926 | } 927 | if ( in_array( $type, array( 'success', 'mixed' ), true ) && ! empty( $successful_updates ) ) { 928 | $body[] = __( 'The following plugins were successfully updated:' ); 929 | // List successful updates. 930 | foreach ( $successful_updates as $plugin ) { 931 | /* translators: %s: Name of the related plugin. */ 932 | $body[] = ' ' . sprintf( __( '- %s', 'wp-autoupdates' ), $plugin->name ); 933 | } 934 | } 935 | $body[] = "\n"; 936 | 937 | // Add a note about the support forums. 938 | $body[] = __( 'If you experience any issues or need support, the volunteers in the WordPress.org support forums may be able to help.', 'wp-autoupdates' ); 939 | $body[] = __( 'https://wordpress.org/support/forums/', 'wp-autoupdates' ); 940 | $body[] = "\n" . __( 'The WordPress Team', 'wp-autoupdates' ); 941 | 942 | $body = implode( "\n", $body ); 943 | $to = get_site_option( 'admin_email' ); 944 | $subject = sprintf( $subject, wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ) ); 945 | $headers = ''; 946 | 947 | $email = compact( 'to', 'subject', 'body', 'headers' ); 948 | 949 | /** 950 | * Filters the email sent following an automatic background plugin update. 951 | * @param array $email { 952 | * Array of email arguments that will be passed to wp_mail(). 953 | * 954 | * @type string $to The email recipient. An array of emails 955 | * can be returned, as handled by wp_mail(). 956 | * @type string $subject The email's subject. 957 | * @type string $body The email message body. 958 | * @type string $headers Any email headers, defaults to no headers. 959 | * } 960 | * @param string $type The type of email being sent. Can be one of 961 | * 'success', 'fail', 'mixed'. 962 | * @param object $successful_updates The updates that succeded. 963 | * @param object $failed_updates The updates that failed. 964 | */ 965 | $email = apply_filters( 'wp_autoupdates_notifications_email', $email, $type, $successful_updates, $failed_updates ); 966 | wp_mail( $email['to'], wp_specialchars_decode( $email['subject'] ), $email['body'], $email['headers'] ); 967 | } 968 | 969 | 970 | /** 971 | * Determines the appropriate update message to be displayed. 972 | * 973 | * @return string The update message to be shown. 974 | */ 975 | function wp_autoupdates_get_update_message() { 976 | $next_update_time = wp_next_scheduled( 'wp_version_check' ); 977 | 978 | // Check if event exists. 979 | if ( false === $next_update_time ) { 980 | return __( 'There may be a problem with WP-Cron. Automatic update not scheduled.', 'wp-autoupdates' ); 981 | } 982 | 983 | // See if cron is disabled 984 | $cron_disabled = defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON; 985 | if ( $cron_disabled ) { 986 | return __( 'WP-Cron is disabled. Automatic updates not available.', 'wp-autoupdates' ); 987 | } 988 | 989 | $time_to_next_update = human_time_diff( intval( $next_update_time ) ); 990 | 991 | // See if cron is overdue. 992 | $overdue = (time() - $next_update_time) > 0; 993 | if ( $overdue ) { 994 | return sprintf( 995 | /* translators: Duration that WP-Cron has been overdue. */ 996 | __( 'There may be a problem with WP-Cron. Automatic update overdue by %s.', 'wp-autoupdates' ), 997 | $time_to_next_update 998 | ); 999 | } else { 1000 | return sprintf( 1001 | /* translators: Time until the next update. */ 1002 | __( 'Automatic update scheduled in %s.', 'wp-autoupdates' ), 1003 | $time_to_next_update 1004 | ); 1005 | } 1006 | } 1007 | --------------------------------------------------------------------------------