WordPress.org

Make WordPress Core

Ticket #24048: 24048.diff

File 24048.diff, 40.6 KB (added by WraithKenny, 8 months ago)

WIP preview patch (contains 6531.11.diff)

  • src/wp-admin/css/common.css

     
    30413041        overflow: scroll;
    30423042}
    30433043
     3044#templateside ul ul {
     3045        padding-left: 12px;
     3046}
     3047ul[role="tree"] {
     3048  margin: 0;
     3049  padding: 0;
     3050  list-style: none;
     3051}
     3052
     3053ul[role="tree"] li {
     3054  margin: 0;
     3055  padding: 0;
     3056  list-style: none;
     3057}
     3058
     3059ul[role="tree"] a {
     3060  text-decoration: underline;
     3061  border-color: transparent;
     3062}
     3063
     3064[role="treeitem"] ul {
     3065  margin: 0;
     3066  padding: 0;
     3067  margin-left: 0.9em;
     3068}
     3069
     3070[role="treeitem"][aria-expanded="false"] > ul {
     3071  display: none;
     3072}
     3073
     3074[role="treeitem"][aria-expanded="true"] > ul {
     3075  display: block;
     3076}
     3077
     3078[role="treeitem"][aria-expanded="false"] > span:before {
     3079  content: '▶';
     3080  position: relative;
     3081  left: -0.25em;
     3082}
     3083
     3084[role="treeitem"][aria-expanded="true"] > span:before {
     3085  content: '▼';
     3086  position: relative;
     3087  left: -0.25em;
     3088}
     3089
     3090[role="treeitem"],
     3091[role="treeitem"] span {
     3092  margin: 0;
     3093  padding: 0;
     3094  border: 2px transparent solid;
     3095  padding: 0.125em;
     3096  display: block;
     3097}
     3098
     3099/* disable default keyboard focus styling for treeitems
     3100   Keyboard focus is styled with the following CSS */
     3101[role="treeitem"]:focus {
     3102  outline: 0;
     3103}
     3104
     3105[role="treeitem"].focus,
     3106[role="treeitem"] span.focus {
     3107  border-color: black;
     3108  background-color: #EEEEEE;
     3109  width: 16em; 
     3110}
     3111
     3112[role="treeitem"].hover,
     3113[role="treeitem"] span.hover {
     3114  background-color: #DDDDDD;
     3115  width: 16em; 
     3116}
     3117
    30443118#theme-plugin-editor-label {
    30453119        display: inline-block;
    30463120        margin-bottom: 1em;
  • src/wp-admin/includes/file.php

     
    123123 *
    124124 * @param string $folder Optional. Full path to folder. Default empty.
    125125 * @param int    $levels Optional. Levels of folders to follow, Default 100 (PHP Loop limit).
     126 * @param array  $exclusions Optional. List of folders to skip.
    126127 * @return bool|array False on failure, Else array of files
    127128 */
    128 function list_files( $folder = '', $levels = 100 ) {
    129         if ( empty($folder) )
     129function list_files( $folder = '', $levels = 100, $exclusions = array() ) {
     130        if ( empty( $folder ) ) {
    130131                return false;
     132        }
    131133
    132         if ( ! $levels )
     134        $folder = trailingslashit( $folder );
     135
     136        if ( ! $levels ) {
    133137                return false;
     138        }
    134139
    135140        $files = array();
    136         if ( $dir = @opendir( $folder ) ) {
    137                 while (($file = readdir( $dir ) ) !== false ) {
    138                         if ( in_array($file, array('.', '..') ) )
     141
     142        $dir = @opendir( $folder );
     143        if ( $dir ) {
     144                while ( ( $file = readdir( $dir ) ) !== false ) {
     145                        // Skip current and parent folder links.
     146                        if ( in_array( $file, array( '.', '..' ), true ) ) {
    139147                                continue;
    140                         if ( is_dir( $folder . '/' . $file ) ) {
    141                                 $files2 = list_files( $folder . '/' . $file, $levels - 1);
    142                                 if ( $files2 )
     148                        }
     149
     150                        // Skip hidden and excluded files.
     151                        if ( '.' === $file[0] || in_array( $file, $exclusions, true ) ) {
     152                                continue;
     153                        }
     154
     155                        if ( is_dir( $folder . $file ) ) {
     156                                $files2 = list_files( $folder . $file, $levels - 1 );
     157                                if ( $files2 ) {
    143158                                        $files = array_merge($files, $files2 );
    144                                 else
    145                                         $files[] = $folder . '/' . $file . '/';
     159                                } else {
     160                                        $files[] = $folder . $file . '/';
     161                                }
    146162                        } else {
    147                                 $files[] = $folder . '/' . $file;
     163                                $files[] = $folder . $file;
    148164                        }
    149165                }
    150166        }
    151167        @closedir( $dir );
     168
    152169        return $files;
    153170}
    154171
  • src/wp-admin/includes/plugin.php

     
    190190 * @param string $plugin Path to the main plugin file from plugins directory.
    191191 * @return array List of files relative to the plugin root.
    192192 */
    193 function get_plugin_files($plugin) {
     193function get_plugin_files( $plugin ) {
    194194        $plugin_file = WP_PLUGIN_DIR . '/' . $plugin;
    195         $dir = dirname($plugin_file);
    196         $plugin_files = array($plugin);
    197         if ( is_dir($dir) && $dir != WP_PLUGIN_DIR ) {
    198                 $plugins_dir = @ opendir( $dir );
    199                 if ( $plugins_dir ) {
    200                         while (($file = readdir( $plugins_dir ) ) !== false ) {
    201                                 if ( substr($file, 0, 1) == '.' )
    202                                         continue;
    203                                 if ( is_dir( $dir . '/' . $file ) ) {
    204                                         $plugins_subdir = @ opendir( $dir . '/' . $file );
    205                                         if ( $plugins_subdir ) {
    206                                                 while (($subfile = readdir( $plugins_subdir ) ) !== false ) {
    207                                                         if ( substr($subfile, 0, 1) == '.' )
    208                                                                 continue;
    209                                                         $plugin_files[] = plugin_basename("$dir/$file/$subfile");
    210                                                 }
    211                                                 @closedir( $plugins_subdir );
    212                                         }
    213                                 } else {
    214                                         if ( plugin_basename("$dir/$file") != $plugin )
    215                                                 $plugin_files[] = plugin_basename("$dir/$file");
    216                                 }
    217                         }
    218                         @closedir( $plugins_dir );
    219                 }
     195        $dir = dirname( $plugin_file );
     196
     197        $data = get_plugin_data( $plugin_file );
     198        $label = isset( $data['Version'] )
     199                ? sanitize_key( 'files_' . $plugin . '-' . $data['Version'] )
     200                : sanitize_key( 'files_' . $plugin );
     201        $transient_key = substr( $label, 0, 29 ) . md5( $label );
     202
     203        $plugin_files = false; //get_transient( $transient_key );
     204        if ( false !== $plugin_files ) {
     205                return $plugin_files;
    220206        }
    221207
     208        $plugin_files = array( plugin_basename( $plugin_file ) );
     209
     210        if ( is_dir( $dir ) && WP_PLUGIN_DIR !== $dir ) {
     211
     212                /**
     213                 * Filters the array of excluded directories and files while scanning the folder.
     214                 *
     215                 * @since 4.9
     216                 *
     217                 * @param array $exclusions Array of excluded directories and files.
     218                 */
     219                $exclusions = (array) apply_filters( 'get_plugin_files_exclusions', array( 'CVS', 'node_modules', 'vendor', 'bower_components' ) );
     220
     221                $list_files = list_files( $dir, 100, $exclusions );
     222                $list_files = array_map( 'plugin_basename', $list_files );
     223
     224                $plugin_files += $list_files;
     225                $plugin_files = array_unique( $plugin_files );
     226        }
     227
     228        set_transient( $transient_key, $plugin_files, HOUR_IN_SECONDS );
     229
    222230        return $plugin_files;
    223231}
    224232
  • src/wp-admin/plugin-editor.php

     
    1414        exit();
    1515}
    1616
    17 if ( !current_user_can('edit_plugins') )
    18         wp_die( __('Sorry, you are not allowed to edit plugins for this site.') );
     17if ( ! current_user_can( 'edit_plugins' ) ) {
     18        wp_die( __( 'Sorry, you are not allowed to edit plugins for this site.' ) );
     19}
    1920
    20 $title = __("Edit Plugins");
     21$title = __( 'Edit Plugins' );
    2122$parent_file = 'plugins.php';
    2223
    2324$plugins = get_plugins();
     
    6667        }
    6768}
    6869
    69 $plugin_files = get_plugin_files($plugin);
     70$plugin_files = get_plugin_files( $plugin );
    7071
    7172if ( empty( $file ) ) {
    7273        $file = $plugin_files[0];
    7374}
    7475
    75 $file = validate_file_to_edit($file, $plugin_files);
     76$file = validate_file_to_edit( $file, $plugin_files );
    7677$real_file = WP_PLUGIN_DIR . '/' . $file;
    7778
    7879// Handle fallback editing of file when JavaScript is not available.
     
    9899        }
    99100}
    100101
    101         // List of allowable extensions
    102         $editable_extensions = wp_get_plugin_file_editable_extensions( $plugin );
     102// List of allowable extensions
     103$editable_extensions = wp_get_plugin_file_editable_extensions( $plugin );
    103104
    104         if ( ! is_file($real_file) ) {
    105                 wp_die(sprintf('<p>%s</p>', __('No such file exists! Double check the name and try again.')));
    106         } else {
    107                 // Get the extension of the file
    108                 if ( preg_match('/\.([^.]+)$/', $real_file, $matches) ) {
    109                         $ext = strtolower($matches[1]);
    110                         // If extension is not in the acceptable list, skip it
    111                         if ( !in_array( $ext, $editable_extensions) )
    112                                 wp_die(sprintf('<p>%s</p>', __('Files of this type are not editable.')));
     105if ( ! is_file( $real_file ) ) {
     106        wp_die( sprintf( '<p>%s</p>', __( 'No such file exists! Double check the name and try again.' ) ) );
     107} else {
     108        // Get the extension of the file
     109        if ( preg_match( '/\.([^.]+)$/', $real_file, $matches ) ) {
     110                $ext = strtolower( $matches[1] );
     111                // If extension is not in the acceptable list, skip it
     112                if ( ! in_array( $ext, $editable_extensions ) ) {
     113                        wp_die( sprintf( '<p>%s</p>', __( 'Files of this type are not editable.' ) ) );
    113114                }
    114115        }
     116}
    115117
    116118        get_current_screen()->add_help_tab( array(
    117         'id'            => 'overview',
    118         'title'         => __('Overview'),
    119         'content'       =>
    120                 '<p>' . __('You can use the editor to make changes to any of your plugins&#8217; individual PHP files. Be aware that if you make changes, plugins updates will overwrite your customizations.') . '</p>' .
    121                 '<p>' . __('Choose a plugin to edit from the dropdown menu and click the Select button. Click once on any file name to load it in the editor, and make your changes. Don&#8217;t forget to save your changes (Update File) when you&#8217;re finished.') . '</p>' .
    122                 '<p>' . __('The Documentation menu below the editor lists the PHP functions recognized in the plugin file. Clicking Look Up takes you to a web page about that particular function.') . '</p>' .
    123                 '<p id="editor-keyboard-trap-help-1">' . __( 'When using a keyboard to navigate:' ) . '</p>' .
    124                 '<ul>' .
    125                 '<li id="editor-keyboard-trap-help-2">' . __( 'In the editing area, the Tab key enters a tab character.' ) . '</li>' .
    126                 '<li id="editor-keyboard-trap-help-3">' . __( 'To move away from this area, press the Esc key followed by the Tab key.' ) . '</li>' .
    127                 '<li id="editor-keyboard-trap-help-4">' . __( 'Screen reader users: when in forms mode, you may need to press the Esc key twice.' ) . '</li>' .
    128                 '</ul>' .
    129                 '<p>' . __('If you want to make changes but don&#8217;t want them to be overwritten when the plugin is updated, you may be ready to think about writing your own plugin. For information on how to edit plugins, write your own from scratch, or just better understand their anatomy, check out the links below.') . '</p>' .
    130                 ( is_network_admin() ? '<p>' . __('Any edits to files from this screen will be reflected on all sites in the network.') . '</p>' : '' )
     119                'id'        => 'overview',
     120                'title'     => __( 'Overview' ),
     121                'content'   =>
     122                        '<p>' . __( 'You can use the editor to make changes to any of your plugins&#8217; individual PHP files. Be aware that if you make changes, plugins updates will overwrite your customizations.' ) . '</p>' .
     123                        '<p>' . __( 'Choose a plugin to edit from the dropdown menu and click the Select button. Click once on any file name to load it in the editor, and make your changes. Don&#8217;t forget to save your changes (Update File) when you&#8217;re finished.' ) . '</p>' .
     124                        '<p>' . __( 'The Documentation menu below the editor lists the PHP functions recognized in the plugin file. Clicking Look Up takes you to a web page about that particular function.' ) . '</p>' .
     125                        '<p id="editor-keyboard-trap-help-1">' . __( 'When using a keyboard to navigate:' ) . '</p>' .
     126                        '<ul>' .
     127                        '<li id="editor-keyboard-trap-help-2">' . __( 'In the editing area, the Tab key enters a tab character.' ) . '</li>' .
     128                        '<li id="editor-keyboard-trap-help-3">' . __( 'To move away from this area, press the Esc key followed by the Tab key.' ) . '</li>' .
     129                        '<li id="editor-keyboard-trap-help-4">' . __( 'Screen reader users: when in forms mode, you may need to press the Esc key twice.' ) . '</li>' .
     130                        '</ul>' .
     131                        '<p>' . __( 'If you want to make changes but don&#8217;t want them to be overwritten when the plugin is updated, you may be ready to think about writing your own plugin. For information on how to edit plugins, write your own from scratch, or just better understand their anatomy, check out the links below.' ) . '</p>' .
     132                        ( is_network_admin() ? '<p>' . __( 'Any edits to files from this screen will be reflected on all sites in the network.' ) . '</p>' : '' ),
    131133        ) );
    132134
    133135        get_current_screen()->set_help_sidebar(
    134                 '<p><strong>' . __('For more information:') . '</strong></p>' .
    135                 '<p>' . __('<a href="https://codex.wordpress.org/Plugins_Editor_Screen">Documentation on Editing Plugins</a>') . '</p>' .
    136                 '<p>' . __('<a href="https://codex.wordpress.org/Writing_a_Plugin">Documentation on Writing Plugins</a>') . '</p>' .
    137                 '<p>' . __('<a href="https://wordpress.org/support/">Support Forums</a>') . '</p>'
     136                '<p><strong>' . __( 'For more information:' ) . '</strong></p>' .
     137                '<p>' . __( '<a href="https://codex.wordpress.org/Plugins_Editor_Screen">Documentation on Editing Plugins</a>' ) . '</p>' .
     138                '<p>' . __( '<a href="https://codex.wordpress.org/Writing_a_Plugin">Documentation on Writing Plugins</a>' ) . '</p>' .
     139                '<p>' . __( '<a href="https://wordpress.org/support/">Support Forums</a>' ) . '</p>'
    138140        );
    139141
    140142        $settings = array(
     
    141143                'codeEditor' => wp_enqueue_code_editor( array( 'file' => $real_file ) ),
    142144        );
    143145        wp_enqueue_script( 'wp-theme-plugin-editor' );
     146        wp_enqueue_script( 'tree-links' );
     147        wp_enqueue_script( 'treeitem-links' );
    144148        wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'jQuery( function( $ ) { wp.themePluginEditor.init( $( "#template" ), %s ); } )', wp_json_encode( $settings ) ) );
    145149        wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'wp.themePluginEditor.themeOrPlugin = "plugin";' ) );
    146150
    147         require_once(ABSPATH . 'wp-admin/admin-header.php');
     151        require_once( ABSPATH . 'wp-admin/admin-header.php' );
    148152
    149         update_recently_edited(WP_PLUGIN_DIR . '/' . $file);
     153        update_recently_edited( WP_PLUGIN_DIR . '/' . $file );
    150154
    151155        if ( ! empty( $posted_content ) ) {
    152156                $content = $posted_content;
     
    157161        if ( '.php' == substr( $real_file, strrpos( $real_file, '.' ) ) ) {
    158162                $functions = wp_doc_link_parse( $content );
    159163
    160                 if ( !empty($functions) ) {
     164                if ( ! empty( $functions ) ) {
    161165                        $docs_select = '<select name="docs-list" id="docs-list">';
    162166                        $docs_select .= '<option value="">' . __( 'Function Name&hellip;' ) . '</option>';
    163                         foreach ( $functions as $function) {
     167                        foreach ( $functions as $function ) {
    164168                                $docs_select .= '<option value="' . esc_attr( $function ) . '">' . esc_html( $function ) . '()</option>';
    165169                        }
    166170                        $docs_select .= '</select>';
     
    209213</div>
    210214<div class="alignright">
    211215        <form action="plugin-editor.php" method="get">
    212                 <strong><label for="plugin"><?php _e('Select plugin to edit:'); ?> </label></strong>
     216                <strong><label for="plugin"><?php _e( 'Select plugin to edit:' ); ?> </label></strong>
    213217                <select name="plugin" id="plugin">
    214218<?php
    215         foreach ( $plugins as $plugin_key => $a_plugin ) {
    216                 $plugin_name = $a_plugin['Name'];
    217                 if ( $plugin_key == $plugin )
    218                         $selected = " selected='selected'";
    219                 else
    220                         $selected = '';
    221                 $plugin_name = esc_attr($plugin_name);
    222                 $plugin_key = esc_attr($plugin_key);
    223                 echo "\n\t<option value=\"$plugin_key\" $selected>$plugin_name</option>";
     219foreach ( $plugins as $plugin_key => $a_plugin ) {
     220        $plugin_name = $a_plugin['Name'];
     221        if ( $plugin_key == $plugin ) {
     222                $selected = " selected='selected'";
     223        } else {
     224                $selected = '';
    224225        }
     226        $plugin_name = esc_attr( $plugin_name );
     227        $plugin_key = esc_attr( $plugin_key );
     228        echo "\n\t<option value=\"$plugin_key\" $selected>$plugin_name</option>";
     229}
    225230?>
    226231                </select>
    227232                <?php submit_button( __( 'Select' ), '', 'Submit', false ); ?>
     
    230235<br class="clear" />
    231236</div>
    232237
    233 <div id="templateside">
    234         <h2><?php _e( 'Plugin Files' ); ?></h2>
     238<?php
     239$plugin_editable_files = array();
     240foreach ( $plugin_files as $plugin_file ) {
     241        if ( preg_match( '/\.([^.]+)$/', $plugin_file, $matches ) && in_array( $matches[1], $editable_extensions ) ) {
     242                $plugin_editable_files[] = $plugin_file;
     243        }
     244}
    235245
    236         <?php
    237         $plugin_editable_files = array();
    238         foreach ( $plugin_files as $plugin_file ) {
    239                 if ( preg_match('/\.([^.]+)$/', $plugin_file, $matches ) && in_array( $matches[1], $editable_extensions ) ) {
    240                         $plugin_editable_files[] = $plugin_file;
    241                 }
     246$tree_list = array();
     247foreach ( $plugin_editable_files as $plugin_file ) {
     248        $list = explode( '/', preg_replace( '#^.+?/#', '', $plugin_file ) );
     249        $last_dir = &$tree_list;
     250        foreach ( $list as $dir ) {
     251                $last_dir =& $last_dir[ $dir ];
    242252        }
    243         ?>
    244         <ul>
    245         <?php foreach ( $plugin_editable_files as $plugin_file ) : ?>
    246                 <li class="<?php echo esc_attr( $file === $plugin_file ? 'notice notice-info' : '' ); ?>">
    247                         <a href="plugin-editor.php?file=<?php echo urlencode( $plugin_file ); ?>&amp;plugin=<?php echo urlencode( $plugin ); ?>"><?php echo esc_html( preg_replace( '#^.+?/#', '', $plugin_file ) ); ?></a>
     253        $last_dir = $plugin_file;
     254}
     255// https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/treeview-2a.html
     256function build_tree( $tree, $label = false ) {
     257        global $file, $plugin;
     258        if ( is_array( $tree ) ) {
     259                foreach ( $tree as $label => $plugin_file ) :
     260                        if ( ! is_array( $plugin_file ) ) {
     261                                build_tree( $plugin_file, $label );
     262                                continue;
     263                        }
     264                        ?>
     265                        <li role="treeitem" aria-expanded="true" tabindex="-1">
     266                                <span><?php echo esc_html( $label ); ?></span>
     267                                <ul role="group"><?php build_tree( $plugin_file, $plugin ); ?></ul>
     268                        </li>
     269                        <?php
     270                endforeach;
     271        } else {
     272                ?>
     273                <li role="none" class="<?php echo esc_attr( $file === $tree ? 'notice notice-info' : '' ); ?>">
     274                        <a role="treeitem" tabindex="<?php echo $file === $tree ? '0' : '-1'; ?>"
     275                                href="plugin-editor.php?file=<?php echo urlencode( $tree ); ?>&amp;plugin=<?php echo urlencode( $plugin ); ?>">
     276                                <?php echo esc_html( $label ); ?>
     277                        </a>
    248278                </li>
    249         <?php endforeach; ?>
     279                <?php
     280        }
     281}
     282?>
     283<style>
     284</style>
     285<script>
     286jQuery(function($){
     287        // Starts with all expanded for non-js, and for js, collapses all, opens parents of current file.
     288        $('#templateside [role="tree"] [aria-expanded]').attr("aria-expanded", false);
     289        $('#templateside .notice').parents('[aria-expanded]').attr("aria-expanded", true);
     290})
     291</script>
     292<div id="templateside">
     293        <h2 id="plugin-files-label"><?php _e( 'Plugin Files' ); ?></h2>
     294        <ul role="tree" aria-labelledby="plugin-files-label">
     295                <?php build_tree( $tree_list ); ?>
    250296        </ul>
    251297</div>
    252298<form name="template" id="template" action="plugin-editor.php" method="post">
     
    258304                        <input type="hidden" name="file" value="<?php echo esc_attr( $file ); ?>" />
    259305                        <input type="hidden" name="plugin" value="<?php echo esc_attr( $plugin ); ?>" />
    260306                </div>
    261                 <?php if ( !empty( $docs_select ) ) : ?>
    262                 <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() ) + '&amp;locale=<?php echo urlencode( get_user_locale() ) ?>&amp;version=<?php echo urlencode( get_bloginfo( 'version' ) ) ?>&amp;redirect=true'); }" /></div>
     307                <?php if ( ! empty( $docs_select ) ) : ?>
     308                <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() ) + '&amp;locale=<?php echo urlencode( get_user_locale() ); ?>&amp;version=<?php echo urlencode( get_bloginfo( 'version' ) ); ?>&amp;redirect=true'); }" /></div>
    263309                <?php endif; ?>
    264 <?php if ( is_writeable($real_file) ) : ?>
     310<?php if ( is_writeable( $real_file ) ) : ?>
    265311        <div class="editor-notices">
    266312                <?php if ( in_array( $plugin, (array) get_option( 'active_plugins', array() ) ) ) { ?>
    267313                        <div class="notice notice-warning inline active-plugin-edit-warning">
    268                         <p><?php _e('<strong>Warning:</strong> Making changes to active plugins is not recommended.'); ?></p>
     314                        <p><?php _e( '<strong>Warning:</strong> Making changes to active plugins is not recommended.' ); ?></p>
    269315                </div>
    270316                <?php } ?>
    271317        </div>
     
    274320                <span class="spinner"></span>
    275321        </p>
    276322<?php else : ?>
    277         <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>
     323        <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>
    278324<?php endif; ?>
    279325<?php wp_print_file_editor_templates(); ?>
    280326</form>
     
    298344<?php
    299345endif; // editor warning notice
    300346
    301 include(ABSPATH . "wp-admin/admin-footer.php");
     347include( ABSPATH . 'wp-admin/admin-footer.php' );
  • src/wp-admin/theme-editor.php

     
    1414        exit();
    1515}
    1616
    17 if ( !current_user_can('edit_themes') )
    18         wp_die('<p>'.__('Sorry, you are not allowed to edit templates for this site.').'</p>');
     17if ( ! current_user_can( 'edit_themes' ) ) {
     18        wp_die( '<p>' . __( 'Sorry, you are not allowed to edit templates for this site.' ) . '</p>' );
     19}
    1920
    20 $title = __("Edit Themes");
     21$title = __( 'Edit Themes' );
    2122$parent_file = 'themes.php';
    2223
    2324get_current_screen()->add_help_tab( array(
    24 'id'            => 'overview',
    25 'title'         => __('Overview'),
    26 'content'       =>
    27         '<p>' . __( 'You can use the Theme Editor to edit the individual CSS and PHP files which make up your theme.' ) . '</p>' .
    28         '<p>' . __( 'Begin by choosing a theme to edit from the dropdown menu and clicking the Select button. A list then appears of the theme&#8217;s template files. Clicking once on any file name causes the file to appear in the large Editor box.' ) . '</p>' .
    29         '<p>' . __( 'For PHP files, you can use the Documentation dropdown to select from functions recognized in that file. Look Up takes you to a web page with reference material about that particular function.' ) . '</p>' .
    30         '<p id="editor-keyboard-trap-help-1">' . __( 'When using a keyboard to navigate:' ) . '</p>' .
    31         '<ul>' .
    32         '<li id="editor-keyboard-trap-help-2">' . __( 'In the editing area, the Tab key enters a tab character.' ) . '</li>' .
    33         '<li id="editor-keyboard-trap-help-3">' . __( 'To move away from this area, press the Esc key followed by the Tab key.' ) . '</li>' .
    34         '<li id="editor-keyboard-trap-help-4">' . __( 'Screen reader users: when in forms mode, you may need to press the Esc key twice.' ) . '</li>' .
    35         '</ul>' .
    36         '<p>' . __( 'After typing in your edits, click Update File.' ) . '</p>' .
    37         '<p>' . __( '<strong>Advice:</strong> think very carefully about your site crashing if you are live-editing the theme currently in use.' ) . '</p>' .
    38         /* translators: %s: link to codex article about child themes */
    39         '<p>' . sprintf( __( 'Upgrading to a newer version of the same theme will override changes made here. To avoid this, consider creating a <a href="%s">child theme</a> instead.' ), __( 'https://codex.wordpress.org/Child_Themes' ) ) . '</p>' .
    40         ( is_network_admin() ? '<p>' . __( 'Any edits to files from this screen will be reflected on all sites in the network.' ) . '</p>' : '' ),
     25        'id'        => 'overview',
     26        'title'     => __( 'Overview' ),
     27        'content'   =>
     28                '<p>' . __( 'You can use the Theme Editor to edit the individual CSS and PHP files which make up your theme.' ) . '</p>' .
     29                '<p>' . __( 'Begin by choosing a theme to edit from the dropdown menu and clicking the Select button. A list then appears of the theme&#8217;s template files. Clicking once on any file name causes the file to appear in the large Editor box.' ) . '</p>' .
     30                '<p>' . __( 'For PHP files, you can use the Documentation dropdown to select from functions recognized in that file. Look Up takes you to a web page with reference material about that particular function.' ) . '</p>' .
     31                '<p id="editor-keyboard-trap-help-1">' . __( 'When using a keyboard to navigate:' ) . '</p>' .
     32                '<ul>' .
     33                '<li id="editor-keyboard-trap-help-2">' . __( 'In the editing area, the Tab key enters a tab character.' ) . '</li>' .
     34                '<li id="editor-keyboard-trap-help-3">' . __( 'To move away from this area, press the Esc key followed by the Tab key.' ) . '</li>' .
     35                '<li id="editor-keyboard-trap-help-4">' . __( 'Screen reader users: when in forms mode, you may need to press the Esc key twice.' ) . '</li>' .
     36                '</ul>' .
     37                '<p>' . __( 'After typing in your edits, click Update File.' ) . '</p>' .
     38                '<p>' . __( '<strong>Advice:</strong> think very carefully about your site crashing if you are live-editing the theme currently in use.' ) . '</p>' .
     39                /* translators: %s: link to codex article about child themes */
     40                '<p>' . sprintf( __( 'Upgrading to a newer version of the same theme will override changes made here. To avoid this, consider creating a <a href="%s">child theme</a> instead.' ), __( 'https://codex.wordpress.org/Child_Themes' ) ) . '</p>' .
     41                ( is_network_admin() ? '<p>' . __( 'Any edits to files from this screen will be reflected on all sites in the network.' ) . '</p>' : '' ),
    4142) );
    4243
    4344get_current_screen()->set_help_sidebar(
    44         '<p><strong>' . __('For more information:') . '</strong></p>' .
    45         '<p>' . __('<a href="https://codex.wordpress.org/Theme_Development">Documentation on Theme Development</a>') . '</p>' .
    46         '<p>' . __('<a href="https://codex.wordpress.org/Using_Themes">Documentation on Using Themes</a>') . '</p>' .
    47         '<p>' . __('<a href="https://codex.wordpress.org/Editing_Files">Documentation on Editing Files</a>') . '</p>' .
    48         '<p>' . __('<a href="https://codex.wordpress.org/Template_Tags">Documentation on Template Tags</a>') . '</p>' .
    49         '<p>' . __('<a href="https://wordpress.org/support/">Support Forums</a>') . '</p>'
     45        '<p><strong>' . __( 'For more information:' ) . '</strong></p>' .
     46        '<p>' . __( '<a href="https://codex.wordpress.org/Theme_Development">Documentation on Theme Development</a>' ) . '</p>' .
     47        '<p>' . __( '<a href="https://codex.wordpress.org/Using_Themes">Documentation on Using Themes</a>' ) . '</p>' .
     48        '<p>' . __( '<a href="https://codex.wordpress.org/Editing_Files">Documentation on Editing Files</a>' ) . '</p>' .
     49        '<p>' . __( '<a href="https://codex.wordpress.org/Template_Tags">Documentation on Template Tags</a>' ) . '</p>' .
     50        '<p>' . __( '<a href="https://wordpress.org/support/">Support Forums</a>' ) . '</p>'
    5051);
    5152
    5253wp_reset_vars( array( 'action', 'error', 'file', 'theme' ) );
     
    6768        wp_die( __( 'The requested theme does not exist.' ) . ' ' . $theme->errors()->get_error_message() );
    6869}
    6970
    70 $allowed_files = $style_files = array();
     71$allowed_files = array();
     72$style_files   = array();
    7173$has_templates = false;
    7274
    7375$file_types = wp_get_theme_file_editable_extensions( $theme );
     
    7577foreach ( $file_types as $type ) {
    7678        switch ( $type ) {
    7779                case 'php':
    78                         $allowed_files += $theme->get_files( 'php', 1 );
     80                        $allowed_files += $theme->get_files( 'php', -1 );
    7981                        $has_templates = ! empty( $allowed_files );
    8082                        break;
    8183                case 'css':
    82                         $style_files = $theme->get_files( 'css' );
     84                        $style_files = $theme->get_files( 'css', -1 );
    8385                        $allowed_files['style.css'] = $style_files['style.css'];
    8486                        $allowed_files += $style_files;
    8587                        break;
    8688                default:
    87                         $allowed_files += $theme->get_files( $type );
     89                        $allowed_files += $theme->get_files( $type, -1 );
    8890                        break;
    8991        }
    9092}
     
    122124        }
    123125}
    124126
    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 ) ) );
    130         wp_add_inline_script( 'wp-theme-plugin-editor', 'wp.themePluginEditor.themeOrPlugin = "theme";' );
     127$settings = array(
     128        'codeEditor' => wp_enqueue_code_editor( compact( 'file' ) ),
     129);
     130wp_enqueue_script( 'wp-theme-plugin-editor' );
     131wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'jQuery( function( $ ) { wp.themePluginEditor.init( $( "#template" ), %s ); } )', wp_json_encode( $settings ) ) );
     132wp_add_inline_script( 'wp-theme-plugin-editor', 'wp.themePluginEditor.themeOrPlugin = "theme";' );
    131133
    132         require_once( ABSPATH . 'wp-admin/admin-header.php' );
     134require_once( ABSPATH . 'wp-admin/admin-header.php' );
    133135
    134         update_recently_edited( $file );
     136update_recently_edited( $file );
    135137
    136         if ( ! is_file( $file ) )
    137                 $error = true;
     138if ( ! is_file( $file ) ) {
     139        $error = true;
     140}
    138141
    139         $content = '';
    140         if ( ! empty( $posted_content ) ) {
    141                 $content = $posted_content;
    142         } elseif ( ! $error && filesize( $file ) > 0 ) {
    143                 $f = fopen($file, 'r');
    144                 $content = fread($f, filesize($file));
     142$content = '';
     143if ( ! empty( $posted_content ) ) {
     144        $content = $posted_content;
     145} elseif ( ! $error && filesize( $file ) > 0 ) {
     146        $f = fopen( $file, 'r' );
     147        $content = fread( $f, filesize( $file ) );
    145148
    146                 if ( '.php' == substr( $file, strrpos( $file, '.' ) ) ) {
    147                         $functions = wp_doc_link_parse( $content );
     149        if ( '.php' == substr( $file, strrpos( $file, '.' ) ) ) {
     150                $functions = wp_doc_link_parse( $content );
    148151
    149                         $docs_select = '<select name="docs-list" id="docs-list">';
    150                         $docs_select .= '<option value="">' . esc_attr__( 'Function Name&hellip;' ) . '</option>';
    151                         foreach ( $functions as $function ) {
    152                                 $docs_select .= '<option value="' . esc_attr( urlencode( $function ) ) . '">' . htmlspecialchars( $function ) . '()</option>';
    153                         }
    154                         $docs_select .= '</select>';
     152                $docs_select = '<select name="docs-list" id="docs-list">';
     153                $docs_select .= '<option value="">' . esc_attr__( 'Function Name&hellip;' ) . '</option>';
     154                foreach ( $functions as $function ) {
     155                        $docs_select .= '<option value="' . esc_attr( urlencode( $function ) ) . '">' . htmlspecialchars( $function ) . '()</option>';
    155156                }
    156 
    157                 $content = esc_textarea( $content );
     157                $docs_select .= '</select>';
    158158        }
    159159
     160        $content = esc_textarea( $content );
     161}
     162
    160163$file_description = get_file_description( $relative_file );
    161164$file_show = array_search( $file, array_filter( $allowed_files ) );
    162165$description = esc_html( $file_description );
     
    180183
    181184<div class="fileedit-sub">
    182185<div class="alignleft">
    183 <h2><?php echo $theme->display( 'Name' ); if ( $description ) echo ': ' . $description; ?></h2>
     186<h2>
     187<?php
     188echo $theme->display( 'Name' );
     189if ( $description ) {
     190        echo ': ' . $description;
     191}
     192?>
     193</h2>
    184194</div>
    185195<div class="alignright">
    186196        <form action="theme-editor.php" method="get">
    187                 <strong><label for="theme"><?php _e('Select theme to edit:'); ?> </label></strong>
     197                <strong><label for="theme"><?php _e( 'Select theme to edit:' ); ?> </label></strong>
    188198                <select name="theme" id="theme">
    189199<?php
    190200foreach ( wp_get_themes( array( 'errors' => null ) ) as $a_stylesheet => $a_theme ) {
    191         if ( $a_theme->errors() && 'theme_no_stylesheet' == $a_theme->errors()->get_error_code() )
     201        if ( $a_theme->errors() && 'theme_no_stylesheet' == $a_theme->errors()->get_error_code() ) {
    192202                continue;
     203        }
    193204
    194205        $selected = $a_stylesheet == $stylesheet ? ' selected="selected"' : '';
    195         echo "\n\t" . '<option value="' . esc_attr( $a_stylesheet ) . '"' . $selected . '>' . $a_theme->display('Name') . '</option>';
     206        echo "\n\t" . '<option value="' . esc_attr( $a_stylesheet ) . '"' . $selected . '>' . $a_theme->display( 'Name' ) . '</option>';
    196207}
    197208?>
    198209                </select>
     
    202213<br class="clear" />
    203214</div>
    204215<?php
    205 if ( $theme->errors() )
     216if ( $theme->errors() ) {
    206217        echo '<div class="error"><p><strong>' . __( 'This theme is broken.' ) . '</strong> ' . $theme->errors()->get_error_message() . '</p></div>';
     218}
    207219?>
    208220        <div id="templateside">
    209221<?php
     
    223235                                        if ( $has_templates || $theme->parent() ) :
    224236                                                echo "\t<h2>" . __( 'Templates' ) . "</h2>\n";
    225237                                                if ( $theme->parent() ) {
     238                                                        /* translators: %s: link to edit parent theme */
    226239                                                        echo '<p class="howto">' . sprintf( __( 'This child theme inherits templates from a parent theme, %s.' ),
    227240                                                                sprintf( '<a href="%s">%s</a>',
    228241                                                                        self_admin_url( 'theme-editor.php?theme=' . urlencode( $theme->get_template() ) ),
     
    245258                }
    246259
    247260                $file_description = esc_html( get_file_description( $filename ) );
    248                 if ( $filename !== basename( $absolute_filename ) || $file_description !== $filename ) {
     261                if ( basename( $absolute_filename ) !== $filename || $file_description !== $filename ) {
    249262                        $file_description .= '<br /><span class="nonessential">(' . esc_html( $filename ) . ')</span>';
    250263                }
    251264
     
    255268
    256269                $previous_file_type = $file_type;
    257270?>
    258                 <li><a href="theme-editor.php?file=<?php echo urlencode( $filename ) ?>&amp;theme=<?php echo urlencode( $stylesheet ) ?>"><?php echo $file_description; ?></a></li>
     271                <li><a href="theme-editor.php?file=<?php echo urlencode( $filename ); ?>&amp;theme=<?php echo urlencode( $stylesheet ); ?>"><?php echo $file_description; ?></a></li>
    259272<?php
    260273        endforeach;
    261274?>
     
    262275</ul>
    263276<?php endif; ?>
    264277</div>
    265 <?php if ( $error ) :
    266         echo '<div class="error"><p>' . __('Oops, no such file exists! Double check the name and try again, merci.') . '</p></div>';
    267 else : ?>
     278<?php
     279if ( $error ) :
     280        echo '<div class="error"><p>' . __( 'Oops, no such file exists! Double check the name and try again, merci.' ) . '</p></div>';
     281else :
     282        ?>
    268283        <form name="template" id="template" action="theme-editor.php" method="post">
    269284                <?php wp_nonce_field( 'edit-theme_' . $file . $stylesheet, 'nonce' ); ?>
    270285                <div>
     
    276291                </div>
    277292        <?php if ( ! empty( $functions ) ) : ?>
    278293                <div id="documentation" class="hide-if-no-js">
    279                 <label for="docs-list"><?php _e('Documentation:') ?></label>
     294                <label for="docs-list"><?php _e( 'Documentation:' ); ?></label>
    280295                <?php echo $docs_select; ?>
    281                 <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() ) + '&amp;locale=<?php echo urlencode( get_user_locale() ) ?>&amp;version=<?php echo urlencode( get_bloginfo( 'version' ) ) ?>&amp;redirect=true'); }" />
     296                <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() ) + '&amp;locale=<?php echo urlencode( get_user_locale() ); ?>&amp;version=<?php echo urlencode( get_bloginfo( 'version' ) ); ?>&amp;redirect=true'); }" />
    282297                </div>
    283298        <?php endif; ?>
    284299
     
    287302                        <?php if ( is_child_theme() && $theme->get_stylesheet() == get_template() ) : ?>
    288303                                <div class="notice notice-warning inline">
    289304                                        <p>
    290                                                 <?php if ( is_writeable( $file ) ) { ?><strong><?php _e( 'Caution:' ); ?></strong><?php } ?>
     305                                                <?php if ( is_writeable( $file ) ) { ?>
     306                                                        <strong><?php _e( 'Caution:' ); ?></strong>
     307                                                <?php } ?>
    291308                                                <?php _e( 'This is a file in your current parent theme.' ); ?>
    292309                                        </p>
    293310                                </div>
     
    299316                        <span class="spinner"></span>
    300317                </p>
    301318        <?php else : ?>
    302                 <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>
     319                <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>
    303320        <?php endif; ?>
    304321        </div>
    305322        <?php wp_print_file_editor_templates(); ?>
     
    335352<?php
    336353endif; // editor warning notice
    337354
    338 include(ABSPATH . 'wp-admin/admin-footer.php' );
     355include( ABSPATH . 'wp-admin/admin-footer.php' );
  • src/wp-includes/class-wp-theme.php

     
    981981         * @param int $depth Optional. How deep to search for files. Defaults to a flat scan (0 depth). -1 depth is infinite.
    982982         * @param bool $search_parent Optional. Whether to return parent files. Defaults to false.
    983983         * @return array Array of files, keyed by the path to the file relative to the theme's directory, with the values
    984          *                   being absolute paths.
     984         *               being absolute paths.
    985985         */
    986986        public function get_files( $type = null, $depth = 0, $search_parent = false ) {
    987                 $files = (array) self::scandir( $this->get_stylesheet_directory(), $type, $depth );
     987                // get and cache all theme files to start with.
     988                $label = sanitize_key( 'files_' . $this->cache_hash . '-' . $this->get( 'Version' ) );
     989                $transient_key = substr( $label, 0, 29 ) . md5( $label );
    988990
    989                 if ( $search_parent && $this->parent() )
    990                         $files += (array) self::scandir( $this->get_template_directory(), $type, $depth );
     991                $all_files = get_transient( $transient_key );
     992                if ( false === $all_files ) {
     993                        $all_files = (array) self::scandir( $this->get_stylesheet_directory(), null, -1 );
    991994
     995                        if ( $search_parent && $this->parent() ) {
     996                                $all_files += (array) self::scandir( $this->get_template_directory(), null, -1 );
     997                        }
     998
     999                        set_transient( $transient_key, $all_files, HOUR_IN_SECONDS );
     1000                }
     1001
     1002                // Filter $all_files by $type & $depth.
     1003                $files = array();
     1004                if ( $type ) {
     1005                        $type = (array) $type;
     1006                        $_extensions = implode( '|', $type );
     1007                }
     1008                foreach ( $all_files as $key => $file ) {
     1009                        if ( $depth >= 0 && substr_count( $key, '/' ) > $depth ) {
     1010                                continue; // Filter by depth.
     1011                        }
     1012                        if ( ! $type || preg_match( '~\.(' . $_extensions . ')$~', $file ) ) { // Filter by type.
     1013                                $files[ $key ] = $file;
     1014                        }
     1015                }
     1016
    9921017                return $files;
    9931018        }
    9941019
     
    11071132         *                     with `$relative_path`, with the values being absolute paths. False otherwise.
    11081133         */
    11091134        private static function scandir( $path, $extensions = null, $depth = 0, $relative_path = '' ) {
    1110                 if ( ! is_dir( $path ) )
     1135                if ( ! is_dir( $path ) ) {
    11111136                        return false;
     1137                }
    11121138
    11131139                if ( $extensions ) {
    11141140                        $extensions = (array) $extensions;
     
    11161142                }
    11171143
    11181144                $relative_path = trailingslashit( $relative_path );
    1119                 if ( '/' == $relative_path )
     1145                if ( '/' == $relative_path ) {
    11201146                        $relative_path = '';
     1147                }
    11211148
    11221149                $results = scandir( $path );
    11231150                $files = array();
     
    11251152                /**
    11261153                 * Filters the array of excluded directories and files while scanning theme folder.
    11271154                 *
    1128                  * @since 4.7.4
     1155                 * @since 4.7.4
    11291156                 *
    11301157                 * @param array $exclusions Array of excluded directories and files.
    11311158                 */
    1132                 $exclusions = (array) apply_filters( 'theme_scandir_exclusions', array( 'CVS', 'node_modules' ) );
     1159                $exclusions = (array) apply_filters( 'theme_scandir_exclusions', array( 'CVS', 'node_modules', 'vendor', 'bower_components' ) );
    11331160
    11341161                foreach ( $results as $result ) {
    11351162                        if ( '.' == $result[0] || in_array( $result, $exclusions, true ) ) {
     
    11361163                                continue;
    11371164                        }
    11381165                        if ( is_dir( $path . '/' . $result ) ) {
    1139                                 if ( ! $depth )
     1166                                if ( ! $depth ) {
    11401167                                        continue;
     1168                                }
    11411169                                $found = self::scandir( $path . '/' . $result, $extensions, $depth - 1 , $relative_path . $result );
    11421170                                $files = array_merge_recursive( $files, $found );
    11431171                        } elseif ( ! $extensions || preg_match( '~\.(' . $_extensions . ')$~', $result ) ) {
  • src/wp-includes/script-loader.php

     
    628628
    629629        $scripts->add( 'wp-api', "/wp-includes/js/wp-api$suffix.js", array( 'jquery', 'backbone', 'underscore', 'wp-api-request' ), false, 1 );
    630630
     631        $scripts->add( 'tree-links', "/wp-includes/js/tree-links$suffix.js", array( 'jquery' ), false, 1 );
     632        $scripts->add( 'treeitem-links', "/wp-includes/js/treeitem-links$suffix.js", array( 'jquery', 'tree-links' ), false, 1 );
     633
    631634        if ( is_admin() ) {
    632635                $scripts->add( 'admin-tags', "/wp-admin/js/tags$suffix.js", array( 'jquery', 'wp-ajax-response' ), false, 1 );
    633636                did_action( 'init' ) && $scripts->localize( 'admin-tags', 'tagsl10n', array(
  • tests/phpunit/tests/admin/includesPlugin.php

     
    9494        }
    9595
    9696        /**
     97         * @covers ::get_plugin_files
     98         */
     99        public function test_get_plugin_files_folder() {
     100                $plugin_dir = WP_PLUGIN_DIR . '/list_files_test_plugin';
     101                @mkdir( $plugin_dir );
     102                $plugin = $this->_create_plugin(null, 'list_files_test_plugin.php', $plugin_dir );
     103
     104                $sub_dir = trailingslashit( dirname( $plugin[1] ) ) . 'subdir';
     105                @mkdir( $sub_dir );
     106                @file_put_contents( $sub_dir . '/subfile.php', '<?php // Silence.' );
     107
     108                $plugin_files = get_plugin_files( plugin_basename( $plugin[1] ) );
     109                $expected = array(
     110                        'list_files_test_plugin/list_files_test_plugin.php',
     111                        'list_files_test_plugin/subdir/subfile.php',
     112                );
     113                $this->assertEquals( $expected, $plugin_files );
     114
     115                unlink( $sub_dir . '/subfile.php' );
     116                unlink( $plugin[1] );
     117                rmdir( $sub_dir );
     118                rmdir( $plugin_dir );
     119        }
     120
     121        /**
    97122         * @covers ::get_mu_plugins
    98123         */
    99124        public function test_get_mu_plugins_when_mu_plugins_exists_but_is_empty() {