File Editors: Introduce sandboxed live editing of PHP files with rollbacks for both themes and plugins.
* Edits to active plugins which cause PHP fatal errors will no longer auto-deactivate the plugin. Supersedes #39766. * Introduce sandboxed PHP file edits for active themes, preventing accidental whitescreening of a user's site when introducing a fatal error. * After writing a change to a PHP file for an active theme or plugin, perform loopback requests on the file editor admin screens and the homepage to check for fatal errors. If a fatal error is encountered, roll back the edited file and display the error to the user to fix and try again. * Introduce a secure way to scrape PHP fatal errors from a site via `wp_start_scraping_edited_file_errors()` and `wp_finalize_scraping_edited_file_errors()`. * Moves file modifications from `theme-editor.php` and `plugin-editor.php` to common `wp_edit_theme_plugin_file()` function. * Refactor themes and plugin editors to submit file changes via Ajax instead of doing full page refreshes when JS is available. * Use `get` method for theme/plugin dropdowns. * Improve styling of plugin editors, including width of plugin/theme dropdowns. * Improve notices API for theme/plugin editor JS component. * Strip common base directory from plugin file list. See #24048. * Factor out functions to list editable file types in `wp_get_theme_file_editable_extensions()` and `wp_get_plugin_file_editable_extensions()`. * Scroll to line in editor that has linting error when attempting to save. See #41886. * Add checkbox to dismiss lint errors to proceed with saving. See #41887. * Only style the Update File button as disabled instead of actually disabling it for accessibility reasons. * Ensure that value from CodeMirror is used instead of `textarea` when CodeMirror is present. * Add "Are you sure?" check when leaving editor when there are unsaved changes. Supersedes [41560]. See #39766, #24048, #41886. Props westonruter, Clorith, melchoyce, johnbillion, jjj, jdgrimes, azaozz. Fixes #21622, #41887. Built from https://develop.svn.wordpress.org/trunk@41721 git-svn-id: http://core.svn.wordpress.org/trunk@41555 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
parent
0909a73eed
commit
5f7a5c1246
|
@ -64,7 +64,7 @@ $core_actions_post = array(
|
||||||
'parse-media-shortcode', 'destroy-sessions', 'install-plugin', 'update-plugin', 'crop-image',
|
'parse-media-shortcode', 'destroy-sessions', 'install-plugin', 'update-plugin', 'crop-image',
|
||||||
'generate-password', 'save-wporg-username', 'delete-plugin', 'search-plugins',
|
'generate-password', 'save-wporg-username', 'delete-plugin', 'search-plugins',
|
||||||
'search-install-plugins', 'activate-plugin', 'update-theme', 'delete-theme', 'install-theme',
|
'search-install-plugins', 'activate-plugin', 'update-theme', 'delete-theme', 'install-theme',
|
||||||
'get-post-thumbnail-html', 'get-community-events',
|
'get-post-thumbnail-html', 'get-community-events', 'edit-theme-plugin-file',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
|
|
|
@ -2217,14 +2217,16 @@ h1.nav-tab-wrapper, /* Back-compat for pre-4.4 */
|
||||||
#template > div {
|
#template > div {
|
||||||
margin-left: 190px;
|
margin-left: 190px;
|
||||||
}
|
}
|
||||||
#template .active-plugin-edit-warning {
|
#template .notice {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
margin-left: 30%;
|
margin-left: 3%;
|
||||||
margin-left: calc( 184px + 3% );
|
|
||||||
}
|
}
|
||||||
#template .active-plugin-edit-warning p {
|
#template .notice p {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
#template .submit .spinner {
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
|
||||||
.metabox-holder .stuffbox > h3, /* Back-compat for pre-4.4 */
|
.metabox-holder .stuffbox > h3, /* Back-compat for pre-4.4 */
|
||||||
.metabox-holder .postbox > h3, /* Back-compat for pre-4.4 */
|
.metabox-holder .postbox > h3, /* Back-compat for pre-4.4 */
|
||||||
|
@ -3032,10 +3034,14 @@ img {
|
||||||
#template textarea,
|
#template textarea,
|
||||||
#template .CodeMirror {
|
#template .CodeMirror {
|
||||||
width: 97%;
|
width: 97%;
|
||||||
height: calc( 100vh - 220px );
|
height: calc( 100vh - 280px );
|
||||||
|
}
|
||||||
|
#templateside {
|
||||||
|
margin-top: 31px;
|
||||||
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
#template label {
|
#theme-plugin-editor-label {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
@ -3047,6 +3053,14 @@ img {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fileedit-sub #theme,
|
||||||
|
.fileedit-sub #plugin {
|
||||||
|
max-width: 40%;
|
||||||
|
}
|
||||||
|
.fileedit-sub .alignright {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
#template p {
|
#template p {
|
||||||
width: 97%;
|
width: 97%;
|
||||||
}
|
}
|
||||||
|
@ -3624,7 +3638,7 @@ img {
|
||||||
}
|
}
|
||||||
|
|
||||||
#template > div,
|
#template > div,
|
||||||
#template .active-plugin-edit-warning {
|
#template .notice {
|
||||||
float: none;
|
float: none;
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -2217,14 +2217,16 @@ h1.nav-tab-wrapper, /* Back-compat for pre-4.4 */
|
||||||
#template > div {
|
#template > div {
|
||||||
margin-right: 190px;
|
margin-right: 190px;
|
||||||
}
|
}
|
||||||
#template .active-plugin-edit-warning {
|
#template .notice {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
margin-right: 30%;
|
margin-right: 3%;
|
||||||
margin-right: calc( 184px + 3% );
|
|
||||||
}
|
}
|
||||||
#template .active-plugin-edit-warning p {
|
#template .notice p {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
#template .submit .spinner {
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
|
||||||
.metabox-holder .stuffbox > h3, /* Back-compat for pre-4.4 */
|
.metabox-holder .stuffbox > h3, /* Back-compat for pre-4.4 */
|
||||||
.metabox-holder .postbox > h3, /* Back-compat for pre-4.4 */
|
.metabox-holder .postbox > h3, /* Back-compat for pre-4.4 */
|
||||||
|
@ -3032,10 +3034,14 @@ img {
|
||||||
#template textarea,
|
#template textarea,
|
||||||
#template .CodeMirror {
|
#template .CodeMirror {
|
||||||
width: 97%;
|
width: 97%;
|
||||||
height: calc( 100vh - 220px );
|
height: calc( 100vh - 280px );
|
||||||
|
}
|
||||||
|
#templateside {
|
||||||
|
margin-top: 31px;
|
||||||
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
#template label {
|
#theme-plugin-editor-label {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
@ -3047,6 +3053,14 @@ img {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fileedit-sub #theme,
|
||||||
|
.fileedit-sub #plugin {
|
||||||
|
max-width: 40%;
|
||||||
|
}
|
||||||
|
.fileedit-sub .alignright {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
#template p {
|
#template p {
|
||||||
width: 97%;
|
width: 97%;
|
||||||
}
|
}
|
||||||
|
@ -3624,7 +3638,7 @@ img {
|
||||||
}
|
}
|
||||||
|
|
||||||
#template > div,
|
#template > div,
|
||||||
#template .active-plugin-edit-warning {
|
#template .notice {
|
||||||
float: none;
|
float: none;
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -3966,3 +3966,26 @@ function wp_ajax_search_install_plugins() {
|
||||||
|
|
||||||
wp_send_json_success( $status );
|
wp_send_json_success( $status );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ajax handler for editing a theme or plugin file.
|
||||||
|
*
|
||||||
|
* @since 4.9.0
|
||||||
|
* @see wp_edit_theme_plugin_file()
|
||||||
|
*/
|
||||||
|
function wp_ajax_edit_theme_plugin_file() {
|
||||||
|
$r = wp_edit_theme_plugin_file( wp_unslash( $_POST ) ); // Validation of args is done in wp_edit_theme_plugin_file().
|
||||||
|
if ( is_wp_error( $r ) ) {
|
||||||
|
wp_send_json_error( array_merge(
|
||||||
|
array(
|
||||||
|
'code' => $r->get_error_code(),
|
||||||
|
'message' => $r->get_error_message(),
|
||||||
|
),
|
||||||
|
(array) $r->get_error_data()
|
||||||
|
) );
|
||||||
|
} else {
|
||||||
|
wp_send_json_success( array(
|
||||||
|
'message' => __( 'File edited successfully.' ),
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ $wp_file_descriptions = array(
|
||||||
* @since 1.5.0
|
* @since 1.5.0
|
||||||
*
|
*
|
||||||
* @global array $wp_file_descriptions Theme file descriptions.
|
* @global array $wp_file_descriptions Theme file descriptions.
|
||||||
* @global array $allowed_files List of allowed files.
|
* @global array $allowed_files List of allowed files.
|
||||||
* @param string $file Filesystem path or filename
|
* @param string $file Filesystem path or filename
|
||||||
* @return string Description of file from $wp_file_descriptions or basename of $file if description doesn't exist.
|
* @return string Description of file from $wp_file_descriptions or basename of $file if description doesn't exist.
|
||||||
* Appends 'Page Template' to basename of $file if the file is a page template
|
* Appends 'Page Template' to basename of $file if the file is a page template
|
||||||
|
@ -152,6 +152,398 @@ function list_files( $folder = '', $levels = 100 ) {
|
||||||
return $files;
|
return $files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of file extensions that are editable in plugins.
|
||||||
|
*
|
||||||
|
* @since 4.9.0
|
||||||
|
*
|
||||||
|
* @param string $plugin Plugin.
|
||||||
|
* @return array File extensions.
|
||||||
|
*/
|
||||||
|
function wp_get_plugin_file_editable_extensions( $plugin ) {
|
||||||
|
|
||||||
|
$editable_extensions = array(
|
||||||
|
'bash',
|
||||||
|
'conf',
|
||||||
|
'css',
|
||||||
|
'diff',
|
||||||
|
'htm',
|
||||||
|
'html',
|
||||||
|
'http',
|
||||||
|
'inc',
|
||||||
|
'include',
|
||||||
|
'js',
|
||||||
|
'json',
|
||||||
|
'jsx',
|
||||||
|
'less',
|
||||||
|
'md',
|
||||||
|
'patch',
|
||||||
|
'php',
|
||||||
|
'php3',
|
||||||
|
'php4',
|
||||||
|
'php5',
|
||||||
|
'php7',
|
||||||
|
'phps',
|
||||||
|
'phtml',
|
||||||
|
'sass',
|
||||||
|
'scss',
|
||||||
|
'sh',
|
||||||
|
'sql',
|
||||||
|
'svg',
|
||||||
|
'text',
|
||||||
|
'txt',
|
||||||
|
'xml',
|
||||||
|
'yaml',
|
||||||
|
'yml',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters file type extensions editable in the plugin editor.
|
||||||
|
*
|
||||||
|
* @since 2.8.0
|
||||||
|
* @since 4.9.0 Adds $plugin param.
|
||||||
|
*
|
||||||
|
* @param string $plugin Plugin file.
|
||||||
|
* @param array $editable_extensions An array of editable plugin file extensions.
|
||||||
|
*/
|
||||||
|
$editable_extensions = (array) apply_filters( 'editable_extensions', $editable_extensions, $plugin );
|
||||||
|
|
||||||
|
return $editable_extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of file extensions that are editable for a given theme.
|
||||||
|
*
|
||||||
|
* @param WP_Theme $theme Theme.
|
||||||
|
* @return array File extensions.
|
||||||
|
*/
|
||||||
|
function wp_get_theme_file_editable_extensions( $theme ) {
|
||||||
|
|
||||||
|
$default_types = array(
|
||||||
|
'bash',
|
||||||
|
'conf',
|
||||||
|
'css',
|
||||||
|
'diff',
|
||||||
|
'htm',
|
||||||
|
'html',
|
||||||
|
'http',
|
||||||
|
'inc',
|
||||||
|
'include',
|
||||||
|
'js',
|
||||||
|
'json',
|
||||||
|
'jsx',
|
||||||
|
'less',
|
||||||
|
'md',
|
||||||
|
'patch',
|
||||||
|
'php',
|
||||||
|
'php3',
|
||||||
|
'php4',
|
||||||
|
'php5',
|
||||||
|
'php7',
|
||||||
|
'phps',
|
||||||
|
'phtml',
|
||||||
|
'sass',
|
||||||
|
'scss',
|
||||||
|
'sh',
|
||||||
|
'sql',
|
||||||
|
'svg',
|
||||||
|
'text',
|
||||||
|
'txt',
|
||||||
|
'xml',
|
||||||
|
'yaml',
|
||||||
|
'yml',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the list of file types allowed for editing in the Theme editor.
|
||||||
|
*
|
||||||
|
* @since 4.4.0
|
||||||
|
*
|
||||||
|
* @param array $default_types List of file types. Default types include 'php' and 'css'.
|
||||||
|
* @param WP_Theme $theme The current Theme object.
|
||||||
|
*/
|
||||||
|
$file_types = apply_filters( 'wp_theme_editor_filetypes', $default_types, $theme );
|
||||||
|
|
||||||
|
// Ensure that default types are still there.
|
||||||
|
return array_unique( array_merge( $file_types, $default_types ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print file editor templates (for plugins and themes).
|
||||||
|
*
|
||||||
|
* @since 4.9.0
|
||||||
|
*/
|
||||||
|
function wp_print_file_editor_templates() {
|
||||||
|
?>
|
||||||
|
<script type="text/html" id="tmpl-wp-file-editor-notice">
|
||||||
|
<div class="notice inline notice-{{ data.type || 'info' }} {{ data.alt ? 'notice-alt' : '' }} {{ data.dismissible ? 'is-dismissible' : '' }} {{ data.classes || '' }}">
|
||||||
|
<# if ( 'php_error' === data.code ) { #>
|
||||||
|
<p>
|
||||||
|
<?php
|
||||||
|
printf(
|
||||||
|
/* translators: %$1s is line number and %1$s is file path. */
|
||||||
|
__( 'Your PHP code changes were rolled back due to an error on line %1$s of file %2$s. Please fix and try saving again.' ),
|
||||||
|
'{{ data.line }}',
|
||||||
|
'{{ data.file }}'
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</p>
|
||||||
|
<pre>{{ data.message }}</pre>
|
||||||
|
<# } else if ( 'file_not_writable' === data.code ) { #>
|
||||||
|
<p><?php _e( 'You need to make this file writable before you can save your changes. See <a href="https://codex.wordpress.org/Changing_File_Permissions">the Codex</a> for more information.' ); ?></p>
|
||||||
|
<# } else { #>
|
||||||
|
<p>{{ data.message || data.code }}</p>
|
||||||
|
|
||||||
|
<# if ( 'lint_errors' === data.code ) { #>
|
||||||
|
<p>
|
||||||
|
<# var elementId = 'el-' + String( Math.random() ); #>
|
||||||
|
<input id="{{ elementId }}" type="checkbox">
|
||||||
|
<label for="{{ elementId }}"><?php _e( 'Update anyway, even though it might break your site?' ); ?></label>
|
||||||
|
</p>
|
||||||
|
<# } #>
|
||||||
|
<# } #>
|
||||||
|
<# if ( data.dismissible ) { #>
|
||||||
|
<button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php _e( 'Dismiss' ); ?></span></button>
|
||||||
|
<# } #>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to edit a file for a theme or plugin.
|
||||||
|
*
|
||||||
|
* When editing a PHP file, loopback requests will be made to the admin and the homepage
|
||||||
|
* to attempt to see if there is a fatal error introduced. If so, the PHP change will be
|
||||||
|
* reverted.
|
||||||
|
*
|
||||||
|
* @since 4.9.0
|
||||||
|
*
|
||||||
|
* @param array $args {
|
||||||
|
* Args. Note that all of the arg values are already unslashed. They are, however,
|
||||||
|
* coming straight from $_POST and are not validated or sanitized in any way.
|
||||||
|
*
|
||||||
|
* @type string $file Relative path to file.
|
||||||
|
* @type string $plugin Plugin being edited.
|
||||||
|
* @type string $theme Theme being edited.
|
||||||
|
* @type string $newcontent New content for the file.
|
||||||
|
* @type string $nonce Nonce.
|
||||||
|
* }
|
||||||
|
* @return true|WP_Error True on success or `WP_Error` on failure.
|
||||||
|
*/
|
||||||
|
function wp_edit_theme_plugin_file( $args ) {
|
||||||
|
if ( empty( $args['file'] ) ) {
|
||||||
|
return new WP_Error( 'missing_file' );
|
||||||
|
}
|
||||||
|
$file = $args['file'];
|
||||||
|
if ( 0 !== validate_file( $file ) ) {
|
||||||
|
return new WP_Error( 'bad_file' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! isset( $args['newcontent'] ) ) {
|
||||||
|
return new WP_Error( 'missing_content' );
|
||||||
|
}
|
||||||
|
$content = $args['newcontent'];
|
||||||
|
|
||||||
|
if ( ! isset( $args['nonce'] ) ) {
|
||||||
|
return new WP_Error( 'missing_nonce' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$plugin = null;
|
||||||
|
$theme = null;
|
||||||
|
$real_file = null;
|
||||||
|
if ( ! empty( $args['plugin'] ) ) {
|
||||||
|
$plugin = $args['plugin'];
|
||||||
|
|
||||||
|
if ( ! current_user_can( 'edit_plugins' ) ) {
|
||||||
|
return new WP_Error( 'unauthorized', __( 'Sorry, you are not allowed to edit plugins for this site.' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! wp_verify_nonce( $args['nonce'], 'edit-plugin_' . $file ) ) {
|
||||||
|
return new WP_Error( 'nonce_failure' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! array_key_exists( $plugin, get_plugins() ) ) {
|
||||||
|
return new WP_Error( 'invalid_plugin' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( 0 !== validate_file( $file, get_plugin_files( $plugin ) ) ) {
|
||||||
|
return new WP_Error( 'bad_plugin_file_path', __( 'Sorry, that file cannot be edited.' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$editable_extensions = wp_get_plugin_file_editable_extensions( $plugin );
|
||||||
|
|
||||||
|
$real_file = WP_PLUGIN_DIR . '/' . $file;
|
||||||
|
|
||||||
|
$is_active = in_array(
|
||||||
|
$plugin,
|
||||||
|
(array) get_option( 'active_plugins', array() ),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
} elseif ( ! empty( $args['theme'] ) ) {
|
||||||
|
$stylesheet = $args['theme'];
|
||||||
|
if ( 0 !== validate_file( $stylesheet ) ) {
|
||||||
|
return new WP_Error( 'bad_theme_path' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! current_user_can( 'edit_themes' ) ) {
|
||||||
|
return new WP_Error( 'unauthorized', __( 'Sorry, you are not allowed to edit templates for this site.' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$theme = wp_get_theme( $stylesheet );
|
||||||
|
if ( ! $theme->exists() ) {
|
||||||
|
return new WP_Error( 'non_existent_theme', __( 'The requested theme does not exist.' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$real_file = $theme->get_stylesheet_directory() . '/' . $file;
|
||||||
|
if ( ! wp_verify_nonce( $args['nonce'], 'edit-theme_' . $real_file . $stylesheet ) ) {
|
||||||
|
return new WP_Error( 'nonce_failure' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $theme->errors() && 'theme_no_stylesheet' === $theme->errors()->get_error_code() ) {
|
||||||
|
return new WP_Error(
|
||||||
|
'theme_no_stylesheet',
|
||||||
|
__( 'The requested theme does not exist.' ) . ' ' . $theme->errors()->get_error_message()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$editable_extensions = wp_get_theme_file_editable_extensions( $theme );
|
||||||
|
|
||||||
|
$allowed_files = array();
|
||||||
|
foreach ( $editable_extensions as $type ) {
|
||||||
|
switch ( $type ) {
|
||||||
|
case 'php':
|
||||||
|
$allowed_files = array_merge( $allowed_files, $theme->get_files( 'php', 1 ) );
|
||||||
|
break;
|
||||||
|
case 'css':
|
||||||
|
$style_files = $theme->get_files( 'css' );
|
||||||
|
$allowed_files['style.css'] = $style_files['style.css'];
|
||||||
|
$allowed_files = array_merge( $allowed_files, $style_files );
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$allowed_files = array_merge( $allowed_files, $theme->get_files( $type ) );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( 0 !== validate_file( $real_file, $allowed_files ) ) {
|
||||||
|
return new WP_Error( 'disallowed_theme_file', __( 'Sorry, that file cannot be edited.' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$is_active = ( get_stylesheet() === $stylesheet || get_template() === $stylesheet );
|
||||||
|
} else {
|
||||||
|
return new WP_Error( 'missing_theme_or_plugin' );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure file is real.
|
||||||
|
if ( ! is_file( $real_file ) ) {
|
||||||
|
return new WP_Error( 'file_does_not_exist', __( 'No such file exists! Double check the name and try again.' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure file extension is allowed.
|
||||||
|
$extension = null;
|
||||||
|
if ( preg_match( '/\.([^.]+)$/', $real_file, $matches ) ) {
|
||||||
|
$extension = strtolower( $matches[1] );
|
||||||
|
if ( ! in_array( $extension, $editable_extensions, true ) ) {
|
||||||
|
return new WP_Error( 'illegal_file_type', __( 'Files of this type are not editable.' ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$previous_content = file_get_contents( $real_file );
|
||||||
|
|
||||||
|
if ( ! is_writeable( $real_file ) ) {
|
||||||
|
return new WP_Error( 'file_not_writable' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$f = fopen( $real_file, 'w+' );
|
||||||
|
if ( false === $f ) {
|
||||||
|
return new WP_Error( 'file_not_writable' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$written = fwrite( $f, $content );
|
||||||
|
fclose( $f );
|
||||||
|
if ( false === $written ) {
|
||||||
|
return new WP_Error( 'unable_to_write', __( 'Unable to write to file.' ) );
|
||||||
|
}
|
||||||
|
if ( 'php' === $extension && function_exists( 'opcache_invalidate' ) ) {
|
||||||
|
opcache_invalidate( $real_file, true );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $is_active && 'php' === $extension ) {
|
||||||
|
|
||||||
|
$scrape_key = md5( rand() );
|
||||||
|
$transient = 'scrape_key_' . $scrape_key;
|
||||||
|
$scrape_nonce = strval( rand() );
|
||||||
|
set_transient( $transient, $scrape_nonce, 60 ); // It shouldn't take more than 60 seconds to make the two loopback requests.
|
||||||
|
|
||||||
|
$cookies = wp_unslash( $_COOKIE );
|
||||||
|
$scrape_params = array(
|
||||||
|
'wp_scrape_key' => $scrape_key,
|
||||||
|
'wp_scrape_nonce' => $scrape_nonce,
|
||||||
|
);
|
||||||
|
$headers = array(
|
||||||
|
'Cache-Control' => 'no-cache',
|
||||||
|
);
|
||||||
|
|
||||||
|
$needle = "###### begin_scraped_error:$scrape_key ######";
|
||||||
|
|
||||||
|
// Attempt loopback request to editor to see if user just whitescreened themselves.
|
||||||
|
if ( $plugin ) {
|
||||||
|
$url = add_query_arg( compact( 'plugin', 'file' ), admin_url( 'plugin-editor.php' ) );
|
||||||
|
} elseif ( isset( $stylesheet ) ) {
|
||||||
|
$url = add_query_arg(
|
||||||
|
array(
|
||||||
|
'theme' => $stylesheet,
|
||||||
|
'file' => $file,
|
||||||
|
),
|
||||||
|
admin_url( 'theme-editor.php' )
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$url = admin_url();
|
||||||
|
}
|
||||||
|
$url = add_query_arg( $scrape_params, $url );
|
||||||
|
$r = wp_remote_get( $url, compact( 'cookies', 'headers' ) );
|
||||||
|
$body = wp_remote_retrieve_body( $r );
|
||||||
|
$error_position = strpos( $body, $needle );
|
||||||
|
|
||||||
|
// Try making request to homepage as well to see if visitors have been whitescreened.
|
||||||
|
if ( false === $error_position ) {
|
||||||
|
$url = home_url( '/' );
|
||||||
|
$url = add_query_arg( $scrape_params, $url );
|
||||||
|
$r = wp_remote_get( $url, compact( 'cookies', 'headers' ) );
|
||||||
|
$body = wp_remote_retrieve_body( $r );
|
||||||
|
$error_position = strpos( $body, $needle );
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_transient( $transient );
|
||||||
|
|
||||||
|
if ( false !== $error_position ) {
|
||||||
|
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;
|
||||||
|
} else {
|
||||||
|
$message = $error['message'];
|
||||||
|
unset( $error['message'] );
|
||||||
|
}
|
||||||
|
return new WP_Error( 'php_error', $message, $error );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $theme instanceof WP_Theme ) {
|
||||||
|
$theme->cache_delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a filename of a Temporary unique file.
|
* Returns a filename of a Temporary unique file.
|
||||||
* Please note that the calling function must unlink() this itself.
|
* Please note that the calling function must unlink() this itself.
|
||||||
|
|
|
@ -12,25 +12,227 @@ wp.themePluginEditor = (function( $ ) {
|
||||||
lintError: {
|
lintError: {
|
||||||
singular: '',
|
singular: '',
|
||||||
plural: ''
|
plural: ''
|
||||||
}
|
},
|
||||||
|
saveAlert: ''
|
||||||
},
|
},
|
||||||
instance: null
|
codeEditor: {},
|
||||||
|
instance: null,
|
||||||
|
noticeElements: {},
|
||||||
|
dirty: false,
|
||||||
|
lintErrors: []
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize component.
|
* Initialize component.
|
||||||
*
|
*
|
||||||
* @param {object} settings Settings.
|
* @since 4.9.0
|
||||||
|
*
|
||||||
|
* @param {jQuery} form - Form element.
|
||||||
|
* @param {object} settings - Settings.
|
||||||
|
* @param {object|boolean} settings.codeEditor - Code editor settings (or `false` if syntax highlighting is disabled).
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
component.init = function( settings ) {
|
component.init = function init( form, settings ) {
|
||||||
var codeEditorSettings, noticeContainer, errorNotice = [], editor;
|
|
||||||
|
|
||||||
codeEditorSettings = $.extend( {}, settings );
|
component.form = form;
|
||||||
|
if ( settings ) {
|
||||||
|
$.extend( component, settings );
|
||||||
|
}
|
||||||
|
|
||||||
|
component.noticeTemplate = wp.template( 'wp-file-editor-notice' );
|
||||||
|
component.noticesContainer = component.form.find( '.editor-notices' );
|
||||||
|
component.submitButton = component.form.find( ':input[name=submit]' );
|
||||||
|
component.spinner = component.form.find( '.submit .spinner' );
|
||||||
|
component.form.on( 'submit', component.submit );
|
||||||
|
component.textarea = component.form.find( '#newcontent' );
|
||||||
|
component.textarea.on( 'change', component.onChange );
|
||||||
|
|
||||||
|
if ( false !== component.codeEditor ) {
|
||||||
|
/*
|
||||||
|
* Defer adding notices until after DOM ready as workaround for WP Admin injecting
|
||||||
|
* its own managed dismiss buttons and also to prevent the editor from showing a notice
|
||||||
|
* when the file had linting errors to begin with.
|
||||||
|
*/
|
||||||
|
_.defer( function() {
|
||||||
|
component.initCodeEditor();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
$( window ).on( 'beforeunload', function() {
|
||||||
|
if ( component.dirty ) {
|
||||||
|
return component.l10n.saveAlert;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for when a change happens.
|
||||||
|
*
|
||||||
|
* @since 4.9.0
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
component.onChange = function() {
|
||||||
|
component.dirty = true;
|
||||||
|
component.removeNotice( 'file_saved' );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit file via Ajax.
|
||||||
|
*
|
||||||
|
* @since 4.9.0
|
||||||
|
* @param {jQuery.Event} event - Event.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
component.submit = function( event ) {
|
||||||
|
var data = {}, request;
|
||||||
|
event.preventDefault(); // Prevent form submission in favor of Ajax below.
|
||||||
|
$.each( component.form.serializeArray(), function() {
|
||||||
|
data[ this.name ] = this.value;
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Use value from codemirror if present.
|
||||||
|
if ( component.instance ) {
|
||||||
|
data.newcontent = component.instance.codemirror.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( component.isSaving ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll ot the line that has the error.
|
||||||
|
if ( component.lintErrors.length ) {
|
||||||
|
component.instance.codemirror.setCursor( component.lintErrors[0].from.line );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
component.isSaving = true;
|
||||||
|
component.textarea.prop( 'readonly', true );
|
||||||
|
if ( component.instance ) {
|
||||||
|
component.instance.codemirror.setOption( 'readOnly', true );
|
||||||
|
}
|
||||||
|
|
||||||
|
component.spinner.addClass( 'is-active' );
|
||||||
|
request = wp.ajax.post( 'edit-theme-plugin-file', data );
|
||||||
|
|
||||||
|
// Remove previous save notice before saving.
|
||||||
|
if ( component.lastSaveNoticeCode ) {
|
||||||
|
component.removeNotice( component.lastSaveNoticeCode );
|
||||||
|
}
|
||||||
|
|
||||||
|
request.done( function ( response ) {
|
||||||
|
component.lastSaveNoticeCode = 'file_saved';
|
||||||
|
component.addNotice({
|
||||||
|
code: component.lastSaveNoticeCode,
|
||||||
|
type: 'success',
|
||||||
|
message: response.message,
|
||||||
|
dismissible: true
|
||||||
|
});
|
||||||
|
component.dirty = false;
|
||||||
|
} );
|
||||||
|
|
||||||
|
request.fail( function ( response ) {
|
||||||
|
var notice = $.extend(
|
||||||
|
{
|
||||||
|
code: 'save_error'
|
||||||
|
},
|
||||||
|
response,
|
||||||
|
{
|
||||||
|
type: 'error',
|
||||||
|
dismissible: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
component.lastSaveNoticeCode = notice.code;
|
||||||
|
component.addNotice( notice );
|
||||||
|
} );
|
||||||
|
|
||||||
|
request.always( function() {
|
||||||
|
component.spinner.removeClass( 'is-active' );
|
||||||
|
component.isSaving = false;
|
||||||
|
|
||||||
|
component.textarea.prop( 'readonly', false );
|
||||||
|
if ( component.instance ) {
|
||||||
|
component.instance.codemirror.setOption( 'readOnly', false );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add notice.
|
||||||
|
*
|
||||||
|
* @since 4.9.0
|
||||||
|
*
|
||||||
|
* @param {object} notice - Notice.
|
||||||
|
* @param {string} notice.code - Code.
|
||||||
|
* @param {string} notice.type - Type.
|
||||||
|
* @param {string} notice.message - Message.
|
||||||
|
* @param {boolean} [notice.dismissible=false] - Dismissible.
|
||||||
|
* @param {Function} [notice.onDismiss] - Callback for when a user dismisses the notice.
|
||||||
|
* @returns {jQuery} Notice element.
|
||||||
|
*/
|
||||||
|
component.addNotice = function( notice ) {
|
||||||
|
var noticeElement;
|
||||||
|
|
||||||
|
if ( ! notice.code ) {
|
||||||
|
throw new Error( 'Missing code.' );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only let one notice of a given type be displayed at a time.
|
||||||
|
component.removeNotice( notice.code );
|
||||||
|
|
||||||
|
noticeElement = $( component.noticeTemplate( notice ) );
|
||||||
|
noticeElement.hide();
|
||||||
|
|
||||||
|
noticeElement.find( '.notice-dismiss' ).on( 'click', function() {
|
||||||
|
component.removeNotice( notice.code );
|
||||||
|
if ( notice.onDismiss ) {
|
||||||
|
notice.onDismiss( notice );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
wp.a11y.speak( notice.message );
|
||||||
|
|
||||||
|
component.noticesContainer.append( noticeElement );
|
||||||
|
noticeElement.slideDown( 'fast' );
|
||||||
|
component.noticeElements[ notice.code ] = noticeElement;
|
||||||
|
return noticeElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove notice.
|
||||||
|
*
|
||||||
|
* @since 4.9.0
|
||||||
|
*
|
||||||
|
* @param {string} code - Notice code.
|
||||||
|
* @returns {boolean} Whether a notice was removed.
|
||||||
|
*/
|
||||||
|
component.removeNotice = function( code ) {
|
||||||
|
if ( component.noticeElements[ code ] ) {
|
||||||
|
component.noticeElements[ code ].slideUp( 'fast', function() {
|
||||||
|
$( this ).remove();
|
||||||
|
} );
|
||||||
|
delete component.noticeElements[ code ];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize code editor.
|
||||||
|
*
|
||||||
|
* @since 4.9.0
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
component.initCodeEditor = function initCodeEditor() {
|
||||||
|
var codeEditorSettings, editor;
|
||||||
|
|
||||||
|
codeEditorSettings = $.extend( {}, component.codeEditor );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle tabbing to the field before the editor.
|
* Handle tabbing to the field before the editor.
|
||||||
*
|
*
|
||||||
|
* @since 4.9.0
|
||||||
|
*
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
codeEditorSettings.onTabPrevious = function() {
|
codeEditorSettings.onTabPrevious = function() {
|
||||||
|
@ -40,48 +242,67 @@ wp.themePluginEditor = (function( $ ) {
|
||||||
/**
|
/**
|
||||||
* Handle tabbing to the field after the editor.
|
* Handle tabbing to the field after the editor.
|
||||||
*
|
*
|
||||||
|
* @since 4.9.0
|
||||||
|
*
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
codeEditorSettings.onTabNext = function() {
|
codeEditorSettings.onTabNext = function() {
|
||||||
$( '#template' ).find( ':tabbable:not(.CodeMirror-code)' ).first().focus();
|
$( '#template' ).find( ':tabbable:not(.CodeMirror-code)' ).first().focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create the error notice container.
|
/**
|
||||||
noticeContainer = $( '<div id="file-editor-linting-error"></div>' );
|
* Handle change to the linting errors.
|
||||||
errorNotice = $( '<div class="inline notice notice-error"></div>' );
|
*
|
||||||
noticeContainer.append( errorNotice );
|
* @since 4.9.0
|
||||||
noticeContainer.hide();
|
*
|
||||||
$( 'p.submit' ).before( noticeContainer );
|
* @param {Array} errors - List of linting errors.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
codeEditorSettings.onChangeLintingErrors = function( errors ) {
|
||||||
|
component.lintErrors = errors;
|
||||||
|
|
||||||
|
// Only disable the button in onUpdateErrorNotice when there are errors so users can still feel they can click the button.
|
||||||
|
if ( 0 === errors.length ) {
|
||||||
|
component.submitButton.toggleClass( 'disabled', false );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update error notice.
|
* Update error notice.
|
||||||
*
|
*
|
||||||
|
* @since 4.9.0
|
||||||
|
*
|
||||||
* @param {Array} errorAnnotations - Error annotations.
|
* @param {Array} errorAnnotations - Error annotations.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
codeEditorSettings.onUpdateErrorNotice = function onUpdateErrorNotice( errorAnnotations ) {
|
codeEditorSettings.onUpdateErrorNotice = function onUpdateErrorNotice( errorAnnotations ) {
|
||||||
var message;
|
var message, noticeElement;
|
||||||
|
|
||||||
$( '#submit' ).prop( 'disabled', 0 !== errorAnnotations.length );
|
component.submitButton.toggleClass( 'disabled', errorAnnotations.length > 0 );
|
||||||
|
|
||||||
if ( 0 !== errorAnnotations.length ) {
|
if ( 0 !== errorAnnotations.length ) {
|
||||||
errorNotice.empty();
|
|
||||||
if ( 1 === errorAnnotations.length ) {
|
if ( 1 === errorAnnotations.length ) {
|
||||||
message = component.l10n.singular.replace( '%d', '1' );
|
message = component.l10n.lintError.singular.replace( '%d', '1' );
|
||||||
} else {
|
} else {
|
||||||
message = component.l10n.plural.replace( '%d', String( errorAnnotations.length ) );
|
message = component.l10n.lintError.plural.replace( '%d', String( errorAnnotations.length ) );
|
||||||
}
|
}
|
||||||
errorNotice.append( $( '<p></p>', {
|
noticeElement = component.addNotice({
|
||||||
text: message
|
code: 'lint_errors',
|
||||||
} ) );
|
type: 'error',
|
||||||
noticeContainer.slideDown( 'fast' );
|
message: message,
|
||||||
wp.a11y.speak( message );
|
dismissible: false
|
||||||
|
});
|
||||||
|
noticeElement.find( 'input[type=checkbox]' ).on( 'click', function() {
|
||||||
|
codeEditorSettings.onChangeLintingErrors( [] );
|
||||||
|
component.removeNotice( 'lint_errors' );
|
||||||
|
} );
|
||||||
} else {
|
} else {
|
||||||
noticeContainer.slideUp( 'fast' );
|
component.removeNotice( 'lint_errors' );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
editor = wp.codeEditor.initialize( $( '#newcontent' ), codeEditorSettings );
|
editor = wp.codeEditor.initialize( $( '#newcontent' ), codeEditorSettings );
|
||||||
|
editor.codemirror.on( 'change', component.onChange );
|
||||||
|
|
||||||
// Improve the editor accessibility.
|
// Improve the editor accessibility.
|
||||||
$( editor.codemirror.display.lineDiv )
|
$( editor.codemirror.display.lineDiv )
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
window.wp||(window.wp={}),wp.themePluginEditor=function(a){"use strict";var b={l10n:{lintError:{singular:"",plural:""}},instance:null};return b.init=function(c){var d,e,f,g=[];d=a.extend({},c),d.onTabPrevious=function(){a("#templateside").find(":tabbable").last().focus()},d.onTabNext=function(){a("#template").find(":tabbable:not(.CodeMirror-code)").first().focus()},e=a('<div id="file-editor-linting-error"></div>'),g=a('<div class="inline notice notice-error"></div>'),e.append(g),e.hide(),a("p.submit").before(e),d.onUpdateErrorNotice=function(c){var d;a("#submit").prop("disabled",0!==c.length),0!==c.length?(g.empty(),d=1===c.length?b.l10n.singular.replace("%d","1"):b.l10n.plural.replace("%d",String(c.length)),g.append(a("<p></p>",{text:d})),e.slideDown("fast"),wp.a11y.speak(d)):e.slideUp("fast")},f=wp.codeEditor.initialize(a("#newcontent"),d),a(f.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(){f.codemirror.focus()}),b.instance=f},b}(jQuery);
|
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),!1!==b.codeEditor&&_.defer(function(){b.initCodeEditor()}),a(window).on("beforeunload",function(){if(b.dirty)return b.l10n.saveAlert})},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);
|
|
@ -68,113 +68,38 @@ if ( empty( $plugin ) ) {
|
||||||
|
|
||||||
$plugin_files = get_plugin_files($plugin);
|
$plugin_files = get_plugin_files($plugin);
|
||||||
|
|
||||||
if ( empty($file) )
|
if ( empty( $file ) ) {
|
||||||
$file = $plugin_files[0];
|
$file = $plugin_files[0];
|
||||||
|
}
|
||||||
|
|
||||||
$file = validate_file_to_edit($file, $plugin_files);
|
$file = validate_file_to_edit($file, $plugin_files);
|
||||||
$real_file = WP_PLUGIN_DIR . '/' . $file;
|
$real_file = WP_PLUGIN_DIR . '/' . $file;
|
||||||
$scrollto = isset($_REQUEST['scrollto']) ? (int) $_REQUEST['scrollto'] : 0;
|
|
||||||
|
|
||||||
if ( isset( $_REQUEST['action'] ) && 'update' === $_REQUEST['action'] ) {
|
// Handle fallback editing of file when JavaScript is not available.
|
||||||
|
$edit_error = null;
|
||||||
check_admin_referer('edit-plugin_' . $file);
|
$posted_content = null;
|
||||||
|
if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) {
|
||||||
$newcontent = wp_unslash( $_POST['newcontent'] );
|
$r = wp_edit_theme_plugin_file( wp_unslash( $_POST ) );
|
||||||
if ( is_writeable($real_file) ) {
|
if ( is_wp_error( $r ) ) {
|
||||||
$f = fopen($real_file, 'w+');
|
$edit_error = $r;
|
||||||
fwrite($f, $newcontent);
|
if ( check_ajax_referer( 'edit-plugin_' . $file, 'nonce', false ) && isset( $_POST['newcontent'] ) ) {
|
||||||
fclose($f);
|
$posted_content = wp_unslash( $_POST['newcontent'] );
|
||||||
|
|
||||||
if ( preg_match( '/\.php$/', $real_file ) && function_exists( 'opcache_invalidate' ) ) {
|
|
||||||
opcache_invalidate( $real_file, true );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$network_wide = is_plugin_active_for_network( $file );
|
|
||||||
|
|
||||||
// Deactivate so we can test it.
|
|
||||||
if ( is_plugin_active( $plugin ) || isset( $_POST['phperror'] ) ) {
|
|
||||||
if ( is_plugin_active( $plugin ) ) {
|
|
||||||
deactivate_plugins( $plugin, true );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! is_network_admin() ) {
|
|
||||||
update_option( 'recently_activated', array( $file => time() ) + (array) get_option( 'recently_activated' ) );
|
|
||||||
} else {
|
|
||||||
update_site_option( 'recently_activated', array( $file => time() ) + (array) get_site_option( 'recently_activated' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_redirect( add_query_arg( '_wpnonce', wp_create_nonce( 'edit-plugin-test_' . $file ), "plugin-editor.php?file=$file&plugin=$plugin&liveupdate=1&scrollto=$scrollto&networkwide=" . $network_wide ) );
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
wp_redirect( self_admin_url( "plugin-editor.php?file=$file&plugin=$plugin&a=te&scrollto=$scrollto" ) );
|
|
||||||
} else {
|
} else {
|
||||||
wp_redirect( self_admin_url( "plugin-editor.php?file=$file&plugin=$plugin&scrollto=$scrollto" ) );
|
wp_redirect( add_query_arg(
|
||||||
}
|
array(
|
||||||
exit;
|
'a' => 1, // This means "success" for some reason.
|
||||||
|
'plugin' => $plugin,
|
||||||
} else {
|
'file' => $file,
|
||||||
|
),
|
||||||
if ( isset($_GET['liveupdate']) ) {
|
admin_url( 'plugin-editor.php' )
|
||||||
check_admin_referer('edit-plugin-test_' . $file);
|
) );
|
||||||
|
|
||||||
$error = validate_plugin( $plugin );
|
|
||||||
|
|
||||||
if ( is_wp_error( $error ) ) {
|
|
||||||
wp_die( $error );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ( ! empty( $_GET['networkwide'] ) && ! is_plugin_active_for_network( $file ) ) || ! is_plugin_active( $file ) ) {
|
|
||||||
activate_plugin( $plugin, "plugin-editor.php?file=" . urlencode( $file ) . "&phperror=1", ! empty( $_GET['networkwide'] ) );
|
|
||||||
} // we'll override this later if the plugin can be included without fatal error
|
|
||||||
|
|
||||||
wp_redirect( self_admin_url( 'plugin-editor.php?file=' . urlencode( $file ) . '&plugin=' . urlencode( $plugin ) . "&a=te&scrollto=$scrollto" ) );
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// List of allowable extensions
|
// List of allowable extensions
|
||||||
$editable_extensions = array(
|
$editable_extensions = wp_get_plugin_file_editable_extensions( $plugin );
|
||||||
'bash',
|
|
||||||
'conf',
|
|
||||||
'css',
|
|
||||||
'diff',
|
|
||||||
'htm',
|
|
||||||
'html',
|
|
||||||
'http',
|
|
||||||
'inc',
|
|
||||||
'include',
|
|
||||||
'js',
|
|
||||||
'json',
|
|
||||||
'jsx',
|
|
||||||
'less',
|
|
||||||
'md',
|
|
||||||
'patch',
|
|
||||||
'php',
|
|
||||||
'php3',
|
|
||||||
'php4',
|
|
||||||
'php5',
|
|
||||||
'php7',
|
|
||||||
'phps',
|
|
||||||
'phtml',
|
|
||||||
'sass',
|
|
||||||
'scss',
|
|
||||||
'sh',
|
|
||||||
'sql',
|
|
||||||
'svg',
|
|
||||||
'text',
|
|
||||||
'txt',
|
|
||||||
'xml',
|
|
||||||
'yaml',
|
|
||||||
'yml',
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filters file type extensions editable in the plugin editor.
|
|
||||||
*
|
|
||||||
* @since 2.8.0
|
|
||||||
*
|
|
||||||
* @param array $editable_extensions An array of editable plugin file extensions.
|
|
||||||
*/
|
|
||||||
$editable_extensions = (array) apply_filters( 'editable_extensions', $editable_extensions );
|
|
||||||
|
|
||||||
if ( ! is_file($real_file) ) {
|
if ( ! is_file($real_file) ) {
|
||||||
wp_die(sprintf('<p>%s</p>', __('No such file exists! Double check the name and try again.')));
|
wp_die(sprintf('<p>%s</p>', __('No such file exists! Double check the name and try again.')));
|
||||||
|
@ -212,17 +137,21 @@ if ( isset( $_REQUEST['action'] ) && 'update' === $_REQUEST['action'] ) {
|
||||||
'<p>' . __('<a href="https://wordpress.org/support/">Support Forums</a>') . '</p>'
|
'<p>' . __('<a href="https://wordpress.org/support/">Support Forums</a>') . '</p>'
|
||||||
);
|
);
|
||||||
|
|
||||||
$settings = wp_enqueue_code_editor( array( 'file' => $real_file ) );
|
$settings = array(
|
||||||
if ( ! empty( $settings ) ) {
|
'codeEditor' => wp_enqueue_code_editor( array( 'file' => $real_file ) ),
|
||||||
wp_enqueue_script( 'wp-theme-plugin-editor' );
|
);
|
||||||
wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'jQuery( function() { wp.themePluginEditor.init( %s ); } )', wp_json_encode( $settings ) ) );
|
wp_enqueue_script( 'wp-theme-plugin-editor' );
|
||||||
}
|
wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'jQuery( function( $ ) { wp.themePluginEditor.init( $( "#template" ), %s ); } )', wp_json_encode( $settings ) ) );
|
||||||
|
|
||||||
require_once(ABSPATH . 'wp-admin/admin-header.php');
|
require_once(ABSPATH . 'wp-admin/admin-header.php');
|
||||||
|
|
||||||
update_recently_edited(WP_PLUGIN_DIR . '/' . $file);
|
update_recently_edited(WP_PLUGIN_DIR . '/' . $file);
|
||||||
|
|
||||||
$content = file_get_contents( $real_file );
|
if ( ! empty( $posted_content ) ) {
|
||||||
|
$content = $posted_content;
|
||||||
|
} else {
|
||||||
|
$content = file_get_contents( $real_file );
|
||||||
|
}
|
||||||
|
|
||||||
if ( '.php' == substr( $real_file, strrpos( $real_file, '.' ) ) ) {
|
if ( '.php' == substr( $real_file, strrpos( $real_file, '.' ) ) ) {
|
||||||
$functions = wp_doc_link_parse( $content );
|
$functions = wp_doc_link_parse( $content );
|
||||||
|
@ -239,25 +168,20 @@ if ( isset( $_REQUEST['action'] ) && 'update' === $_REQUEST['action'] ) {
|
||||||
|
|
||||||
$content = esc_textarea( $content );
|
$content = esc_textarea( $content );
|
||||||
?>
|
?>
|
||||||
<?php if (isset($_GET['a'])) : ?>
|
|
||||||
<div id="message" class="updated notice is-dismissible"><p><?php _e('File edited successfully.') ?></p></div>
|
|
||||||
<?php elseif (isset($_GET['phperror'])) : ?>
|
|
||||||
<div id="message" class="notice notice-error"><p><?php _e( 'This plugin has been deactivated because your changes resulted in a <strong>fatal error</strong>.' ); ?></p>
|
|
||||||
<?php
|
|
||||||
if ( wp_verify_nonce( $_GET['_error_nonce'], 'plugin-activation-error_' . $plugin ) ) {
|
|
||||||
$iframe_url = add_query_arg( array(
|
|
||||||
'action' => 'error_scrape',
|
|
||||||
'plugin' => urlencode( $plugin ),
|
|
||||||
'_wpnonce' => urlencode( $_GET['_error_nonce'] ),
|
|
||||||
), admin_url( 'plugins.php' ) );
|
|
||||||
?>
|
|
||||||
<iframe style="border:0" width="100%" height="70px" src="<?php echo esc_url( $iframe_url ); ?>"></iframe>
|
|
||||||
<?php } ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1><?php echo esc_html( $title ); ?></h1>
|
<h1><?php echo esc_html( $title ); ?></h1>
|
||||||
|
|
||||||
|
<?php if ( isset( $_GET['a'] ) ) : ?>
|
||||||
|
<div id="message" class="updated notice is-dismissible">
|
||||||
|
<p><?php _e( 'File edited successfully.' ); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php elseif ( is_wp_error( $edit_error ) ) : ?>
|
||||||
|
<div id="message" class="notice notice-error">
|
||||||
|
<p><?php _e( 'There was an error while trying to update the file. You may need to fix something and try updating again.' ); ?></p>
|
||||||
|
<pre><?php echo esc_html( $edit_error->get_error_message() ? $edit_error->get_error_message() : $edit_error->get_error_code() ); ?></pre>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="fileedit-sub">
|
<div class="fileedit-sub">
|
||||||
<div class="alignleft">
|
<div class="alignleft">
|
||||||
<h2>
|
<h2>
|
||||||
|
@ -283,7 +207,7 @@ if ( isset( $_REQUEST['action'] ) && 'update' === $_REQUEST['action'] ) {
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="alignright">
|
<div class="alignright">
|
||||||
<form action="plugin-editor.php" method="post">
|
<form action="plugin-editor.php" method="get">
|
||||||
<strong><label for="plugin"><?php _e('Select plugin to edit:'); ?> </label></strong>
|
<strong><label for="plugin"><?php _e('Select plugin to edit:'); ?> </label></strong>
|
||||||
<select name="plugin" id="plugin">
|
<select name="plugin" id="plugin">
|
||||||
<?php
|
<?php
|
||||||
|
@ -308,66 +232,53 @@ if ( isset( $_REQUEST['action'] ) && 'update' === $_REQUEST['action'] ) {
|
||||||
<div id="templateside">
|
<div id="templateside">
|
||||||
<h2><?php _e( 'Plugin Files' ); ?></h2>
|
<h2><?php _e( 'Plugin Files' ); ?></h2>
|
||||||
|
|
||||||
<ul>
|
<?php
|
||||||
<?php
|
$plugin_editable_files = array();
|
||||||
foreach ( $plugin_files as $plugin_file ) :
|
foreach ( $plugin_files as $plugin_file ) {
|
||||||
// Get the extension of the file
|
if ( preg_match('/\.([^.]+)$/', $plugin_file, $matches ) && in_array( $matches[1], $editable_extensions ) ) {
|
||||||
if ( preg_match('/\.([^.]+)$/', $plugin_file, $matches) ) {
|
$plugin_editable_files[] = $plugin_file;
|
||||||
$ext = strtolower($matches[1]);
|
}
|
||||||
// If extension is not in the acceptable list, skip it
|
|
||||||
if ( !in_array( $ext, $editable_extensions ) )
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
// No extension found
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<li class="<?php echo esc_attr( $file === $plugin_file ? 'notice notice-info' : '' ); ?>"><a href="plugin-editor.php?file=<?php echo urlencode( $plugin_file ); ?>&plugin=<?php echo urlencode( $plugin ); ?>"><?php echo esc_html( $plugin_file ); ?></a></li>
|
<ul>
|
||||||
<?php endforeach; ?>
|
<?php foreach ( $plugin_editable_files as $plugin_file ) : ?>
|
||||||
|
<li class="<?php echo esc_attr( $file === $plugin_file ? 'notice notice-info' : '' ); ?>">
|
||||||
|
<a href="plugin-editor.php?file=<?php echo urlencode( $plugin_file ); ?>&plugin=<?php echo urlencode( $plugin ); ?>"><?php echo esc_html( preg_replace( '#^.+?/#', '', $plugin_file ) ); ?></a>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<form name="template" id="template" action="plugin-editor.php" method="post">
|
<form name="template" id="template" action="plugin-editor.php" method="post">
|
||||||
<?php wp_nonce_field('edit-plugin_' . $file) ?>
|
<?php wp_nonce_field( 'edit-plugin_' . $file, 'nonce' ); ?>
|
||||||
<div>
|
<div>
|
||||||
<label for="newcontent" id="theme-plugin-editor-label"><?php _e( 'Selected file content:' ); ?></label>
|
<label for="newcontent" id="theme-plugin-editor-label"><?php _e( 'Selected file content:' ); ?></label>
|
||||||
<textarea cols="70" rows="25" name="newcontent" id="newcontent" aria-describedby="editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4"><?php echo $content; ?></textarea>
|
<textarea cols="70" rows="25" name="newcontent" id="newcontent" aria-describedby="editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4"><?php echo $content; ?></textarea>
|
||||||
<input type="hidden" name="action" value="update" />
|
<input type="hidden" name="action" value="update" />
|
||||||
<input type="hidden" name="file" value="<?php echo esc_attr( $file ); ?>" />
|
<input type="hidden" name="file" value="<?php echo esc_attr( $file ); ?>" />
|
||||||
<input type="hidden" name="plugin" value="<?php echo esc_attr( $plugin ); ?>" />
|
<input type="hidden" name="plugin" value="<?php echo esc_attr( $plugin ); ?>" />
|
||||||
<input type="hidden" name="scrollto" id="scrollto" value="<?php echo esc_attr( $scrollto ); ?>" />
|
|
||||||
</div>
|
</div>
|
||||||
<?php if ( !empty( $docs_select ) ) : ?>
|
<?php if ( !empty( $docs_select ) ) : ?>
|
||||||
<div id="documentation" class="hide-if-no-js"><label for="docs-list"><?php _e('Documentation:') ?></label> <?php echo $docs_select ?> <input type="button" class="button" value="<?php esc_attr_e( 'Look Up' ) ?> " onclick="if ( '' != jQuery('#docs-list').val() ) { window.open( 'https://api.wordpress.org/core/handbook/1.0/?function=' + escape( jQuery( '#docs-list' ).val() ) + '&locale=<?php echo urlencode( get_user_locale() ) ?>&version=<?php echo urlencode( get_bloginfo( 'version' ) ) ?>&redirect=true'); }" /></div>
|
<div id="documentation" class="hide-if-no-js"><label for="docs-list"><?php _e('Documentation:') ?></label> <?php echo $docs_select ?> <input type="button" class="button" value="<?php esc_attr_e( 'Look Up' ) ?> " onclick="if ( '' != jQuery('#docs-list').val() ) { window.open( 'https://api.wordpress.org/core/handbook/1.0/?function=' + escape( jQuery( '#docs-list' ).val() ) + '&locale=<?php echo urlencode( get_user_locale() ) ?>&version=<?php echo urlencode( get_bloginfo( 'version' ) ) ?>&redirect=true'); }" /></div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if ( is_writeable($real_file) ) : ?>
|
<?php if ( is_writeable($real_file) ) : ?>
|
||||||
<?php if ( in_array( $plugin, (array) get_option( 'active_plugins', array() ) ) ) { ?>
|
<div class="editor-notices">
|
||||||
<div class="notice notice-warning inline active-plugin-edit-warning">
|
<?php if ( in_array( $plugin, (array) get_option( 'active_plugins', array() ) ) ) { ?>
|
||||||
<p><?php _e('<strong>Warning:</strong> Making changes to active plugins is not recommended. If your changes cause a fatal error, the plugin will be automatically deactivated.'); ?></p>
|
<div class="notice notice-warning inline active-plugin-edit-warning">
|
||||||
|
<p><?php _e('<strong>Warning:</strong> Making changes to active plugins is not recommended.'); ?></p>
|
||||||
</div>
|
</div>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
|
</div>
|
||||||
<p class="submit">
|
<p class="submit">
|
||||||
<?php
|
<?php submit_button( __( 'Update File' ), 'primary', 'submit', false ); ?>
|
||||||
if ( isset($_GET['phperror']) ) {
|
<span class="spinner"></span>
|
||||||
echo "<input type='hidden' name='phperror' value='1' />";
|
|
||||||
submit_button( __( 'Update File and Attempt to Reactivate' ), 'primary', 'submit', false );
|
|
||||||
} else {
|
|
||||||
submit_button( __( 'Update File' ), 'primary', 'submit', false );
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
</p>
|
</p>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<p><em><?php _e('You need to make this file writable before you can save your changes. See <a href="https://codex.wordpress.org/Changing_File_Permissions">the Codex</a> for more information.'); ?></em></p>
|
<p><em><?php _e('You need to make this file writable before you can save your changes. See <a href="https://codex.wordpress.org/Changing_File_Permissions">the Codex</a> for more information.'); ?></em></p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<?php wp_print_file_editor_templates(); ?>
|
||||||
</form>
|
</form>
|
||||||
<br class="clear" />
|
<br class="clear" />
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript">
|
|
||||||
jQuery(document).ready(function($){
|
|
||||||
$('#template').submit(function(){ $('#scrollto').val( $('#newcontent').scrollTop() ); });
|
|
||||||
$('#newcontent').scrollTop( $('#scrollto').val() );
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<?php
|
<?php
|
||||||
}
|
|
||||||
|
|
||||||
include(ABSPATH . "wp-admin/admin-footer.php");
|
include(ABSPATH . "wp-admin/admin-footer.php");
|
||||||
|
|
|
@ -69,53 +69,8 @@ if ( $theme->errors() && 'theme_no_stylesheet' == $theme->errors()->get_error_co
|
||||||
|
|
||||||
$allowed_files = $style_files = array();
|
$allowed_files = $style_files = array();
|
||||||
$has_templates = false;
|
$has_templates = false;
|
||||||
$default_types = array(
|
|
||||||
'bash',
|
|
||||||
'conf',
|
|
||||||
'css',
|
|
||||||
'diff',
|
|
||||||
'htm',
|
|
||||||
'html',
|
|
||||||
'http',
|
|
||||||
'inc',
|
|
||||||
'include',
|
|
||||||
'js',
|
|
||||||
'json',
|
|
||||||
'jsx',
|
|
||||||
'less',
|
|
||||||
'md',
|
|
||||||
'patch',
|
|
||||||
'php',
|
|
||||||
'php3',
|
|
||||||
'php4',
|
|
||||||
'php5',
|
|
||||||
'php7',
|
|
||||||
'phps',
|
|
||||||
'phtml',
|
|
||||||
'sass',
|
|
||||||
'scss',
|
|
||||||
'sh',
|
|
||||||
'sql',
|
|
||||||
'svg',
|
|
||||||
'text',
|
|
||||||
'txt',
|
|
||||||
'xml',
|
|
||||||
'yaml',
|
|
||||||
'yml',
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
$file_types = wp_get_theme_file_editable_extensions( $theme );
|
||||||
* Filters the list of file types allowed for editing in the Theme editor.
|
|
||||||
*
|
|
||||||
* @since 4.4.0
|
|
||||||
*
|
|
||||||
* @param array $default_types List of file types. Default types include 'php' and 'css'.
|
|
||||||
* @param WP_Theme $theme The current Theme object.
|
|
||||||
*/
|
|
||||||
$file_types = apply_filters( 'wp_theme_editor_filetypes', $default_types, $theme );
|
|
||||||
|
|
||||||
// Ensure that default types are still there.
|
|
||||||
$file_types = array_unique( array_merge( $file_types, $default_types ) );
|
|
||||||
|
|
||||||
foreach ( $file_types as $type ) {
|
foreach ( $file_types as $type ) {
|
||||||
switch ( $type ) {
|
switch ( $type ) {
|
||||||
|
@ -143,33 +98,35 @@ if ( empty( $file ) ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
validate_file_to_edit( $file, $allowed_files );
|
validate_file_to_edit( $file, $allowed_files );
|
||||||
$scrollto = isset( $_REQUEST['scrollto'] ) ? (int) $_REQUEST['scrollto'] : 0;
|
|
||||||
|
|
||||||
switch( $action ) {
|
// Handle fallback editing of file when JavaScript is not available.
|
||||||
case 'update':
|
$edit_error = null;
|
||||||
check_admin_referer( 'edit-theme_' . $file . $stylesheet );
|
$posted_content = null;
|
||||||
$newcontent = wp_unslash( $_POST['newcontent'] );
|
if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) {
|
||||||
$location = 'theme-editor.php?file=' . urlencode( $relative_file ) . '&theme=' . urlencode( $stylesheet ) . '&scrollto=' . $scrollto;
|
$r = wp_edit_theme_plugin_file( wp_unslash( $_POST ) );
|
||||||
if ( is_writeable( $file ) ) {
|
if ( is_wp_error( $r ) ) {
|
||||||
// is_writable() not always reliable, check return value. see comments @ https://secure.php.net/is_writable
|
$edit_error = $r;
|
||||||
$f = fopen( $file, 'w+' );
|
if ( check_ajax_referer( 'edit-theme_' . $file . $stylesheet, 'nonce', false ) && isset( $_POST['newcontent'] ) ) {
|
||||||
if ( $f !== false ) {
|
$posted_content = wp_unslash( $_POST['newcontent'] );
|
||||||
fwrite( $f, $newcontent );
|
|
||||||
fclose( $f );
|
|
||||||
$location .= '&updated=true';
|
|
||||||
$theme->cache_delete();
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
wp_redirect( add_query_arg(
|
||||||
|
array(
|
||||||
|
'a' => 1, // This means "success" for some reason.
|
||||||
|
'theme' => $stylesheet,
|
||||||
|
'file' => $relative_file,
|
||||||
|
),
|
||||||
|
admin_url( 'theme-editor.php' )
|
||||||
|
) );
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
wp_redirect( $location );
|
}
|
||||||
exit;
|
|
||||||
|
|
||||||
default:
|
$settings = array(
|
||||||
|
'codeEditor' => wp_enqueue_code_editor( compact( 'file' ) ),
|
||||||
$settings = wp_enqueue_code_editor( compact( 'file' ) );
|
);
|
||||||
if ( ! empty( $settings ) ) {
|
wp_enqueue_script( 'wp-theme-plugin-editor' );
|
||||||
wp_enqueue_script( 'wp-theme-plugin-editor' );
|
wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'jQuery( function( $ ) { wp.themePluginEditor.init( $( "#template" ), %s ); } )', wp_json_encode( $settings ) ) );
|
||||||
wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'jQuery( function() { wp.themePluginEditor.init( %s ); } )', wp_json_encode( $settings ) ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once( ABSPATH . 'wp-admin/admin-header.php' );
|
require_once( ABSPATH . 'wp-admin/admin-header.php' );
|
||||||
|
|
||||||
|
@ -179,7 +136,9 @@ default:
|
||||||
$error = true;
|
$error = true;
|
||||||
|
|
||||||
$content = '';
|
$content = '';
|
||||||
if ( ! $error && filesize( $file ) > 0 ) {
|
if ( ! empty( $posted_content ) ) {
|
||||||
|
$content = $posted_content;
|
||||||
|
} elseif ( ! $error && filesize( $file ) > 0 ) {
|
||||||
$f = fopen($file, 'r');
|
$f = fopen($file, 'r');
|
||||||
$content = fread($f, filesize($file));
|
$content = fread($f, filesize($file));
|
||||||
|
|
||||||
|
@ -197,10 +156,6 @@ default:
|
||||||
$content = esc_textarea( $content );
|
$content = esc_textarea( $content );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isset( $_GET['updated'] ) ) : ?>
|
|
||||||
<div id="message" class="updated notice is-dismissible"><p><?php _e( 'File edited successfully.' ) ?></p></div>
|
|
||||||
<?php endif;
|
|
||||||
|
|
||||||
$file_description = get_file_description( $relative_file );
|
$file_description = get_file_description( $relative_file );
|
||||||
$file_show = array_search( $file, array_filter( $allowed_files ) );
|
$file_show = array_search( $file, array_filter( $allowed_files ) );
|
||||||
$description = esc_html( $file_description );
|
$description = esc_html( $file_description );
|
||||||
|
@ -211,12 +166,23 @@ if ( $file_description != $file_show ) {
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1><?php echo esc_html( $title ); ?></h1>
|
<h1><?php echo esc_html( $title ); ?></h1>
|
||||||
|
|
||||||
|
<?php if ( isset( $_GET['a'] ) ) : ?>
|
||||||
|
<div id="message" class="updated notice is-dismissible">
|
||||||
|
<p><?php _e( 'File edited successfully.' ); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php elseif ( is_wp_error( $edit_error ) ) : ?>
|
||||||
|
<div id="message" class="notice notice-error">
|
||||||
|
<p><?php _e( 'There was an error while trying to update the file. You may need to fix something and try updating again.' ); ?></p>
|
||||||
|
<pre><?php echo esc_html( $edit_error->get_error_message() ? $edit_error->get_error_message() : $edit_error->get_error_code() ); ?></pre>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="fileedit-sub">
|
<div class="fileedit-sub">
|
||||||
<div class="alignleft">
|
<div class="alignleft">
|
||||||
<h2><?php echo $theme->display( 'Name' ); if ( $description ) echo ': ' . $description; ?></h2>
|
<h2><?php echo $theme->display( 'Name' ); if ( $description ) echo ': ' . $description; ?></h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="alignright">
|
<div class="alignright">
|
||||||
<form action="theme-editor.php" method="post">
|
<form action="theme-editor.php" method="get">
|
||||||
<strong><label for="theme"><?php _e('Select theme to edit:'); ?> </label></strong>
|
<strong><label for="theme"><?php _e('Select theme to edit:'); ?> </label></strong>
|
||||||
<select name="theme" id="theme">
|
<select name="theme" id="theme">
|
||||||
<?php
|
<?php
|
||||||
|
@ -299,14 +265,13 @@ if ( $allowed_files ) :
|
||||||
echo '<div class="error"><p>' . __('Oops, no such file exists! Double check the name and try again, merci.') . '</p></div>';
|
echo '<div class="error"><p>' . __('Oops, no such file exists! Double check the name and try again, merci.') . '</p></div>';
|
||||||
else : ?>
|
else : ?>
|
||||||
<form name="template" id="template" action="theme-editor.php" method="post">
|
<form name="template" id="template" action="theme-editor.php" method="post">
|
||||||
<?php wp_nonce_field( 'edit-theme_' . $file . $stylesheet ); ?>
|
<?php wp_nonce_field( 'edit-theme_' . $file . $stylesheet, 'nonce' ); ?>
|
||||||
<div>
|
<div>
|
||||||
<label for="newcontent" id="theme-plugin-editor-label"><?php _e( 'Selected file content:' ); ?></label>
|
<label for="newcontent" id="theme-plugin-editor-label"><?php _e( 'Selected file content:' ); ?></label>
|
||||||
<textarea cols="70" rows="30" name="newcontent" id="newcontent" aria-describedby="editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4"><?php echo $content; ?></textarea>
|
<textarea cols="70" rows="30" name="newcontent" id="newcontent" aria-describedby="editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4"><?php echo $content; ?></textarea>
|
||||||
<input type="hidden" name="action" value="update" />
|
<input type="hidden" name="action" value="update" />
|
||||||
<input type="hidden" name="file" value="<?php echo esc_attr( $relative_file ); ?>" />
|
<input type="hidden" name="file" value="<?php echo esc_attr( $relative_file ); ?>" />
|
||||||
<input type="hidden" name="theme" value="<?php echo esc_attr( $theme->get_stylesheet() ); ?>" />
|
<input type="hidden" name="theme" value="<?php echo esc_attr( $theme->get_stylesheet() ); ?>" />
|
||||||
<input type="hidden" name="scrollto" id="scrollto" value="<?php echo esc_attr( $scrollto ); ?>" />
|
|
||||||
</div>
|
</div>
|
||||||
<?php if ( ! empty( $functions ) ) : ?>
|
<?php if ( ! empty( $functions ) ) : ?>
|
||||||
<div id="documentation" class="hide-if-no-js">
|
<div id="documentation" class="hide-if-no-js">
|
||||||
|
@ -316,32 +281,33 @@ else : ?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<?php if ( is_child_theme() && $theme->get_stylesheet() == get_template() ) : ?>
|
<div class="editor-notices">
|
||||||
<p><?php if ( is_writeable( $file ) ) { ?><strong><?php _e( 'Caution:' ); ?></strong><?php } ?>
|
<?php if ( is_child_theme() && $theme->get_stylesheet() == get_template() ) : ?>
|
||||||
<?php _e( 'This is a file in your current parent theme.' ); ?></p>
|
<div class="notice notice-warning inline">
|
||||||
<?php endif; ?>
|
<p>
|
||||||
<?php
|
<?php if ( is_writeable( $file ) ) { ?><strong><?php _e( 'Caution:' ); ?></strong><?php } ?>
|
||||||
if ( is_writeable( $file ) ) :
|
<?php _e( 'This is a file in your current parent theme.' ); ?>
|
||||||
submit_button( __( 'Update File' ), 'primary', 'submit', true );
|
</p>
|
||||||
else : ?>
|
</div>
|
||||||
<p><em><?php _e('You need to make this file writable before you can save your changes. See <a href="https://codex.wordpress.org/Changing_File_Permissions">the Codex</a> for more information.'); ?></em></p>
|
<?php endif; ?>
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
</div>
|
||||||
|
<?php if ( is_writeable( $file ) ) : ?>
|
||||||
|
<p class="submit">
|
||||||
|
<?php submit_button( __( 'Update File' ), 'primary', 'submit', false ); ?>
|
||||||
|
<span class="spinner"></span>
|
||||||
|
</p>
|
||||||
|
<?php else : ?>
|
||||||
|
<p><em><?php _e('You need to make this file writable before you can save your changes. See <a href="https://codex.wordpress.org/Changing_File_Permissions">the Codex</a> for more information.'); ?></em></p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php wp_print_file_editor_templates(); ?>
|
||||||
</form>
|
</form>
|
||||||
<?php
|
<?php
|
||||||
endif; // $error
|
endif; // $error
|
||||||
?>
|
?>
|
||||||
<br class="clear" />
|
<br class="clear" />
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript">
|
|
||||||
jQuery(document).ready(function($){
|
|
||||||
$('#template').submit(function(){ $('#scrollto').val( $('#newcontent').scrollTop() ); });
|
|
||||||
$('#newcontent').scrollTop( $('#scrollto').val() );
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<?php
|
<?php
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
include(ABSPATH . 'wp-admin/admin-footer.php' );
|
include(ABSPATH . 'wp-admin/admin-footer.php' );
|
||||||
|
|
|
@ -14,9 +14,10 @@ window.wp = window.wp || {};
|
||||||
* @since 4.2.0
|
* @since 4.2.0
|
||||||
* @since 4.3.0 Introduced the 'ariaLive' argument.
|
* @since 4.3.0 Introduced the 'ariaLive' argument.
|
||||||
*
|
*
|
||||||
* @param {String} message The message to be announced by Assistive Technologies.
|
* @param {String} message The message to be announced by Assistive Technologies.
|
||||||
* @param {String} ariaLive Optional. The politeness level for aria-live. Possible values:
|
* @param {String} [ariaLive] The politeness level for aria-live. Possible values:
|
||||||
* polite or assertive. Default polite.
|
* polite or assertive. Default polite.
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function speak( message, ariaLive ) {
|
function speak( message, ariaLive ) {
|
||||||
// Clear previous messages to allow repeated strings being read out.
|
// Clear previous messages to allow repeated strings being read out.
|
||||||
|
|
|
@ -1112,3 +1112,46 @@ function wp_is_file_mod_allowed( $context ) {
|
||||||
*/
|
*/
|
||||||
return apply_filters( 'file_mod_allowed', ! defined( 'DISALLOW_FILE_MODS' ) || ! DISALLOW_FILE_MODS, $context );
|
return apply_filters( 'file_mod_allowed', ! defined( 'DISALLOW_FILE_MODS' ) || ! DISALLOW_FILE_MODS, $context );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start scraping edited file errors.
|
||||||
|
*
|
||||||
|
* @since 4.9.0
|
||||||
|
*/
|
||||||
|
function wp_start_scraping_edited_file_errors() {
|
||||||
|
if ( ! isset( $_REQUEST['wp_scrape_key'] ) || ! isset( $_REQUEST['wp_scrape_nonce'] ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$key = substr( sanitize_key( wp_unslash( $_REQUEST['wp_scrape_key'] ) ), 0, 32 );
|
||||||
|
$nonce = wp_unslash( $_REQUEST['wp_scrape_nonce'] );
|
||||||
|
|
||||||
|
if ( get_transient( 'scrape_key_' . $key ) !== $nonce ) {
|
||||||
|
echo "###### begin_scraped_error:$key ######";
|
||||||
|
echo wp_json_encode( array(
|
||||||
|
'code' => 'scrape_nonce_failure',
|
||||||
|
'message' => __( 'Scrape nonce check failed. Please try again.' ),
|
||||||
|
) );
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
register_shutdown_function( 'wp_finalize_scraping_edited_file_errors', $key );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finalize scraping for edited file errors.
|
||||||
|
*
|
||||||
|
* @since 4.9.0
|
||||||
|
*
|
||||||
|
* @param string $scrape_key Scrape key.
|
||||||
|
*/
|
||||||
|
function wp_finalize_scraping_edited_file_errors( $scrape_key ) {
|
||||||
|
$error = error_get_last();
|
||||||
|
if ( empty( $error ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
|
|
@ -471,11 +471,14 @@ function wp_default_scripts( &$scripts ) {
|
||||||
$scripts->add( 'htmlhint', '/wp-includes/js/codemirror/htmlhint.js', array(), '0.9.14-xwp' );
|
$scripts->add( 'htmlhint', '/wp-includes/js/codemirror/htmlhint.js', array(), '0.9.14-xwp' );
|
||||||
$scripts->add( 'htmlhint-kses', '/wp-includes/js/codemirror/htmlhint-kses.js', array( 'htmlhint' ) );
|
$scripts->add( 'htmlhint-kses', '/wp-includes/js/codemirror/htmlhint-kses.js', array( 'htmlhint' ) );
|
||||||
$scripts->add( 'code-editor', "/wp-admin/js/code-editor$suffix.js", array( 'jquery', 'wp-codemirror' ) );
|
$scripts->add( 'code-editor', "/wp-admin/js/code-editor$suffix.js", array( 'jquery', 'wp-codemirror' ) );
|
||||||
$scripts->add( 'wp-theme-plugin-editor', "/wp-admin/js/theme-plugin-editor$suffix.js", array( 'code-editor', 'jquery', 'jquery-ui-core', 'wp-a11y', 'underscore' ) );
|
$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( wp_array_slice_assoc(
|
did_action( 'init' ) && $scripts->add_inline_script( 'wp-theme-plugin-editor', sprintf( 'wp.themePluginEditor.l10n = %s;', wp_json_encode( array(
|
||||||
/* translators: %d: error count */
|
'saveAlert' => __( 'The changes you made will be lost if you navigate away from this page.' ),
|
||||||
_n_noop( 'There is %d error which must be fixed before you can save.', 'There are %d errors which must be fixed before you can save.' ),
|
'lintError' => wp_array_slice_assoc(
|
||||||
array( 'singular', 'plural' )
|
/* 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.' ),
|
||||||
|
array( 'singular', 'plural' )
|
||||||
|
),
|
||||||
) ) ) );
|
) ) ) );
|
||||||
|
|
||||||
$scripts->add( 'wp-playlist', "/wp-includes/js/mediaelement/wp-playlist$suffix.js", array( 'wp-util', 'backbone', 'mediaelement' ), false, 1 );
|
$scripts->add( 'wp-playlist', "/wp-includes/js/mediaelement/wp-playlist$suffix.js", array( 'wp-util', 'backbone', 'mediaelement' ), false, 1 );
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
*
|
*
|
||||||
* @global string $wp_version
|
* @global string $wp_version
|
||||||
*/
|
*/
|
||||||
$wp_version = '4.9-alpha-41720';
|
$wp_version = '4.9-alpha-41721';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
|
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
|
||||||
|
|
|
@ -294,6 +294,8 @@ require( ABSPATH . WPINC . '/vars.php' );
|
||||||
create_initial_taxonomies();
|
create_initial_taxonomies();
|
||||||
create_initial_post_types();
|
create_initial_post_types();
|
||||||
|
|
||||||
|
wp_start_scraping_edited_file_errors();
|
||||||
|
|
||||||
// Register the default theme directory root
|
// Register the default theme directory root
|
||||||
register_theme_directory( get_theme_root() );
|
register_theme_directory( get_theme_root() );
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue