<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[44524] trunk/src: Bootstrap/Load: Introduce fatal error recovery mechanism allowing users to still log in to their admin dashboard.</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/44524">44524</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/44524","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>flixos90</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2019-01-09 20:04:55 +0000 (Wed, 09 Jan 2019)</dd>
</dl>
<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Bootstrap/Load: Introduce fatal error recovery mechanism allowing users to still log in to their admin dashboard.
This changeset introduces a `WP_Shutdown_Handler` class that detects fatal errors and which extension (plugin or theme) causes them. Such an error is then recorded, and an error message is displayed. Subsequently, in certain protected areas, for example the admin, the broken extension will be paused, ensuring that the website is still usable in the respective area. The major benefit is that this mechanism allows site owners to still log in to their website, to fix the problem by either disabling the extension or solving the bug and then resuming the extension.
Extensions are only paused in certain designated areas. The frontend for example stays unaffected, as it is impossible to know what pausing the extension would cause to be missing, so it might be preferrable to clearly see that the website is temporarily not accessible instead.
The fatal error recovery is especially important in scope of encouraging the switch to a maintained PHP version, as not necessarily every WordPress extension is compatible with all PHP versions. If problems occur now, non-technical site owners that do not have immediate access to the codebase are not locked out of their site and can at least temporarily solve the problem quickly.
Websites that have custom requirements in that regard can implement their own shutdown handler by adding a `shutdown-handler.php` drop-in that returns the handler instance to use, which must be based on a class that inherits `WP_Shutdown_Handler`. That handler will then be used in place of the default one.
Websites that would like to modify specifically the error template displayed in the frontend can add a `php-error.php` drop-in that works similarly to the existing `db-error.php` drop-in.
Props afragen, bradleyt, flixos90, ocean90, schlessera, SergeyBiryukov, spacedmonkey.
Fixes <a href="https://core.trac.wordpress.org/ticket/44458">#44458</a>.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpadmincsslisttablescss">trunk/src/wp-admin/css/list-tables.css</a></li>
<li><a href="#trunksrcwpadminincludesadminfiltersphp">trunk/src/wp-admin/includes/admin-filters.php</a></li>
<li><a href="#trunksrcwpadminincludesclasswppluginslisttablephp">trunk/src/wp-admin/includes/class-wp-plugins-list-table.php</a></li>
<li><a href="#trunksrcwpadminincludespluginphp">trunk/src/wp-admin/includes/plugin.php</a></li>
<li><a href="#trunksrcwpadminincludesthemephp">trunk/src/wp-admin/includes/theme.php</a></li>
<li><a href="#trunksrcwpadminpluginsphp">trunk/src/wp-admin/plugins.php</a></li>
<li><a href="#trunksrcwpadminthemesphp">trunk/src/wp-admin/themes.php</a></li>
<li><a href="#trunksrcwpincludescapabilitiesphp">trunk/src/wp-includes/capabilities.php</a></li>
<li><a href="#trunksrcwpincludesclasswpthemephp">trunk/src/wp-includes/class-wp-theme.php</a></li>
<li><a href="#trunksrcwpincludesloadphp">trunk/src/wp-includes/load.php</a></li>
<li><a href="#trunksrcwpincludesmsloadphp">trunk/src/wp-includes/ms-load.php</a></li>
<li><a href="#trunksrcwpincludestemplateloaderphp">trunk/src/wp-includes/template-loader.php</a></li>
<li><a href="#trunksrcwpsettingsphp">trunk/src/wp-settings.php</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesclasswppausedextensionsstoragephp">trunk/src/wp-includes/class-wp-paused-extensions-storage.php</a></li>
<li><a href="#trunksrcwpincludesclasswpshutdownhandlerphp">trunk/src/wp-includes/class-wp-shutdown-handler.php</a></li>
<li><a href="#trunksrcwpincludeserrorprotectionphp">trunk/src/wp-includes/error-protection.php</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpadmincsslisttablescss"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/css/list-tables.css</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/css/list-tables.css 2019-01-09 19:05:41 UTC (rev 44523)
+++ trunk/src/wp-admin/css/list-tables.css 2019-01-09 20:04:55 UTC (rev 44524)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1301,6 +1301,31 @@
</span><span class="cx" style="display: block; padding: 0 10px"> text-decoration: underline;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+.plugins tr.paused th.check-column {
+ border-left: 4px solid #d54e21;
+}
+
+.plugins tr.paused th,
+.plugins tr.paused td {
+ background-color: #fef7f1;
+}
+
+.plugins tr.paused .plugin-title,
+.plugins .paused .dashicons-warning {
+ color: #dc3232;
+}
+
+.plugins .paused .error-display p,
+.plugins .paused .error-display code {
+ font-size: 90%;
+ font-style: italic;
+ color: rgb( 0, 0, 0, 0.7 );
+}
+
+.plugins .resume-link {
+ color: #dc3232;
+}
+
</ins><span class="cx" style="display: block; padding: 0 10px"> .plugin-card .update-now:before {
</span><span class="cx" style="display: block; padding: 0 10px"> color: #f56e28;
</span><span class="cx" style="display: block; padding: 0 10px"> content: "\f463";
</span></span></pre></div>
<a id="trunksrcwpadminincludesadminfiltersphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/includes/admin-filters.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/includes/admin-filters.php 2019-01-09 19:05:41 UTC (rev 44523)
+++ trunk/src/wp-admin/includes/admin-filters.php 2019-01-09 20:04:55 UTC (rev 44524)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -123,6 +123,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'load-themes.php', 'wp_theme_update_rows', 20 ); // After wp_update_themes() is called.
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'admin_notices', 'update_nag', 3 );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+add_action( 'admin_notices', 'paused_plugins_notice', 5 );
+add_action( 'admin_notices', 'paused_themes_notice', 5 );
</ins><span class="cx" style="display: block; padding: 0 10px"> add_action( 'admin_notices', 'maintenance_nag', 10 );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> add_filter( 'update_footer', 'core_update_footer' );
</span></span></pre></div>
<a id="trunksrcwpadminincludesclasswppluginslisttablephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/includes/class-wp-plugins-list-table.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/includes/class-wp-plugins-list-table.php 2019-01-09 19:05:41 UTC (rev 44523)
+++ trunk/src/wp-admin/includes/class-wp-plugins-list-table.php 2019-01-09 20:04:55 UTC (rev 44524)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -40,7 +40,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $status = 'all';
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], array( 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search' ) ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], array( 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search', 'paused' ) ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> $status = $_REQUEST['plugin_status'];
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -99,6 +99,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'upgrade' => array(),
</span><span class="cx" style="display: block; padding: 0 10px"> 'mustuse' => array(),
</span><span class="cx" style="display: block; padding: 0 10px"> 'dropins' => array(),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'paused' => array(),
</ins><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $screen = $this->screen;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -209,6 +210,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> if ( $show_network_active ) {
</span><span class="cx" style="display: block; padding: 0 10px"> // On the non-network screen, show network-active plugins if allowed
</span><span class="cx" style="display: block; padding: 0 10px"> $plugins['active'][ $plugin_file ] = $plugin_data;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( is_plugin_paused( $plugin_file ) ) {
+ $plugins['paused'][ $plugin_file ] = $plugin_data;
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> } else {
</span><span class="cx" style="display: block; padding: 0 10px"> // On the non-network screen, filter out network-active plugins
</span><span class="cx" style="display: block; padding: 0 10px"> unset( $plugins['all'][ $plugin_file ] );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -218,6 +222,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> // On the non-network screen, populate the active list with plugins that are individually activated
</span><span class="cx" style="display: block; padding: 0 10px"> // On the network-admin screen, populate the active list with plugins that are network activated
</span><span class="cx" style="display: block; padding: 0 10px"> $plugins['active'][ $plugin_file ] = $plugin_data;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( is_plugin_paused( $plugin_file ) ) {
+ $plugins['paused'][ $plugin_file ] = $plugin_data;
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> } else {
</span><span class="cx" style="display: block; padding: 0 10px"> if ( isset( $recently_activated[ $plugin_file ] ) ) {
</span><span class="cx" style="display: block; padding: 0 10px"> // Populate the recently activated list with plugins that have been recently activated
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -438,6 +445,10 @@
</span><span class="cx" style="display: block; padding: 0 10px"> case 'dropins':
</span><span class="cx" style="display: block; padding: 0 10px"> $text = _n( 'Drop-ins <span class="count">(%s)</span>', 'Drop-ins <span class="count">(%s)</span>', $count );
</span><span class="cx" style="display: block; padding: 0 10px"> break;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ case 'paused':
+ /* translators: %s: plugin count */
+ $text = _n( 'Paused <span class="count">(%s)</span>', 'Paused <span class="count">(%s)</span>', $count );
+ break;
</ins><span class="cx" style="display: block; padding: 0 10px"> case 'upgrade':
</span><span class="cx" style="display: block; padding: 0 10px"> $text = _n( 'Update Available <span class="count">(%s)</span>', 'Update Available <span class="count">(%s)</span>', $count );
</span><span class="cx" style="display: block; padding: 0 10px"> break;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -625,11 +636,19 @@
</span><span class="cx" style="display: block; padding: 0 10px"> /* translators: %s: plugin name */
</span><span class="cx" style="display: block; padding: 0 10px"> $actions['deactivate'] = '<a href="' . wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'deactivate-plugin_' . $plugin_file ) . '" aria-label="' . esc_attr( sprintf( _x( 'Network Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Network Deactivate' ) . '</a>';
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( current_user_can( 'manage_network_plugins' ) && count_paused_plugin_sites_for_network( $plugin_file ) ) {
+ /* translators: %s: plugin name */
+ $actions['resume'] = '<a class="resume-link" href="' . wp_nonce_url( 'plugins.php?action=resume&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'resume-plugin_' . $plugin_file ) . '" aria-label="' . esc_attr( sprintf( _x( 'Network Resume %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Network Resume' ) . '</a>';
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> } else {
</span><span class="cx" style="display: block; padding: 0 10px"> if ( current_user_can( 'manage_network_plugins' ) ) {
</span><span class="cx" style="display: block; padding: 0 10px"> /* translators: %s: plugin name */
</span><span class="cx" style="display: block; padding: 0 10px"> $actions['activate'] = '<a href="' . wp_nonce_url( 'plugins.php?action=activate&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'activate-plugin_' . $plugin_file ) . '" class="edit" aria-label="' . esc_attr( sprintf( _x( 'Network Activate %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Network Activate' ) . '</a>';
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( current_user_can( 'manage_network_plugins' ) && count_paused_plugin_sites_for_network( $plugin_file ) ) {
+ /* translators: %s: plugin name */
+ $actions['resume'] = '<a class="resume-link" href="' . wp_nonce_url( 'plugins.php?action=resume&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'resume-plugin_' . $plugin_file ) . '" aria-label="' . esc_attr( sprintf( _x( 'Network Resume %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Network Resume' ) . '</a>';
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> if ( current_user_can( 'delete_plugins' ) && ! is_plugin_active( $plugin_file ) ) {
</span><span class="cx" style="display: block; padding: 0 10px"> /* translators: %s: plugin name */
</span><span class="cx" style="display: block; padding: 0 10px"> $actions['delete'] = '<a href="' . wp_nonce_url( 'plugins.php?action=delete-selected&checked[]=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'bulk-plugins' ) . '" class="delete" aria-label="' . esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Delete' ) . '</a>';
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -640,6 +659,10 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $actions = array(
</span><span class="cx" style="display: block; padding: 0 10px"> 'network_active' => __( 'Network Active' ),
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( ! $restrict_network_only && current_user_can( 'resume_plugin' ) && is_plugin_paused( $plugin_file ) ) {
+ /* translators: %s: plugin name */
+ $actions['resume'] = '<a class="resume-link" href="' . wp_nonce_url( 'plugins.php?action=resume&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'resume-plugin_' . $plugin_file ) . '" aria-label="' . esc_attr( sprintf( _x( 'Resume %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Resume' ) . '</a>';
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> } elseif ( $restrict_network_only ) {
</span><span class="cx" style="display: block; padding: 0 10px"> $actions = array(
</span><span class="cx" style="display: block; padding: 0 10px"> 'network_only' => __( 'Network Only' ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -649,6 +672,10 @@
</span><span class="cx" style="display: block; padding: 0 10px"> /* translators: %s: plugin name */
</span><span class="cx" style="display: block; padding: 0 10px"> $actions['deactivate'] = '<a href="' . wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'deactivate-plugin_' . $plugin_file ) . '" aria-label="' . esc_attr( sprintf( _x( 'Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Deactivate' ) . '</a>';
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( current_user_can( 'resume_plugin' ) && is_plugin_paused( $plugin_file ) ) {
+ /* translators: %s: plugin name */
+ $actions['resume'] = '<a class="resume-link" href="' . wp_nonce_url( 'plugins.php?action=resume&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'resume-plugin_' . $plugin_file ) . '" aria-label="' . esc_attr( sprintf( _x( 'Resume %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Resume' ) . '</a>';
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> } else {
</span><span class="cx" style="display: block; padding: 0 10px"> if ( current_user_can( 'activate_plugin', $plugin_file ) ) {
</span><span class="cx" style="display: block; padding: 0 10px"> /* translators: %s: plugin name */
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -755,6 +782,12 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $class .= ' update';
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $paused = is_plugin_paused( $plugin_file );
+ $paused_on_network_sites_count = $screen->in_admin( 'network' ) ? count_paused_plugin_sites_for_network( $plugin_file ) : 0;
+ if ( $paused || $paused_on_network_sites_count ) {
+ $class .= ' paused';
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> $plugin_slug = isset( $plugin_data['slug'] ) ? $plugin_data['slug'] : sanitize_title( $plugin_name );
</span><span class="cx" style="display: block; padding: 0 10px"> printf(
</span><span class="cx" style="display: block; padding: 0 10px"> '<tr class="%s" data-slug="%s" data-plugin="%s">',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -833,12 +866,54 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @param array $plugin_data An array of plugin data.
</span><span class="cx" style="display: block; padding: 0 10px"> * @param string $status Status of the plugin. Defaults are 'All', 'Active',
</span><span class="cx" style="display: block; padding: 0 10px"> * 'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * 'Drop-ins', 'Search'.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * 'Drop-ins', 'Search', 'Paused'.
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> $plugin_meta = apply_filters( 'plugin_row_meta', $plugin_meta, $plugin_file, $plugin_data, $status );
</span><span class="cx" style="display: block; padding: 0 10px"> echo implode( ' | ', $plugin_meta );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- echo '</div></td>';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ echo '</div>';
+
+ if ( $paused || $paused_on_network_sites_count ) {
+ $notice_text = __( 'This plugin failed to load properly and was paused within the admin backend.' );
+ if ( $screen->in_admin( 'network' ) && $paused_on_network_sites_count ) {
+ $notice_text = sprintf(
+ /* translators: %s: number of sites */
+ _n( 'This plugin failed to load properly and was paused within the admin backend for %s site.', 'This plugin failed to load properly and was paused within the admin backend for %s sites.', $paused_on_network_sites_count ),
+ number_format_i18n( $paused_on_network_sites_count )
+ );
+ }
+
+ printf( '<p><span class="dashicons dashicons-warning"></span> <strong>%s</strong></p>', $notice_text );
+
+ $error = wp_get_plugin_error( $plugin_file );
+
+ if ( false !== $error ) {
+ $constants = get_defined_constants( true );
+ $constants = isset( $constants['Core'] ) ? $constants['Core'] : $constants['internal'];
+
+ foreach ( $constants as $constant => $value ) {
+ if ( 0 === strpos( $constant, 'E_' ) ) {
+ $core_errors[ $value ] = $constant;
+ }
+ }
+
+ $error['type'] = $core_errors[ $error['type'] ];
+
+ printf(
+ '<div class="error-display"><p>%s</p></div>',
+ sprintf(
+ /* translators: 1: error type, 2: error line number, 3: error file name, 4: error message */
+ __( 'The plugin caused an error of type %1$s in line %2$s of the file %3$s. Error message: %4$s' ),
+ "<code>{$error['type']}</code>",
+ "<code>{$error['line']}</code>",
+ "<code>{$error['file']}</code>",
+ "<code>{$error['message']}</code>"
+ )
+ );
+ }
+ }
+
+ echo '</td>';
</ins><span class="cx" style="display: block; padding: 0 10px"> break;
</span><span class="cx" style="display: block; padding: 0 10px"> default:
</span><span class="cx" style="display: block; padding: 0 10px"> $classes = "$column_name column-$column_name $class";
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -871,7 +946,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @param array $plugin_data An array of plugin data.
</span><span class="cx" style="display: block; padding: 0 10px"> * @param string $status Status of the plugin. Defaults are 'All', 'Active',
</span><span class="cx" style="display: block; padding: 0 10px"> * 'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * 'Drop-ins', 'Search'.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * 'Drop-ins', 'Search', 'Paused'.
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> do_action( 'after_plugin_row', $plugin_file, $plugin_data, $status );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -887,7 +962,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @param array $plugin_data An array of plugin data.
</span><span class="cx" style="display: block; padding: 0 10px"> * @param string $status Status of the plugin. Defaults are 'All', 'Active',
</span><span class="cx" style="display: block; padding: 0 10px"> * 'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * 'Drop-ins', 'Search'.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * 'Drop-ins', 'Search', 'Paused'.
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> do_action( "after_plugin_row_{$plugin_file}", $plugin_file, $plugin_data, $status );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunksrcwpadminincludespluginphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/includes/plugin.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/includes/plugin.php 2019-01-09 19:05:41 UTC (rev 44523)
+++ trunk/src/wp-admin/includes/plugin.php 2019-01-09 20:04:55 UTC (rev 44524)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -438,12 +438,14 @@
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> function _get_dropins() {
</span><span class="cx" style="display: block; padding: 0 10px"> $dropins = array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- 'advanced-cache.php' => array( __( 'Advanced caching plugin.' ), 'WP_CACHE' ), // WP_CACHE
- 'db.php' => array( __( 'Custom database class.' ), true ), // auto on load
- 'db-error.php' => array( __( 'Custom database error message.' ), true ), // auto on error
- 'install.php' => array( __( 'Custom installation script.' ), true ), // auto on installation
- 'maintenance.php' => array( __( 'Custom maintenance message.' ), true ), // auto on maintenance
- 'object-cache.php' => array( __( 'External object cache.' ), true ), // auto on load
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'advanced-cache.php' => array( __( 'Advanced caching plugin.' ), 'WP_CACHE' ), // WP_CACHE
+ 'db.php' => array( __( 'Custom database class.' ), true ), // auto on load
+ 'db-error.php' => array( __( 'Custom database error message.' ), true ), // auto on error
+ 'install.php' => array( __( 'Custom installation script.' ), true ), // auto on installation
+ 'maintenance.php' => array( __( 'Custom maintenance message.' ), true ), // auto on maintenance
+ 'object-cache.php' => array( __( 'External object cache.' ), true ), // auto on load
+ 'php-error.php' => array( __( 'Custom PHP error message.' ), true ), // auto on error
+ 'shutdown-handler.php' => array( __( 'Custom PHP shutdown handler.' ), true ), // auto on error
</ins><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> if ( is_multisite() ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -497,6 +499,84 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Determines whether a plugin is technically active but was paused while
+ * loading.
+ *
+ * For more information on this and similar theme functions, check out
+ * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
+ * Conditional Tags} article in the Theme Developer Handbook.
+ *
+ * @since 5.1.0
+ *
+ * @param string $plugin Path to the plugin file relative to the plugins directory.
+ * @return bool True, if in the list of paused plugins. False, not in the list.
+ */
+function is_plugin_paused( $plugin ) {
+ if ( ! isset( $GLOBALS['_paused_plugins'] ) ) {
+ return false;
+ }
+
+ if ( ! is_plugin_active( $plugin ) && ! is_plugin_active_for_network( $plugin ) ) {
+ return false;
+ }
+
+ list( $plugin ) = explode( '/', $plugin );
+
+ return array_key_exists( $plugin, $GLOBALS['_paused_plugins'] );
+}
+
+/**
+ * Gets the error that was recorded for a paused plugin.
+ *
+ * @since 5.1.0
+ *
+ * @param string $plugin Path to the plugin file relative to the plugins
+ * directory.
+ * @return array|false Array of error information as it was returned by
+ * `error_get_last()`, or false if none was recorded.
+ */
+function wp_get_plugin_error( $plugin ) {
+ if ( ! isset( $GLOBALS['_paused_plugins'] ) ) {
+ return false;
+ }
+
+ list( $plugin ) = explode( '/', $plugin );
+
+ if ( ! array_key_exists( $plugin, $GLOBALS['_paused_plugins'] ) ) {
+ return false;
+ }
+
+ return $GLOBALS['_paused_plugins'][ $plugin ];
+}
+
+/**
+ * Gets the number of sites on which a specific plugin is paused.
+ *
+ * @since 5.1.0
+ *
+ * @param string $plugin Path to the plugin file relative to the plugins directory.
+ * @return int Site count.
+ */
+function count_paused_plugin_sites_for_network( $plugin ) {
+ if ( ! is_multisite() ) {
+ return is_plugin_paused( $plugin ) ? 1 : 0;
+ }
+
+ list( $plugin ) = explode( '/', $plugin );
+
+ $query_args = array(
+ 'count' => true,
+ 'number' => 0,
+ 'network_id' => get_current_network_id(),
+ 'meta_query' => array(
+ wp_paused_plugins()->get_site_meta_query_clause( $plugin ),
+ ),
+ );
+
+ return get_sites( $query_args );
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Determines whether the plugin is active for the entire network.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * Only plugins installed in the plugins/ folder can be active.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -693,6 +773,11 @@
</span><span class="cx" style="display: block; padding: 0 10px"> continue;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // Clean up the database before deactivating the plugin.
+ if ( is_plugin_paused( $plugin ) ) {
+ resume_plugin( $plugin );
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> $network_deactivating = false !== $network_wide && is_plugin_active_for_network( $plugin );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> if ( ! $silent ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -887,6 +972,11 @@
</span><span class="cx" style="display: block; padding: 0 10px"> uninstall_plugin( $plugin_file );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // Clean up the database before removing the plugin.
+ if ( is_plugin_paused( $plugin_file ) ) {
+ resume_plugin( $plugin_file );
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px"> * Fires immediately before a plugin deletion attempt.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -960,6 +1050,57 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Tries to resume a single plugin.
+ *
+ * If a redirect was provided, we first ensure the plugin does not throw fatal
+ * errors anymore.
+ *
+ * The way it works is by setting the redirection to the error before trying to
+ * include the plugin file. If the plugin fails, then the redirection will not
+ * be overwritten with the success message and the plugin will not be resumed.
+ *
+ * @since 5.1.0
+ *
+ * @param string $plugin Single plugin to resume.
+ * @param string $redirect Optional. URL to redirect to. Default empty string.
+ * @param bool $network_wide Optional. Whether to resume the plugin for the entire
+ * network. Default false.
+ * @return bool|WP_Error True on success, false if `$plugin` was not paused,
+ * `WP_Error` on failure.
+ */
+function resume_plugin( $plugin, $redirect = '', $network_wide = false ) {
+ /*
+ * We'll override this later if the plugin could be included without
+ * creating a fatal error.
+ */
+ if ( ! empty( $redirect ) ) {
+ wp_redirect(
+ add_query_arg(
+ '_error_nonce',
+ wp_create_nonce( 'plugin-resume-error_' . $plugin ),
+ $redirect
+ )
+ );
+
+ // Load the plugin to test whether it throws a fatal error.
+ ob_start();
+ plugin_sandbox_scrape( $plugin );
+ ob_clean();
+ }
+
+ $result = wp_forget_extension_error( 'plugins', $plugin, $network_wide );
+
+ if ( ! $result ) {
+ return new WP_Error(
+ 'could_not_resume_plugin',
+ __( 'Could not resume the plugin.' )
+ );
+ }
+
+ return true;
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Validate active plugins
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * Validate all active plugins, deactivates invalid and
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2066,3 +2207,33 @@
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> WP_Privacy_Policy_Content::add( $plugin_name, $policy_text );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/**
+ * Renders an admin notice in case some plugins have been paused due to errors.
+ *
+ * @since 5.1.0
+ */
+function paused_plugins_notice() {
+ if ( 'plugins.php' === $GLOBALS['pagenow'] ) {
+ return;
+ }
+
+ if ( ! current_user_can( 'deactivate_plugins' ) ) {
+ return;
+ }
+
+ if ( ! isset( $GLOBALS['_paused_plugins'] ) || empty( $GLOBALS['_paused_plugins'] ) ) {
+ return;
+ }
+
+ printf(
+ '<div class="notice notice-error"><p><strong>%s</strong><br>%s</p><p>%s</p></div>',
+ __( 'One or more plugins failed to load properly.' ),
+ __( 'You can find more details and make changes on the Plugins screen.' ),
+ sprintf(
+ '<a href="%s">%s</a>',
+ admin_url( 'plugins.php?plugin_status=paused' ),
+ 'Go to the Plugins screen'
+ )
+ );
+}
</ins></span></pre></div>
<a id="trunksrcwpadminincludesthemephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/includes/theme.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/includes/theme.php 2019-01-09 19:05:41 UTC (rev 44523)
+++ trunk/src/wp-admin/includes/theme.php 2019-01-09 20:04:55 UTC (rev 44524)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -763,3 +763,127 @@
</span><span class="cx" style="display: block; padding: 0 10px"> </script>
</span><span class="cx" style="display: block; padding: 0 10px"> <?php
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/**
+ * Determines whether a theme is technically active but was paused while
+ * loading.
+ *
+ * For more information on this and similar theme functions, check out
+ * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
+ * Conditional Tags} article in the Theme Developer Handbook.
+ *
+ * @since 5.1.0
+ *
+ * @param string $theme Path to the theme directory relative to the themes directory.
+ * @return bool True, if in the list of paused themes. False, not in the list.
+ */
+function is_theme_paused( $theme ) {
+ if ( ! isset( $GLOBALS['_paused_themes'] ) ) {
+ return false;
+ }
+
+ if ( $theme !== get_stylesheet() && $theme !== get_template() ) {
+ return false;
+ }
+
+ return array_key_exists( $theme, $GLOBALS['_paused_themes'] );
+}
+
+/**
+ * Gets the error that was recorded for a paused theme.
+ *
+ * @since 5.1.0
+ *
+ * @param string $theme Path to the theme directory relative to the themes
+ * directory.
+ * @return array|false Array of error information as it was returned by
+ * `error_get_last()`, or false if none was recorded.
+ */
+function wp_get_theme_error( $theme ) {
+ if ( ! isset( $GLOBALS['_paused_themes'] ) ) {
+ return false;
+ }
+
+ if ( ! array_key_exists( $theme, $GLOBALS['_paused_themes'] ) ) {
+ return false;
+ }
+
+ return $GLOBALS['_paused_themes'][ $theme ];
+}
+
+/**
+ * Gets the number of sites on which a specific theme is paused.
+ *
+ * @since 5.1.0
+ *
+ * @param string $theme Path to the theme directory relative to the themes directory.
+ * @return int Site count.
+ */
+function count_paused_theme_sites_for_network( $theme ) {
+ if ( ! is_multisite() ) {
+ return is_theme_paused( $theme ) ? 1 : 0;
+ }
+
+ $query_args = array(
+ 'count' => true,
+ 'number' => 0,
+ 'network_id' => get_current_network_id(),
+ 'meta_query' => array(
+ wp_paused_themes()->get_site_meta_query_clause( $theme ),
+ ),
+ );
+
+ return get_sites( $query_args );
+}
+
+/**
+ * Tries to resume a single theme.
+ *
+ * @since 5.1.0
+ *
+ * @param string $theme Single theme to resume.
+ * @return bool|WP_Error True on success, false if `$theme` was not paused,
+ * `WP_Error` on failure.
+ */
+function resume_theme( $theme ) {
+ $result = wp_forget_extension_error( 'themes', $theme );
+
+ if ( ! $result ) {
+ return new WP_Error(
+ 'could_not_resume_theme',
+ __( 'Could not resume the theme.' )
+ );
+ }
+
+ return true;
+}
+
+/**
+ * Renders an admin notice in case some themes have been paused due to errors.
+ *
+ * @since 5.1.0
+ */
+function paused_themes_notice() {
+ if ( 'themes.php' === $GLOBALS['pagenow'] ) {
+ return;
+ }
+
+ if ( ! current_user_can( 'switch_themes' ) ) {
+ return;
+ }
+
+ if ( ! isset( $GLOBALS['_paused_themes'] ) || empty( $GLOBALS['_paused_themes'] ) ) {
+ return;
+ }
+
+ printf(
+ '<div class="notice notice-error"><p><strong>%s</strong><br>%s</p><p>%s</p></div>',
+ __( 'One or more themes failed to load properly.' ),
+ __( 'You can find more details and make changes on the Themes screen.' ),
+ sprintf(
+ '<a href="%s">%s</a>',
+ admin_url( 'themes.php' ),
+ 'Go to the Themes screen'
+ )
+ );
+}
</ins></span></pre></div>
<a id="trunksrcwpadminpluginsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/plugins.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/plugins.php 2019-01-09 19:05:41 UTC (rev 44523)
+++ trunk/src/wp-admin/plugins.php 2019-01-09 20:04:55 UTC (rev 44524)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -389,6 +389,27 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> break;
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ case 'resume':
+ if ( ! current_user_can( 'resume_plugin', $plugin ) ) {
+ wp_die( __( 'Sorry, you are not allowed to resume this plugin.' ) );
+ }
+
+ if ( is_multisite() && ! is_network_admin() && is_network_only_plugin( $plugin ) ) {
+ wp_redirect( self_admin_url( "plugins.php?plugin_status=$status&paged=$page&s=$s" ) );
+ exit;
+ }
+
+ check_admin_referer( 'resume-plugin_' . $plugin );
+
+ $result = resume_plugin( $plugin, self_admin_url( 'plugins.php?error=resuming' ), is_network_admin() );
+
+ if ( is_wp_error( $result ) ) {
+ wp_die( $result );
+ }
+
+ wp_redirect( self_admin_url( "plugins.php?resume=true&plugin_status=$status&paged=$page&s=$s" ) );
+ exit;
+
</ins><span class="cx" style="display: block; padding: 0 10px"> default:
</span><span class="cx" style="display: block; padding: 0 10px"> if ( isset( $_POST['checked'] ) ) {
</span><span class="cx" style="display: block; padding: 0 10px"> check_admin_referer( 'bulk-plugins' );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -488,6 +509,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $_GET['charsout']
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px"> $errmsg .= ' ' . __( 'If you notice “headers already sent” messages, problems with syndication feeds or other issues, try deactivating or removing this plugin.' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ } elseif ( 'resuming' === $_GET['error'] ) {
+ $errmsg = __( 'Plugin could not be resumed because it triggered a <strong>fatal error</strong>.' );
</ins><span class="cx" style="display: block; padding: 0 10px"> } else {
</span><span class="cx" style="display: block; padding: 0 10px"> $errmsg = __( 'Plugin could not be activated because it triggered a <strong>fatal error</strong>.' );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -541,6 +564,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> <div id="message" class="updated notice is-dismissible"><p><?php _e( 'Selected plugins <strong>deactivated</strong>.' ); ?></p></div>
</span><span class="cx" style="display: block; padding: 0 10px"> <?php elseif ( 'update-selected' == $action ) : ?>
</span><span class="cx" style="display: block; padding: 0 10px"> <div id="message" class="updated notice is-dismissible"><p><?php _e( 'All selected plugins are up to date.' ); ?></p></div>
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php elseif ( isset( $_GET['resume'] ) ) : ?>
+ <div id="message" class="updated notice is-dismissible"><p><?php _e( 'Plugin <strong>resumed</strong>.' ); ?></p></div>
</ins><span class="cx" style="display: block; padding: 0 10px"> <?php endif; ?>
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> <div class="wrap">
</span></span></pre></div>
<a id="trunksrcwpadminthemesphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/themes.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/themes.php 2019-01-09 19:05:41 UTC (rev 44523)
+++ trunk/src/wp-admin/themes.php 2019-01-09 20:04:55 UTC (rev 44524)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -33,6 +33,26 @@
</span><span class="cx" style="display: block; padding: 0 10px"> switch_theme( $theme->get_stylesheet() );
</span><span class="cx" style="display: block; padding: 0 10px"> wp_redirect( admin_url( 'themes.php?activated=true' ) );
</span><span class="cx" style="display: block; padding: 0 10px"> exit;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ } elseif ( 'resume' === $_GET['action'] ) {
+ check_admin_referer( 'resume-theme_' . $_GET['stylesheet'] );
+ $theme = wp_get_theme( $_GET['stylesheet'] );
+
+ if ( ! current_user_can( 'resume_themes' ) ) {
+ wp_die(
+ '<h1>' . __( 'You need a higher level of permission.' ) . '</h1>' .
+ '<p>' . __( 'Sorry, you are not allowed to resume this theme.' ) . '</p>',
+ 403
+ );
+ }
+
+ $result = resume_theme( $theme->get_stylesheet() );
+
+ if ( is_wp_error( $result ) ) {
+ wp_die( $result );
+ }
+
+ wp_redirect( admin_url( 'themes.php?resumed=true' ) );
+ exit;
</ins><span class="cx" style="display: block; padding: 0 10px"> } elseif ( 'delete' == $_GET['action'] ) {
</span><span class="cx" style="display: block; padding: 0 10px"> check_admin_referer( 'delete-theme_' . $_GET['stylesheet'] );
</span><span class="cx" style="display: block; padding: 0 10px"> $theme = wp_get_theme( $_GET['stylesheet'] );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -173,25 +193,33 @@
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> <hr class="wp-header-end">
</span><span class="cx" style="display: block; padding: 0 10px"> <?php
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-if ( ! validate_current_theme() || isset( $_GET['broken'] ) ) :
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+if ( ! validate_current_theme() || isset( $_GET['broken'] ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> ?>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-<div id="message1" class="updated notice is-dismissible"><p><?php _e( 'The active theme is broken. Reverting to the default theme.' ); ?></p></div>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ <div id="message1" class="updated notice is-dismissible"><p><?php _e( 'The active theme is broken. Reverting to the default theme.' ); ?></p></div>
</ins><span class="cx" style="display: block; padding: 0 10px"> <?php
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-elseif ( isset( $_GET['activated'] ) ) :
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+} elseif ( isset( $_GET['activated'] ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> if ( isset( $_GET['previewed'] ) ) {
</span><span class="cx" style="display: block; padding: 0 10px"> ?>
</span><span class="cx" style="display: block; padding: 0 10px"> <div id="message2" class="updated notice is-dismissible"><p><?php _e( 'Settings saved and theme activated.' ); ?> <a href="<?php echo home_url( '/' ); ?>"><?php _e( 'Visit site' ); ?></a></p></div>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- <?php } else { ?>
-<div id="message2" class="updated notice is-dismissible"><p><?php _e( 'New theme activated.' ); ?> <a href="<?php echo home_url( '/' ); ?>"><?php _e( 'Visit site' ); ?></a></p></div>
- <?php
-}
- elseif ( isset( $_GET['deleted'] ) ) :
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ <?php
+ } else {
</ins><span class="cx" style="display: block; padding: 0 10px"> ?>
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-<div id="message3" class="updated notice is-dismissible"><p><?php _e( 'Theme deleted.' ); ?></p></div>
-<?php elseif ( isset( $_GET['delete-active-child'] ) ) : ?>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ <div id="message2" class="updated notice is-dismissible"><p><?php _e( 'New theme activated.' ); ?> <a href="<?php echo home_url( '/' ); ?>"><?php _e( 'Visit site' ); ?></a></p></div>
+ <?php
+ }
+} elseif ( isset( $_GET['deleted'] ) ) {
+ ?>
+ <div id="message3" class="updated notice is-dismissible"><p><?php _e( 'Theme deleted.' ); ?></p></div>
+ <?php
+} elseif ( isset( $_GET['delete-active-child'] ) ) {
+ ?>
</ins><span class="cx" style="display: block; padding: 0 10px"> <div id="message4" class="error"><p><?php _e( 'You cannot delete a theme while it has an active child theme.' ); ?></p></div>
</span><span class="cx" style="display: block; padding: 0 10px"> <?php
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-endif;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+} elseif ( isset( $_GET['resumed'] ) ) {
+ ?>
+ <div id="message5" class="updated notice is-dismissible"><p><?php _e( 'Theme resumed.' ); ?></p></div>
+ <?php
+}
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $ct = wp_get_theme();
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -344,6 +372,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> <p><?php _e( 'The following themes are installed but incomplete.' ); ?></p>
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> <?php
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $can_resume = current_user_can( 'resume_themes' );
</ins><span class="cx" style="display: block; padding: 0 10px"> $can_delete = current_user_can( 'delete_themes' );
</span><span class="cx" style="display: block; padding: 0 10px"> $can_install = current_user_can( 'install_themes' );
</span><span class="cx" style="display: block; padding: 0 10px"> ?>
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -351,6 +380,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> <tr>
</span><span class="cx" style="display: block; padding: 0 10px"> <th><?php _ex( 'Name', 'theme name' ); ?></th>
</span><span class="cx" style="display: block; padding: 0 10px"> <th><?php _e( 'Description' ); ?></th>
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ <?php if ( $can_resume ) { ?>
+ <td></td>
+ <?php } ?>
</ins><span class="cx" style="display: block; padding: 0 10px"> <?php if ( $can_delete ) { ?>
</span><span class="cx" style="display: block; padding: 0 10px"> <td></td>
</span><span class="cx" style="display: block; padding: 0 10px"> <?php } ?>
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -363,6 +395,27 @@
</span><span class="cx" style="display: block; padding: 0 10px"> <td><?php echo $broken_theme->get( 'Name' ) ? $broken_theme->display( 'Name' ) : $broken_theme->get_stylesheet(); ?></td>
</span><span class="cx" style="display: block; padding: 0 10px"> <td><?php echo $broken_theme->errors()->get_error_message(); ?></td>
</span><span class="cx" style="display: block; padding: 0 10px"> <?php
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( $can_resume ) {
+ if ( 'theme_paused' === $broken_theme->errors()->get_error_code() ) {
+ $stylesheet = $broken_theme->get_stylesheet();
+ $resume_url = add_query_arg(
+ array(
+ 'action' => 'resume',
+ 'stylesheet' => urlencode( $stylesheet ),
+ ),
+ admin_url( 'themes.php' )
+ );
+ $resume_url = wp_nonce_url( $resume_url, 'resume-theme_' . $stylesheet );
+ ?>
+ <td><a href="<?php echo esc_url( $resume_url ); ?>" class="button resume-theme"><?php _e( 'Resume' ); ?></a></td>
+ <?php
+ } else {
+ ?>
+ <td></td>
+ <?php
+ }
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> if ( $can_delete ) {
</span><span class="cx" style="display: block; padding: 0 10px"> $stylesheet = $broken_theme->get_stylesheet();
</span><span class="cx" style="display: block; padding: 0 10px"> $delete_url = add_query_arg(
</span></span></pre></div>
<a id="trunksrcwpincludescapabilitiesphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/capabilities.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/capabilities.php 2019-01-09 19:05:41 UTC (rev 44523)
+++ trunk/src/wp-includes/capabilities.php 2019-01-09 20:04:55 UTC (rev 44524)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -464,6 +464,14 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> break;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ case 'resume_plugin':
+ // Even in a multisite, regular administrators should be able to resume a plugin.
+ $caps[] = 'activate_plugins';
+ break;
+ case 'resume_themes':
+ // Even in a multisite, regular administrators should be able to resume a theme.
+ $caps[] = 'switch_themes';
+ break;
</ins><span class="cx" style="display: block; padding: 0 10px"> case 'delete_user':
</span><span class="cx" style="display: block; padding: 0 10px"> case 'delete_users':
</span><span class="cx" style="display: block; padding: 0 10px"> // If multisite only super admins can delete users.
</span></span></pre></div>
<a id="trunksrcwpincludesclasswppausedextensionsstoragephp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/class-wp-paused-extensions-storage.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-paused-extensions-storage.php (rev 0)
+++ trunk/src/wp-includes/class-wp-paused-extensions-storage.php 2019-01-09 20:04:55 UTC (rev 44524)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,221 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Error Protection API: WP_Paused_Extensions_Storage class
+ *
+ * @package WordPress
+ * @since 5.1.0
+ */
+
+/**
+ * Core class used for storing paused extensions.
+ *
+ * @since 5.1.0
+ */
+class WP_Paused_Extensions_Storage {
+
+ /**
+ * Option name for storing paused extensions.
+ *
+ * @since 5.1.0
+ * @var string
+ */
+ protected $option_name;
+
+ /**
+ * Prefix for paused extensions stored as site metadata.
+ *
+ * @since 5.1.0
+ * @var string
+ */
+ protected $meta_prefix;
+
+ /**
+ * Constructor.
+ *
+ * @since 5.1.0
+ *
+ * @param string $option_name Option name for storing paused extensions.
+ * @param string $meta_prefix Prefix for paused extensions stored as site metadata.
+ */
+ public function __construct( $option_name, $meta_prefix ) {
+ $this->option_name = $option_name;
+ $this->meta_prefix = $meta_prefix;
+ }
+
+ /**
+ * Records an extension error.
+ *
+ * Only one error is stored per extension, with subsequent errors for the same extension overriding the
+ * previously stored error.
+ *
+ * @since 5.1.0
+ *
+ * @param string $extension Plugin or theme directory name.
+ * @param array $error {
+ * Error that was triggered.
+ *
+ * @type string $type The error type.
+ * @type string $file The name of the file in which the error occurred.
+ * @type string $line The line number in which the error occurred.
+ * @type string $message The error message.
+ * }
+ * @return bool True on success, false on failure.
+ */
+ public function record( $extension, $error ) {
+ if ( ! $this->is_api_loaded() ) {
+ return false;
+ }
+
+ if ( is_multisite() && is_site_meta_supported() ) {
+ // Do not update if the error is already stored.
+ if ( get_site_meta( get_current_blog_id(), $this->meta_prefix . $extension, true ) === $error ) {
+ return true;
+ }
+
+ return (bool) update_site_meta( get_current_blog_id(), $this->meta_prefix . $extension, $error );
+ }
+
+ $paused_extensions = $this->get_all();
+
+ // Do not update if the error is already stored.
+ if ( isset( $paused_extensions[ $extension ] ) && $paused_extensions[ $extension ] === $error ) {
+ return true;
+ }
+
+ $paused_extensions[ $extension ] = $error;
+
+ return update_option( $this->option_name, $paused_extensions );
+ }
+
+ /**
+ * Forgets a previously recorded extension error.
+ *
+ * @since 5.1.0
+ *
+ * @param string $extension Plugin or theme directory name.
+ * @return bool True on success, false on failure.
+ */
+ public function forget( $extension ) {
+ if ( ! $this->is_api_loaded() ) {
+ return false;
+ }
+
+ if ( is_multisite() && is_site_meta_supported() ) {
+ // Do not delete if no error is stored.
+ if ( get_site_meta( get_current_blog_id(), $this->meta_prefix . $extension ) === array() ) {
+ return true;
+ }
+
+ return (bool) delete_site_meta( get_current_blog_id(), $this->meta_prefix . $extension );
+ }
+
+ $paused_extensions = $this->get_all();
+
+ // Do not delete if no error is stored.
+ if ( ! isset( $paused_extensions[ $extension ] ) ) {
+ return true;
+ }
+
+ // Clean up the entire option if we're removing the only error.
+ if ( count( $paused_extensions ) === 1 ) {
+ return delete_option( $this->option_name );
+ }
+
+ unset( $paused_extensions[ $extension ] );
+
+ return update_option( $this->option_name, $paused_extensions );
+ }
+
+ /**
+ * Gets the error for an extension, if paused.
+ *
+ * @since 5.1.0
+ *
+ * @param string $extension Plugin or theme directory name.
+ * @return array|null Error that is stored, or null if the extension is not paused.
+ */
+ public function get( $extension ) {
+ if ( ! $this->is_api_loaded() ) {
+ return null;
+ }
+
+ if ( is_multisite() && is_site_meta_supported() ) {
+ $error = get_site_meta( get_current_blog_id(), $this->meta_prefix . $extension, true );
+ if ( ! $error ) {
+ return null;
+ }
+
+ return $error;
+ }
+
+ $paused_extensions = $this->get_all();
+
+ if ( ! isset( $paused_extensions[ $extension ] ) ) {
+ return null;
+ }
+
+ return $paused_extensions[ $extension ];
+ }
+
+ /**
+ * Gets the paused extensions with their errors.
+ *
+ * @since 5.1.0
+ *
+ * @return array Associative array of $extension => $error pairs.
+ */
+ public function get_all() {
+ if ( ! $this->is_api_loaded() ) {
+ return array();
+ }
+
+ if ( is_multisite() && is_site_meta_supported() ) {
+ $site_metadata = get_site_meta( get_current_blog_id() );
+
+ $paused_extensions = array();
+ foreach ( $site_metadata as $meta_key => $meta_values ) {
+ if ( 0 !== strpos( $meta_key, $this->meta_prefix ) ) {
+ continue;
+ }
+
+ $error = maybe_unserialize( array_shift( $meta_values ) );
+
+ $paused_extensions[ substr( $meta_key, strlen( $this->meta_prefix ) ) ] = $error;
+ }
+
+ return $paused_extensions;
+ }
+
+ return (array) get_option( $this->option_name, array() );
+ }
+
+ /**
+ * Gets the site meta query clause for querying sites with paused extensions.
+ *
+ * @since 5.1.0
+ *
+ * @param string $extension Plugin or theme directory name.
+ * @return array A single clause to add to a meta query.
+ */
+ public function get_site_meta_query_clause( $extension ) {
+ return array(
+ 'key' => $this->meta_prefix . $extension,
+ 'compare_key' => '=',
+ );
+ }
+
+ /**
+ * Checks whether the underlying API to store paused extensions is loaded.
+ *
+ * @since 5.1.0
+ *
+ * @return bool True if the API is loaded, false otherwise.
+ */
+ protected function is_api_loaded() {
+ if ( is_multisite() ) {
+ return function_exists( 'is_site_meta_supported' ) && function_exists( 'get_site_meta' );
+ }
+
+ return function_exists( 'get_option' );
+ }
+}
</ins></span></pre></div>
<a id="trunksrcwpincludesclasswpshutdownhandlerphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/class-wp-shutdown-handler.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-shutdown-handler.php (rev 0)
+++ trunk/src/wp-includes/class-wp-shutdown-handler.php 2019-01-09 20:04:55 UTC (rev 44524)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,176 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Error Protection API: WP_Shutdown_Handler class
+ *
+ * @package WordPress
+ * @since 5.1.0
+ */
+
+/**
+ * Core class used as the default shutdown handler.
+ *
+ * A drop-in 'shutdown-handler.php' can be used to override the instance of this class and use a custom implementation
+ * for the shutdown handler that WordPress registers. The custom class should extend this class and can override its
+ * methods individually as necessary. The file must return the instance of the class that should be registered.
+ *
+ * @since 5.1.0
+ */
+class WP_Shutdown_Handler {
+
+ /**
+ * Runs the shutdown handler.
+ *
+ * This method is registered via `register_shutdown_function()`.
+ *
+ * @since 5.1.0
+ */
+ public function handle() {
+ // Bail if WordPress executed successfully.
+ if ( defined( 'WP_EXECUTION_SUCCEEDED' ) && WP_EXECUTION_SUCCEEDED ) {
+ return;
+ }
+
+ try {
+ // Bail if no error found or if it could not be stored.
+ if ( ! $this->detect_error() ) {
+ return;
+ }
+
+ // Redirect the request to catch multiple errors in one go.
+ $this->redirect_protected();
+
+ // Display the PHP error template.
+ $this->display_error_template();
+ } catch ( Exception $e ) {
+ // Catch exceptions and remain silent.
+ }
+ }
+
+ /**
+ * Detects the error causing the crash and stores it if one was found.
+ *
+ * @since 5.1.0
+ *
+ * @return bool True if an error was found and stored, false otherwise.
+ */
+ protected function detect_error() {
+ $error = error_get_last();
+
+ // No error, just skip the error handling code.
+ if ( null === $error ) {
+ return false;
+ }
+
+ // Bail if this error should not be handled.
+ if ( ! wp_should_handle_error( $error ) ) {
+ return false;
+ }
+
+ // Try to store the error so that the respective extension is paused.
+ return wp_record_extension_error( $error );
+ }
+
+ /**
+ * Redirects the current request to allow recovering multiple errors in one go.
+ *
+ * The redirection will only happen when on a protected endpoint.
+ *
+ * It must be ensured that this method is only called when an error actually occurred and will not occur on the
+ * next request again. Otherwise it will create a redirect loop.
+ *
+ * @since 5.1.0
+ */
+ protected function redirect_protected() {
+ // Do not redirect requests on non-protected endpoints.
+ if ( ! is_protected_endpoint() ) {
+ return;
+ }
+
+ // Pluggable is usually loaded after plugins, so we manually include it here for redirection functionality.
+ if ( ! function_exists( 'wp_redirect' ) ) {
+ include ABSPATH . WPINC . '/pluggable.php';
+ }
+
+ $scheme = is_ssl() ? 'https://' : 'http://';
+
+ $url = "{$scheme}{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
+ wp_redirect( $url );
+ exit;
+ }
+
+ /**
+ * Displays the PHP error template and sends the HTTP status code, typically 500.
+ *
+ * A drop-in 'php-error.php' can be used as a custom template. This drop-in should control the HTTP status code and
+ * print the HTML markup indicating that a PHP error occurred. Alternatively, {@see wp_die()} can be used. Note
+ * that this drop-in may potentially be executed very early in the WordPress bootstrap process, so any core
+ * functions used that are not part of `wp-includes/load.php` should be checked for before being called.
+ *
+ * The default template also displays a link to the admin in order to fix the problem, however doing so is not
+ * mandatory.
+ *
+ * @since 5.1.0
+ */
+ protected function display_error_template() {
+ if ( defined( 'WP_CONTENT_DIR' ) ) {
+ // Load custom PHP error template, if present.
+ $php_error_pluggable = WP_CONTENT_DIR . '/php-error.php';
+ if ( is_readable( $php_error_pluggable ) ) {
+ require_once $php_error_pluggable;
+ die();
+ }
+ }
+
+ // Otherwise, fail with a `wp_die()` message.
+ $message = $this->get_error_message_markup();
+
+ // `wp_die()` wraps the message in paragraph tags, so let's just try working around that.
+ if ( substr( $message, 0, 3 ) === '<p>' && substr( $message, -4 ) === '</p>' ) {
+ $message = substr( $message, 3, -4 );
+ }
+
+ wp_die( $message, '', 500 );
+ }
+
+ /**
+ * Returns the error message markup to display in the default error template.
+ *
+ * @since 5.1.0
+ *
+ * @return string Error message HTML output.
+ */
+ protected function get_error_message_markup() {
+ if ( ! function_exists( '__' ) ) {
+ function __( $text ) {
+ return $text;
+ }
+ }
+
+ $message = sprintf(
+ '<p>%s</p>',
+ __( 'The site is experiencing technical difficulties.' )
+ );
+
+ if ( function_exists( 'admin_url' ) ) {
+ $message .= sprintf(
+ '<hr><p><em>%s <a href="%s">%s</a></em></p>',
+ __( 'Are you the site owner?' ),
+ admin_url(),
+ __( 'Log into the admin backend to fix this.' )
+ );
+ }
+
+ if ( function_exists( 'apply_filters' ) ) {
+ /**
+ * Filters the message that the default PHP error page displays.
+ *
+ * @since 5.1.0
+ *
+ * @param string $message HTML error message to display.
+ */
+ $message = apply_filters( 'wp_technical_issues_display', $message );
+ }
+
+ return $message;
+ }
+}
</ins></span></pre></div>
<a id="trunksrcwpincludesclasswpthemephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-theme.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-theme.php 2019-01-09 19:05:41 UTC (rev 44523)
+++ trunk/src/wp-includes/class-wp-theme.php 2019-01-09 20:04:55 UTC (rev 44524)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -371,6 +371,10 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $this->parent = new WP_Theme( $this->template, isset( $theme_root_template ) ? $theme_root_template : $this->theme_root, $this );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( wp_paused_themes()->get( $this->stylesheet ) && ( ! is_wp_error( $this->errors ) || ! isset( $this->errors->errors['theme_paused'] ) ) ) {
+ $this->errors = new WP_Error( 'theme_paused', __( 'This theme failed to load properly and was paused within the admin backend.' ) );
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> // We're good. If we didn't retrieve from cache, set it.
</span><span class="cx" style="display: block; padding: 0 10px"> if ( ! is_array( $cache ) ) {
</span><span class="cx" style="display: block; padding: 0 10px"> $cache = array(
</span></span></pre></div>
<a id="trunksrcwpincludeserrorprotectionphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/wp-includes/error-protection.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/error-protection.php (rev 0)
+++ trunk/src/wp-includes/error-protection.php 2019-01-09 20:04:55 UTC (rev 44524)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,166 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Error Protection API: Functions
+ *
+ * @package WordPress
+ * @since 5.1.0
+ */
+
+/**
+ * Gets the instance for storing paused plugins.
+ *
+ * @since 5.1.0
+ *
+ * @return WP_Paused_Extensions_Storage Paused plugins storage.
+ */
+function wp_paused_plugins() {
+ static $wp_paused_plugins_storage = null;
+
+ if ( null === $wp_paused_plugins_storage ) {
+ $wp_paused_plugins_storage = new WP_Paused_Extensions_Storage( 'paused_plugins', 'paused_plugin_' );
+ }
+
+ return $wp_paused_plugins_storage;
+}
+
+/**
+ * Gets the instance for storing paused themes.
+ *
+ * @since 5.1.0
+ *
+ * @return WP_Paused_Extensions_Storage Paused themes storage.
+ */
+function wp_paused_themes() {
+ static $wp_paused_themes_storage = null;
+
+ if ( null === $wp_paused_themes_storage ) {
+ $wp_paused_themes_storage = new WP_Paused_Extensions_Storage( 'paused_themes', 'paused_theme_' );
+ }
+
+ return $wp_paused_themes_storage;
+}
+
+/**
+ * Records the extension error as a database option.
+ *
+ * @since 5.1.0
+ *
+ * @global array $wp_theme_directories
+ *
+ * @param array $error Error that was triggered.
+ * @return bool Whether the error was correctly recorded.
+ */
+function wp_record_extension_error( $error ) {
+ global $wp_theme_directories;
+
+ if ( ! isset( $error['file'] ) ) {
+ return false;
+ }
+
+ if ( ! defined( 'WP_PLUGIN_DIR' ) ) {
+ return false;
+ }
+
+ $error_file = wp_normalize_path( $error['file'] );
+ $wp_plugin_dir = wp_normalize_path( WP_PLUGIN_DIR );
+
+ if ( 0 === strpos( $error_file, $wp_plugin_dir ) ) {
+ $callback = 'wp_paused_plugins';
+ $path = str_replace( $wp_plugin_dir . '/', '', $error_file );
+ } else {
+ foreach ( $wp_theme_directories as $theme_directory ) {
+ $theme_directory = wp_normalize_path( $theme_directory );
+ if ( 0 === strpos( $error_file, $theme_directory ) ) {
+ $callback = 'wp_paused_themes';
+ $path = str_replace( $theme_directory . '/', '', $error_file );
+ }
+ }
+ }
+
+ if ( empty( $callback ) || empty( $path ) ) {
+ return false;
+ }
+
+ $parts = explode( '/', $path );
+ $extension = array_shift( $parts );
+
+ return call_user_func( $callback )->record( $extension, $error );
+}
+
+/**
+ * Forgets a previously recorded extension error again.
+ *
+ * @since 5.1.0
+ *
+ * @param string $type Type of the extension.
+ * @param string $extension Relative path of the extension.
+ * @param bool $network_wide Optional. Whether to resume the plugin for the entire
+ * network. Default false.
+ * @return bool Whether the extension error was successfully forgotten.
+ */
+function wp_forget_extension_error( $type, $extension, $network_wide = false ) {
+ switch ( $type ) {
+ case 'plugins':
+ $callback = 'wp_paused_plugins';
+ list( $extension ) = explode( '/', $extension );
+ break;
+ case 'themes':
+ $callback = 'wp_paused_themes';
+ list( $extension ) = explode( '/', $extension );
+ break;
+ }
+
+ if ( empty( $callback ) || empty( $extension ) ) {
+ return false;
+ }
+
+ // Handle manually since the regular APIs do not expose this functionality.
+ if ( $network_wide && is_site_meta_supported() ) {
+ $site_meta_query_clause = call_user_func( $callback )->get_site_meta_query_clause( $extension );
+ return delete_metadata( 'blog', 0, $site_meta_query_clause['key'], '', true );
+ }
+
+ return call_user_func( $callback )->forget( $extension );
+}
+
+/**
+ * Determines whether we are dealing with an error that WordPress should handle
+ * in order to protect the admin backend against WSODs.
+ *
+ * @param array $error Error information retrieved from error_get_last().
+ *
+ * @return bool Whether WordPress should handle this error.
+ */
+function wp_should_handle_error( $error ) {
+ if ( ! isset( $error['type'] ) ) {
+ return false;
+ }
+
+ $error_types_to_handle = array(
+ E_ERROR,
+ E_PARSE,
+ E_USER_ERROR,
+ E_COMPILE_ERROR,
+ E_RECOVERABLE_ERROR,
+ );
+
+ return in_array( $error['type'], $error_types_to_handle, true );
+}
+
+/**
+ * Registers the WordPress premature shutdown handler.
+ *
+ * @since 5.1.0
+ */
+function wp_register_premature_shutdown_handler() {
+ $handler = null;
+ if ( defined( 'WP_CONTENT_DIR' ) && is_readable( WP_CONTENT_DIR . '/shutdown-handler.php' ) ) {
+ $handler = include WP_CONTENT_DIR . '/shutdown-handler.php';
+ }
+
+ if ( ! is_object( $handler ) || ! is_callable( array( $handler, 'handle' ) ) ) {
+ $handler = new WP_Shutdown_Handler();
+ }
+
+ register_shutdown_function( array( $handler, 'handle' ) );
+}
</ins></span></pre></div>
<a id="trunksrcwpincludesloadphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/load.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/load.php 2019-01-09 19:05:41 UTC (rev 44523)
+++ trunk/src/wp-includes/load.php 2019-01-09 20:04:55 UTC (rev 44524)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -696,10 +696,118 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $plugins[] = WP_PLUGIN_DIR . '/' . $plugin;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+ /*
+ * Remove plugins from the list of active plugins when we're on an endpoint
+ * that should be protected against WSODs and the plugin is paused.
+ */
+ if ( is_protected_endpoint() ) {
+ $plugins = wp_skip_paused_plugins( $plugins );
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> return $plugins;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Filters a given list of plugins, removing any paused plugins from it.
+ *
+ * @since 5.1.0
+ *
+ * @param array $plugins List of absolute plugin main file paths.
+ * @return array Filtered value of $plugins, without any paused plugins.
+ */
+function wp_skip_paused_plugins( array $plugins ) {
+ $paused_plugins = wp_paused_plugins()->get_all();
+
+ if ( empty( $paused_plugins ) ) {
+ return $plugins;
+ }
+
+ foreach ( $plugins as $index => $plugin ) {
+ list( $plugin ) = explode( '/', plugin_basename( $plugin ) );
+
+ if ( array_key_exists( $plugin, $paused_plugins ) ) {
+ unset( $plugins[ $index ] );
+
+ // Store list of paused plugins for displaying an admin notice.
+ $GLOBALS['_paused_plugins'][ $plugin ] = $paused_plugins[ $plugin ];
+ }
+ }
+
+ return $plugins;
+}
+
+/**
+ * Retrieves an array of active and valid themes.
+ *
+ * While upgrading or installing WordPress, no themes are returned.
+ *
+ * @since 5.1.0
+ * @access private
+ *
+ * @return array Array of paths to theme directories.
+ */
+function wp_get_active_and_valid_themes() {
+ global $pagenow;
+
+ $themes = array();
+
+ if ( wp_installing() && 'wp-activate.php' !== $pagenow ) {
+ return $themes;
+ }
+
+ if ( TEMPLATEPATH !== STYLESHEETPATH ) {
+ $themes[] = STYLESHEETPATH;
+ }
+
+ $themes[] = TEMPLATEPATH;
+
+ /*
+ * Remove themes from the list of active themes when we're on an endpoint
+ * that should be protected against WSODs and the theme is paused.
+ */
+ if ( is_protected_endpoint() ) {
+ $themes = wp_skip_paused_themes( $themes );
+
+ // If no active and valid themes exist, skip loading themes.
+ if ( empty( $themes ) ) {
+ add_filter( 'wp_using_themes', '__return_false' );
+ }
+ }
+
+ return $themes;
+}
+
+/**
+ * Filters a given list of themes, removing any paused themes from it.
+ *
+ * @since 5.1.0
+ *
+ * @param array $themes List of absolute theme directory paths.
+ * @return array Filtered value of $themes, without any paused themes.
+ */
+function wp_skip_paused_themes( array $themes ) {
+ $paused_themes = wp_paused_themes()->get_all();
+
+ if ( empty( $paused_themes ) ) {
+ return $themes;
+ }
+
+ foreach ( $themes as $index => $theme ) {
+ $theme = basename( $theme );
+
+ if ( array_key_exists( $theme, $paused_themes ) ) {
+ unset( $themes[ $index ] );
+
+ // Store list of paused themes for displaying an admin notice.
+ $GLOBALS['_paused_themes'][ $theme ] = $paused_themes[ $theme ];
+ }
+ }
+
+ return $themes;
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Set internal encoding.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * In most cases the default internal encoding is latin1, which is
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1164,6 +1272,106 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Determines whether the current request should use themes.
+ *
+ * @since 5.1.0
+ *
+ * @return bool True if themes should be used, false otherwise.
+ */
+function wp_using_themes() {
+ /**
+ * Filters whether the current request should use themes.
+ *
+ * @since 5.1.0
+ *
+ * @param bool $wp_using_themes Whether the current request should use themes.
+ */
+ return apply_filters( 'wp_using_themes', defined( 'WP_USE_THEMES' ) && WP_USE_THEMES );
+}
+
+/**
+ * Determines whether we are currently on an endpoint that should be protected against WSODs.
+ *
+ * @since 5.1.0
+ *
+ * @return bool True if the current endpoint should be protected.
+ */
+function is_protected_endpoint() {
+ // Protect login pages.
+ if ( isset( $GLOBALS['pagenow'] ) && 'wp-login.php' === $GLOBALS['pagenow'] ) {
+ return true;
+ }
+
+ // Protect the admin backend.
+ if ( is_admin() && ! wp_doing_ajax() ) {
+ return true;
+ }
+
+ // Protect AJAX actions that could help resolve a fatal error should be available.
+ if ( is_protected_ajax_action() ) {
+ return true;
+ }
+
+ /**
+ * Filters whether the current request is against a protected endpoint.
+ *
+ * This filter is only fired when an endpoint is requested which is not already protected by
+ * WordPress core. As such, it exclusively allows providing further protected endpoints in
+ * addition to the admin backend, login pages and protected AJAX actions.
+ *
+ * @since 5.1.0
+ *
+ * @param bool $is_protected_endpoint Whether the currently requested endpoint is protected. Default false.
+ */
+ return (bool) apply_filters( 'is_protected_endpoint', false );
+}
+
+/**
+ * Determines whether we are currently handling an AJAX action that should be protected against WSODs.
+ *
+ * @since 5.1.0
+ *
+ * @return bool True if the current AJAX action should be protected.
+ */
+function is_protected_ajax_action() {
+ if ( ! wp_doing_ajax() ) {
+ return false;
+ }
+
+ if ( ! isset( $_REQUEST['action'] ) ) {
+ return false;
+ }
+
+ $actions_to_protect = array(
+ 'edit-theme-plugin-file', // Saving changes in the core code editor.
+ 'heartbeat', // Keep the heart beating.
+ 'install-plugin', // Installing a new plugin.
+ 'install-theme', // Installing a new theme.
+ 'search-plugins', // Searching in the list of plugins.
+ 'search-install-plugins', // Searching for a plugin in the plugin install screen.
+ 'update-plugin', // Update an existing plugin.
+ 'update-theme', // Update an existing theme.
+ );
+
+ /**
+ * Filters the array of protected AJAX actions.
+ *
+ * This filter is only fired when doing AJAX and the AJAX request has an 'action' property.
+ *
+ * @since 5.1.0
+ *
+ * @param array $actions_to_protect Array of strings with AJAX actions to protect.
+ */
+ $actions_to_protect = (array) apply_filters( 'wp_protected_ajax_actions', $actions_to_protect );
+
+ if ( ! in_array( $_REQUEST['action'], $actions_to_protect, true ) ) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Determines whether the current request is a WordPress cron request.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 4.8.0
</span></span></pre></div>
<a id="trunksrcwpincludesmsloadphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/ms-load.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/ms-load.php 2019-01-09 19:05:41 UTC (rev 44523)
+++ trunk/src/wp-includes/ms-load.php 2019-01-09 20:04:55 UTC (rev 44524)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -52,6 +52,15 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $plugins[] = WP_PLUGIN_DIR . '/' . $plugin;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+ /*
+ * Remove plugins from the list of active plugins when we're on an endpoint
+ * that should be protected against WSODs and the plugin is paused.
+ */
+ if ( is_protected_endpoint() ) {
+ $plugins = wp_skip_paused_plugins( $plugins );
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> return $plugins;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span></span></pre></div>
<a id="trunksrcwpincludestemplateloaderphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/template-loader.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/template-loader.php 2019-01-09 19:05:41 UTC (rev 44523)
+++ trunk/src/wp-includes/template-loader.php 2019-01-09 20:04:55 UTC (rev 44524)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4,7 +4,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @package WordPress
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-if ( defined( 'WP_USE_THEMES' ) && WP_USE_THEMES ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+if ( wp_using_themes() ) {
</ins><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px"> * Fires before determining which template to load.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -44,7 +44,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> return;
</span><span class="cx" style="display: block; padding: 0 10px"> endif;
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-if ( defined( 'WP_USE_THEMES' ) && WP_USE_THEMES ) :
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+if ( wp_using_themes() ) :
</ins><span class="cx" style="display: block; padding: 0 10px"> $template = false;
</span><span class="cx" style="display: block; padding: 0 10px"> if ( is_embed() && $template = get_embed_template() ) :
</span><span class="cx" style="display: block; padding: 0 10px"> elseif ( is_404() && $template = get_404_template() ) :
</span></span></pre></div>
<a id="trunksrcwpsettingsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-settings.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-settings.php 2019-01-09 19:05:41 UTC (rev 44523)
+++ trunk/src/wp-settings.php 2019-01-09 20:04:55 UTC (rev 44524)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -17,9 +17,15 @@
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> // Include files required for initialization.
</span><span class="cx" style="display: block; padding: 0 10px"> require( ABSPATH . WPINC . '/load.php' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+require( ABSPATH . WPINC . '/class-wp-paused-extensions-storage.php' );
+require( ABSPATH . WPINC . '/class-wp-shutdown-handler.php' );
+require( ABSPATH . WPINC . '/error-protection.php' );
</ins><span class="cx" style="display: block; padding: 0 10px"> require( ABSPATH . WPINC . '/default-constants.php' );
</span><span class="cx" style="display: block; padding: 0 10px"> require_once( ABSPATH . WPINC . '/plugin.php' );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+// Make sure we register the premature shutdown handler as soon as possible.
+wp_register_premature_shutdown_handler();
+
</ins><span class="cx" style="display: block; padding: 0 10px"> /*
</span><span class="cx" style="display: block; padding: 0 10px"> * These can't be directly globalized in version.php. When updating,
</span><span class="cx" style="display: block; padding: 0 10px"> * we're including version.php from another installation and don't want
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -474,14 +480,12 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $GLOBALS['wp_locale_switcher']->init();
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> // Load the functions for the active theme, for both parent and child theme if applicable.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-if ( ! wp_installing() || 'wp-activate.php' === $pagenow ) {
- if ( TEMPLATEPATH !== STYLESHEETPATH && file_exists( STYLESHEETPATH . '/functions.php' ) ) {
- include( STYLESHEETPATH . '/functions.php' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+foreach ( wp_get_active_and_valid_themes() as $theme ) {
+ if ( file_exists( $theme . '/functions.php' ) ) {
+ include $theme . '/functions.php';
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( file_exists( TEMPLATEPATH . '/functions.php' ) ) {
- include( TEMPLATEPATH . '/functions.php' );
- }
</del><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+unset( $theme );
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px"> * Fires after the theme is loaded.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -526,3 +530,12 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 3.0.0
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> do_action( 'wp_loaded' );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/*
+ * Store the fact that we could successfully execute the entire WordPress
+ * lifecycle. This is used to skip the premature shutdown handler, as it cannot
+ * be unregistered.
+ */
+if ( ! defined( 'WP_EXECUTION_SUCCEEDED' ) ) {
+ define( 'WP_EXECUTION_SUCCEEDED', true );
+}
</ins></span></pre>
</div>
</div>
</body>
</html>