Make WordPress Core


Ignore:
Timestamp:
10/04/2017 12:19:16 AM (7 years ago)
Author:
westonruter
Message:

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.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/theme-editor.php

    r41640 r41721  
    7070$allowed_files = $style_files = array();
    7171$has_templates = false;
    72 $default_types = array(
    73     'bash',
    74     'conf',
    75     'css',
    76     'diff',
    77     'htm',
    78     'html',
    79     'http',
    80     'inc',
    81     'include',
    82     'js',
    83     'json',
    84     'jsx',
    85     'less',
    86     'md',
    87     'patch',
    88     'php',
    89     'php3',
    90     'php4',
    91     'php5',
    92     'php7',
    93     'phps',
    94     'phtml',
    95     'sass',
    96     'scss',
    97     'sh',
    98     'sql',
    99     'svg',
    100     'text',
    101     'txt',
    102     'xml',
    103     'yaml',
    104     'yml',
    105 );
    106 
    107 /**
    108  * Filters the list of file types allowed for editing in the Theme editor.
    109  *
    110  * @since 4.4.0
    111  *
    112  * @param array    $default_types List of file types. Default types include 'php' and 'css'.
    113  * @param WP_Theme $theme         The current Theme object.
    114  */
    115 $file_types = apply_filters( 'wp_theme_editor_filetypes', $default_types, $theme );
    116 
    117 // Ensure that default types are still there.
    118 $file_types = array_unique( array_merge( $file_types, $default_types ) );
     72
     73$file_types = wp_get_theme_file_editable_extensions( $theme );
    11974
    12075foreach ( $file_types as $type ) {
     
    14499
    145100validate_file_to_edit( $file, $allowed_files );
    146 $scrollto = isset( $_REQUEST['scrollto'] ) ? (int) $_REQUEST['scrollto'] : 0;
    147 
    148 switch( $action ) {
    149 case 'update':
    150     check_admin_referer( 'edit-theme_' . $file . $stylesheet );
    151     $newcontent = wp_unslash( $_POST['newcontent'] );
    152     $location = 'theme-editor.php?file=' . urlencode( $relative_file ) . '&theme=' . urlencode( $stylesheet ) . '&scrollto=' . $scrollto;
    153     if ( is_writeable( $file ) ) {
    154         // is_writable() not always reliable, check return value. see comments @ https://secure.php.net/is_writable
    155         $f = fopen( $file, 'w+' );
    156         if ( $f !== false ) {
    157             fwrite( $f, $newcontent );
    158             fclose( $f );
    159             $location .= '&updated=true';
    160             $theme->cache_delete();
    161         }
     101
     102// Handle fallback editing of file when JavaScript is not available.
     103$edit_error = null;
     104$posted_content = null;
     105if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) {
     106    $r = wp_edit_theme_plugin_file( wp_unslash( $_POST ) );
     107    if ( is_wp_error( $r ) ) {
     108        $edit_error = $r;
     109        if ( check_ajax_referer( 'edit-theme_' . $file . $stylesheet, 'nonce', false ) && isset( $_POST['newcontent'] ) ) {
     110            $posted_content = wp_unslash( $_POST['newcontent'] );
     111        }
     112    } else {
     113        wp_redirect( add_query_arg(
     114            array(
     115                'a' => 1, // This means "success" for some reason.
     116                'theme' => $stylesheet,
     117                'file' => $relative_file,
     118            ),
     119            admin_url( 'theme-editor.php' )
     120        ) );
     121        exit;
    162122    }
    163     wp_redirect( $location );
    164     exit;
    165 
    166 default:
    167 
    168     $settings = wp_enqueue_code_editor( compact( 'file' ) );
    169     if ( ! empty( $settings ) ) {
    170         wp_enqueue_script( 'wp-theme-plugin-editor' );
    171         wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'jQuery( function() { wp.themePluginEditor.init( %s ); } )', wp_json_encode( $settings ) ) );
    172     }
     123}
     124
     125    $settings = array(
     126        'codeEditor' => wp_enqueue_code_editor( compact( 'file' ) ),
     127    );
     128    wp_enqueue_script( 'wp-theme-plugin-editor' );
     129    wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'jQuery( function( $ ) { wp.themePluginEditor.init( $( "#template" ), %s ); } )', wp_json_encode( $settings ) ) );
    173130
    174131    require_once( ABSPATH . 'wp-admin/admin-header.php' );
     
    180137
    181138    $content = '';
    182     if ( ! $error && filesize( $file ) > 0 ) {
     139    if ( ! empty( $posted_content ) ) {
     140        $content = $posted_content;
     141    } elseif ( ! $error && filesize( $file ) > 0 ) {
    183142        $f = fopen($file, 'r');
    184143        $content = fread($f, filesize($file));
     
    198157    }
    199158
    200     if ( isset( $_GET['updated'] ) ) : ?>
    201  <div id="message" class="updated notice is-dismissible"><p><?php _e( 'File edited successfully.' ) ?></p></div>
    202 <?php endif;
    203 
    204159$file_description = get_file_description( $relative_file );
    205160$file_show = array_search( $file, array_filter( $allowed_files ) );
     
    212167<h1><?php echo esc_html( $title ); ?></h1>
    213168
     169<?php if ( isset( $_GET['a'] ) ) : ?>
     170    <div id="message" class="updated notice is-dismissible">
     171        <p><?php _e( 'File edited successfully.' ); ?></p>
     172    </div>
     173<?php elseif ( is_wp_error( $edit_error ) ) : ?>
     174    <div id="message" class="notice notice-error">
     175        <p><?php _e( 'There was an error while trying to update the file. You may need to fix something and try updating again.' ); ?></p>
     176        <pre><?php echo esc_html( $edit_error->get_error_message() ? $edit_error->get_error_message() : $edit_error->get_error_code() ); ?></pre>
     177    </div>
     178<?php endif; ?>
     179
    214180<div class="fileedit-sub">
    215181<div class="alignleft">
     
    217183</div>
    218184<div class="alignright">
    219     <form action="theme-editor.php" method="post">
     185    <form action="theme-editor.php" method="get">
    220186        <strong><label for="theme"><?php _e('Select theme to edit:'); ?> </label></strong>
    221187        <select name="theme" id="theme">
     
    300266else : ?>
    301267    <form name="template" id="template" action="theme-editor.php" method="post">
    302     <?php wp_nonce_field( 'edit-theme_' . $file . $stylesheet ); ?>
     268        <?php wp_nonce_field( 'edit-theme_' . $file . $stylesheet, 'nonce' ); ?>
    303269        <div>
    304270            <label for="newcontent" id="theme-plugin-editor-label"><?php _e( 'Selected file content:' ); ?></label>
     
    307273            <input type="hidden" name="file" value="<?php echo esc_attr( $relative_file ); ?>" />
    308274            <input type="hidden" name="theme" value="<?php echo esc_attr( $theme->get_stylesheet() ); ?>" />
    309             <input type="hidden" name="scrollto" id="scrollto" value="<?php echo esc_attr( $scrollto ); ?>" />
    310275        </div>
    311276    <?php if ( ! empty( $functions ) ) : ?>
     
    317282    <?php endif; ?>
    318283
    319         <div>
    320         <?php if ( is_child_theme() && $theme->get_stylesheet() == get_template() ) : ?>
    321             <p><?php if ( is_writeable( $file ) ) { ?><strong><?php _e( 'Caution:' ); ?></strong><?php } ?>
    322             <?php _e( 'This is a file in your current parent theme.' ); ?></p>
    323         <?php endif; ?>
    324 <?php
    325     if ( is_writeable( $file ) ) :
    326         submit_button( __( 'Update File' ), 'primary', 'submit', true );
    327     else : ?>
    328 <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>
    329 <?php endif; ?>
     284    <div>
     285        <div class="editor-notices">
     286            <?php if ( is_child_theme() && $theme->get_stylesheet() == get_template() ) : ?>
     287                <div class="notice notice-warning inline">
     288                    <p>
     289                        <?php if ( is_writeable( $file ) ) { ?><strong><?php _e( 'Caution:' ); ?></strong><?php } ?>
     290                        <?php _e( 'This is a file in your current parent theme.' ); ?>
     291                    </p>
     292                </div>
     293            <?php endif; ?>
    330294        </div>
     295    <?php if ( is_writeable( $file ) ) : ?>
     296        <p class="submit">
     297            <?php submit_button( __( 'Update File' ), 'primary', 'submit', false ); ?>
     298            <span class="spinner"></span>
     299        </p>
     300    <?php else : ?>
     301        <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>
     302    <?php endif; ?>
     303    </div>
     304    <?php wp_print_file_editor_templates(); ?>
    331305    </form>
    332306<?php
     
    335309<br class="clear" />
    336310</div>
    337 <script type="text/javascript">
    338 jQuery(document).ready(function($){
    339     $('#template').submit(function(){ $('#scrollto').val( $('#newcontent').scrollTop() ); });
    340     $('#newcontent').scrollTop( $('#scrollto').val() );
    341 });
    342 </script>
    343 <?php
    344 break;
    345 }
     311<?php
    346312
    347313include(ABSPATH . 'wp-admin/admin-footer.php' );
Note: See TracChangeset for help on using the changeset viewer.