File Editor: Increase robustness of fatal error checking when saving PHP file edits.

* Increase PHP execution time limit prior to issuing loopback requests where are themselves given timeouts to ensure PHP file can be reverted.
* Output scrape messages on success and failure so that absence of either can also be flagged as an error condition.
* Forward browser's HTTP Basic Auth credentials in loopback requests to admin and home URL.
* Display more helpful message when loopback request fails.

Amends [41721].
See #21622.
Fixes #42102.

Built from https://develop.svn.wordpress.org/trunk@41805


git-svn-id: http://core.svn.wordpress.org/trunk@41639 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Weston Ruter 2017-10-10 05:27:49 +00:00
parent 132c847087
commit 1a22fb3b60
6 changed files with 73 additions and 27 deletions

View File

@ -486,7 +486,19 @@ function wp_edit_theme_plugin_file( $args ) {
'Cache-Control' => 'no-cache',
);
$needle = "###### begin_scraped_error:$scrape_key ######";
// Include Basic auth in loopback requests.
if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
$headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
}
// Make sure PHP process doesn't die before loopback requests complete.
@set_time_limit( 300 );
// Time to wait for loopback requests to finish.
$timeout = 100;
$needle_start = "###### wp_scraping_result_start:$scrape_key ######";
$needle_end = "###### wp_scraping_result_end:$scrape_key ######";
// Attempt loopback request to editor to see if user just whitescreened themselves.
if ( $plugin ) {
@ -503,36 +515,67 @@ function wp_edit_theme_plugin_file( $args ) {
$url = admin_url();
}
$url = add_query_arg( $scrape_params, $url );
$r = wp_remote_get( $url, compact( 'cookies', 'headers' ) );
$r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) );
$body = wp_remote_retrieve_body( $r );
$error_position = strpos( $body, $needle );
$scrape_result_position = strpos( $body, $needle_start );
$loopback_request_failure = array(
'code' => 'loopback_request_failed',
'message' => __( 'Unable to communicate back with site to check for fatal errors, so the PHP change was reverted. You will need to upload your PHP file change by some other means, such as by using SFTP.' ),
);
$json_parse_failure = array(
'code' => 'json_parse_error',
);
$result = null;
if ( false === $scrape_result_position ) {
$result = $loopback_request_failure;
} else {
$error_output = substr( $body, $scrape_result_position + strlen( $needle_start ) );
$error_output = substr( $error_output, 0, strpos( $error_output, $needle_end ) );
$result = json_decode( trim( $error_output ), true );
if ( empty( $result ) ) {
$result = $json_parse_failure;
}
}
// Try making request to homepage as well to see if visitors have been whitescreened.
if ( false === $error_position ) {
if ( true === $result ) {
$url = home_url( '/' );
$url = add_query_arg( $scrape_params, $url );
$r = wp_remote_get( $url, compact( 'cookies', 'headers' ) );
$r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) );
$body = wp_remote_retrieve_body( $r );
$error_position = strpos( $body, $needle );
$scrape_result_position = strpos( $body, $needle_start );
if ( false === $scrape_result_position ) {
$result = $loopback_request_failure;
} else {
$error_output = substr( $body, $scrape_result_position + strlen( $needle_start ) );
$error_output = substr( $error_output, 0, strpos( $error_output, $needle_end ) );
$result = json_decode( trim( $error_output ), true );
if ( empty( $result ) ) {
$result = $json_parse_failure;
}
}
}
delete_transient( $transient );
if ( false !== $error_position ) {
if ( true !== $result ) {
// Roll-back file change.
file_put_contents( $real_file, $previous_content );
if ( function_exists( 'opcache_invalidate' ) ) {
opcache_invalidate( $real_file, true );
}
$error_output = trim( substr( $body, $error_position + strlen( $needle ) ) );
$error = json_decode( $error_output, true );
if ( ! isset( $error['message'] ) ) {
$message = $error_output;
if ( ! isset( $result['message'] ) ) {
$message = __( 'An unidentified error has occurred.' );
} else {
$message = $error['message'];
unset( $error['message'] );
$message = $result['message'];
unset( $result['message'] );
}
return new WP_Error( 'php_error', $message, $error );
return new WP_Error( 'php_error', $message, $result );
}
}

View File

@ -13,7 +13,8 @@ wp.themePluginEditor = (function( $ ) {
singular: '',
plural: ''
},
saveAlert: ''
saveAlert: '',
saveError: ''
},
codeEditor: {},
instance: null,
@ -161,7 +162,8 @@ wp.themePluginEditor = (function( $ ) {
request.fail( function ( response ) {
var notice = $.extend(
{
code: 'save_error'
code: 'save_error',
message: component.l10n.saveError
},
response,
{

View File

@ -1 +1 @@
window.wp||(window.wp={}),wp.themePluginEditor=function(a){"use strict";var b={l10n:{lintError:{singular:"",plural:""},saveAlert:""},codeEditor:{},instance:null,noticeElements:{},dirty:!1,lintErrors:[]};return b.init=function(c,d){b.form=c,d&&a.extend(b,d),b.noticeTemplate=wp.template("wp-file-editor-notice"),b.noticesContainer=b.form.find(".editor-notices"),b.submitButton=b.form.find(":input[name=submit]"),b.spinner=b.form.find(".submit .spinner"),b.form.on("submit",b.submit),b.textarea=b.form.find("#newcontent"),b.textarea.on("change",b.onChange),b.warning=a(".file-editor-warning"),b.warning.length>0&&(a("body").addClass("modal-open"),b.warning.find(".file-editor-warning-dismiss").focus(),b.warning.on("click",".file-editor-warning-dismiss",b.dismissWarning)),!1!==b.codeEditor&&_.defer(function(){b.initCodeEditor()}),a(window).on("beforeunload",function(){if(b.dirty)return b.l10n.saveAlert})},b.dismissWarning=function(){wp.ajax.post("dismiss-wp-pointer",{pointer:b.themeOrPlugin+"_editor_notice"}),b.warning.remove(),a("body").removeClass("modal-open"),b.instance.codemirror.focus()},b.onChange=function(){b.dirty=!0,b.removeNotice("file_saved")},b.submit=function(c){var d,e={};if(c.preventDefault(),a.each(b.form.serializeArray(),function(){e[this.name]=this.value}),b.instance&&(e.newcontent=b.instance.codemirror.getValue()),!b.isSaving){if(b.lintErrors.length)return void b.instance.codemirror.setCursor(b.lintErrors[0].from.line);b.isSaving=!0,b.textarea.prop("readonly",!0),b.instance&&b.instance.codemirror.setOption("readOnly",!0),b.spinner.addClass("is-active"),d=wp.ajax.post("edit-theme-plugin-file",e),b.lastSaveNoticeCode&&b.removeNotice(b.lastSaveNoticeCode),d.done(function(a){b.lastSaveNoticeCode="file_saved",b.addNotice({code:b.lastSaveNoticeCode,type:"success",message:a.message,dismissible:!0}),b.dirty=!1}),d.fail(function(c){var d=a.extend({code:"save_error"},c,{type:"error",dismissible:!0});b.lastSaveNoticeCode=d.code,b.addNotice(d)}),d.always(function(){b.spinner.removeClass("is-active"),b.isSaving=!1,b.textarea.prop("readonly",!1),b.instance&&b.instance.codemirror.setOption("readOnly",!1)})}},b.addNotice=function(c){var d;if(!c.code)throw new Error("Missing code.");return b.removeNotice(c.code),d=a(b.noticeTemplate(c)),d.hide(),d.find(".notice-dismiss").on("click",function(){b.removeNotice(c.code),c.onDismiss&&c.onDismiss(c)}),wp.a11y.speak(c.message),b.noticesContainer.append(d),d.slideDown("fast"),b.noticeElements[c.code]=d,d},b.removeNotice=function(c){return!!b.noticeElements[c]&&(b.noticeElements[c].slideUp("fast",function(){a(this).remove()}),delete b.noticeElements[c],!0)},b.initCodeEditor=function(){var c,d;c=a.extend({},b.codeEditor),c.onTabPrevious=function(){a("#templateside").find(":tabbable").last().focus()},c.onTabNext=function(){a("#template").find(":tabbable:not(.CodeMirror-code)").first().focus()},c.onChangeLintingErrors=function(a){b.lintErrors=a,0===a.length&&b.submitButton.toggleClass("disabled",!1)},c.onUpdateErrorNotice=function(a){var d,e;b.submitButton.toggleClass("disabled",a.length>0),0!==a.length?(d=1===a.length?b.l10n.lintError.singular.replace("%d","1"):b.l10n.lintError.plural.replace("%d",String(a.length)),e=b.addNotice({code:"lint_errors",type:"error",message:d,dismissible:!1}),e.find("input[type=checkbox]").on("click",function(){c.onChangeLintingErrors([]),b.removeNotice("lint_errors")})):b.removeNotice("lint_errors")},d=wp.codeEditor.initialize(a("#newcontent"),c),d.codemirror.on("change",b.onChange),a(d.codemirror.display.lineDiv).attr({role:"textbox","aria-multiline":"true","aria-labelledby":"theme-plugin-editor-label","aria-describedby":"editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4"}),a("#theme-plugin-editor-label").on("click",function(){d.codemirror.focus()}),b.instance=d},b}(jQuery);
window.wp||(window.wp={}),wp.themePluginEditor=function(a){"use strict";var b={l10n:{lintError:{singular:"",plural:""},saveAlert:"",saveError:""},codeEditor:{},instance:null,noticeElements:{},dirty:!1,lintErrors:[]};return b.init=function(c,d){b.form=c,d&&a.extend(b,d),b.noticeTemplate=wp.template("wp-file-editor-notice"),b.noticesContainer=b.form.find(".editor-notices"),b.submitButton=b.form.find(":input[name=submit]"),b.spinner=b.form.find(".submit .spinner"),b.form.on("submit",b.submit),b.textarea=b.form.find("#newcontent"),b.textarea.on("change",b.onChange),b.warning=a(".file-editor-warning"),b.warning.length>0&&(a("body").addClass("modal-open"),b.warning.find(".file-editor-warning-dismiss").focus(),b.warning.on("click",".file-editor-warning-dismiss",b.dismissWarning)),!1!==b.codeEditor&&_.defer(function(){b.initCodeEditor()}),a(window).on("beforeunload",function(){if(b.dirty)return b.l10n.saveAlert})},b.dismissWarning=function(){wp.ajax.post("dismiss-wp-pointer",{pointer:b.themeOrPlugin+"_editor_notice"}),b.warning.remove(),a("body").removeClass("modal-open"),b.instance.codemirror.focus()},b.onChange=function(){b.dirty=!0,b.removeNotice("file_saved")},b.submit=function(c){var d,e={};if(c.preventDefault(),a.each(b.form.serializeArray(),function(){e[this.name]=this.value}),b.instance&&(e.newcontent=b.instance.codemirror.getValue()),!b.isSaving){if(b.lintErrors.length)return void b.instance.codemirror.setCursor(b.lintErrors[0].from.line);b.isSaving=!0,b.textarea.prop("readonly",!0),b.instance&&b.instance.codemirror.setOption("readOnly",!0),b.spinner.addClass("is-active"),d=wp.ajax.post("edit-theme-plugin-file",e),b.lastSaveNoticeCode&&b.removeNotice(b.lastSaveNoticeCode),d.done(function(a){b.lastSaveNoticeCode="file_saved",b.addNotice({code:b.lastSaveNoticeCode,type:"success",message:a.message,dismissible:!0}),b.dirty=!1}),d.fail(function(c){var d=a.extend({code:"save_error",message:b.l10n.saveError},c,{type:"error",dismissible:!0});b.lastSaveNoticeCode=d.code,b.addNotice(d)}),d.always(function(){b.spinner.removeClass("is-active"),b.isSaving=!1,b.textarea.prop("readonly",!1),b.instance&&b.instance.codemirror.setOption("readOnly",!1)})}},b.addNotice=function(c){var d;if(!c.code)throw new Error("Missing code.");return b.removeNotice(c.code),d=a(b.noticeTemplate(c)),d.hide(),d.find(".notice-dismiss").on("click",function(){b.removeNotice(c.code),c.onDismiss&&c.onDismiss(c)}),wp.a11y.speak(c.message),b.noticesContainer.append(d),d.slideDown("fast"),b.noticeElements[c.code]=d,d},b.removeNotice=function(c){return!!b.noticeElements[c]&&(b.noticeElements[c].slideUp("fast",function(){a(this).remove()}),delete b.noticeElements[c],!0)},b.initCodeEditor=function(){var c,d;c=a.extend({},b.codeEditor),c.onTabPrevious=function(){a("#templateside").find(":tabbable").last().focus()},c.onTabNext=function(){a("#template").find(":tabbable:not(.CodeMirror-code)").first().focus()},c.onChangeLintingErrors=function(a){b.lintErrors=a,0===a.length&&b.submitButton.toggleClass("disabled",!1)},c.onUpdateErrorNotice=function(a){var d,e;b.submitButton.toggleClass("disabled",a.length>0),0!==a.length?(d=1===a.length?b.l10n.lintError.singular.replace("%d","1"):b.l10n.lintError.plural.replace("%d",String(a.length)),e=b.addNotice({code:"lint_errors",type:"error",message:d,dismissible:!1}),e.find("input[type=checkbox]").on("click",function(){c.onChangeLintingErrors([]),b.removeNotice("lint_errors")})):b.removeNotice("lint_errors")},d=wp.codeEditor.initialize(a("#newcontent"),c),d.codemirror.on("change",b.onChange),a(d.codemirror.display.lineDiv).attr({role:"textbox","aria-multiline":"true","aria-labelledby":"theme-plugin-editor-label","aria-describedby":"editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4"}),a("#theme-plugin-editor-label").on("click",function(){d.codemirror.focus()}),b.instance=d},b}(jQuery);

View File

@ -1126,11 +1126,12 @@ function wp_start_scraping_edited_file_errors() {
$nonce = wp_unslash( $_REQUEST['wp_scrape_nonce'] );
if ( get_transient( 'scrape_key_' . $key ) !== $nonce ) {
echo "###### begin_scraped_error:$key ######";
echo "###### wp_scraping_result_start:$key ######";
echo wp_json_encode( array(
'code' => 'scrape_nonce_failure',
'message' => __( 'Scrape nonce check failed. Please try again.' ),
) );
echo "###### wp_scraping_result_end:$key ######";
die();
}
register_shutdown_function( 'wp_finalize_scraping_edited_file_errors', $key );
@ -1145,13 +1146,12 @@ function wp_start_scraping_edited_file_errors() {
*/
function wp_finalize_scraping_edited_file_errors( $scrape_key ) {
$error = error_get_last();
if ( empty( $error ) ) {
return;
echo "\n###### wp_scraping_result_start:$scrape_key ######\n";
if ( ! empty( $error ) && in_array( $error['type'], array( E_CORE_ERROR, E_COMPILE_ERROR, E_ERROR, E_PARSE, E_USER_ERROR, E_RECOVERABLE_ERROR ), true ) ) {
$error = str_replace( ABSPATH, '', $error );
echo wp_json_encode( $error );
} else {
echo wp_json_encode( true );
}
if ( ! in_array( $error['type'], array( E_CORE_ERROR, E_COMPILE_ERROR, E_ERROR, E_PARSE, E_USER_ERROR, E_RECOVERABLE_ERROR ), true ) ) {
return;
}
$error = str_replace( ABSPATH, '', $error );
echo "###### begin_scraped_error:$scrape_key ######";
echo wp_json_encode( $error );
echo "\n###### wp_scraping_result_end:$scrape_key ######\n";
}

View File

@ -472,6 +472,7 @@ function wp_default_scripts( &$scripts ) {
$scripts->add( 'wp-theme-plugin-editor', "/wp-admin/js/theme-plugin-editor$suffix.js", array( 'wp-util', 'jquery', 'jquery-ui-core', 'wp-a11y', 'underscore' ) );
did_action( 'init' ) && $scripts->add_inline_script( 'wp-theme-plugin-editor', sprintf( 'wp.themePluginEditor.l10n = %s;', wp_json_encode( array(
'saveAlert' => __( 'The changes you made will be lost if you navigate away from this page.' ),
'saveError' => __( 'Something went wrong. Your change may not have been saved. Please try again. There is also a chance that you may need to manually fix and upload the file over FTP.' ),
'lintError' => wp_array_slice_assoc(
/* translators: %d: error count */
_n_noop( 'There is %d error which must be fixed before you can update this file.', 'There are %d errors which must be fixed before you can update this file.' ),

View File

@ -4,7 +4,7 @@
*
* @global string $wp_version
*/
$wp_version = '4.9-beta1-41804';
$wp_version = '4.9-beta1-41805';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.