WordPress.org

Make WordPress Core

Ticket #24048: 24048.4.diff

File 24048.4.diff, 72.1 KB (added by WraithKenny, 3 months ago)
  • src/wp-admin/css/common.css

     
    14661466.wrap #templateside .notice { 
    14671467        display: block; 
    14681468        margin: 0; 
    1469         padding: 5px 12px; 
    14701469        font-weight: 600; 
     1470        padding: 5px 8px; 
    14711471        text-decoration: none; 
    14721472} 
    14731473 
     
    14761476} 
    14771477 
    14781478#templateside li.notice a { 
    1479   padding: 0; 
     1479        padding: 0; 
    14801480} 
    14811481 
    14821482/* Update icon. */ 
     
    30383038} 
    30393039#templateside { 
    30403040        margin-top: 31px; 
    3041         overflow: scroll; 
     3041        overflow: auto; 
     3042        padding: 2px; 
    30423043} 
    30433044 
     3045#templateside ul ul { 
     3046        padding-left: .9em; 
     3047} 
     3048 
     3049[role="treeitem"][aria-expanded="false"] > ul { 
     3050        display: none; 
     3051} 
     3052 
     3053[role="treeitem"] span[aria-hidden] { 
     3054        display: inline; 
     3055        font-family: dashicons; 
     3056        font-size: 20px; 
     3057        position: absolute; 
     3058        pointer-events: none; 
     3059} 
     3060[role="treeitem"][aria-expanded="false"] > .folder-label .icon:after { 
     3061  content: "\f139"; 
     3062} 
     3063 
     3064[role="treeitem"][aria-expanded="true"] > .folder-label .icon:after { 
     3065  content: "\f140"; 
     3066} 
     3067 
     3068[role="treeitem"] { 
     3069        outline: 0; 
     3070} 
     3071[role="treeitem"] .folder-label { 
     3072        display: block; 
     3073        padding: 3px 3px 3px .9em; 
     3074} 
     3075[role="treeitem"] .folder-label.focus { 
     3076        color: #124964; 
     3077        box-shadow: 0 0 0 1px #5b9dd9, 0 0 2px 1px rgba(30, 140, 190, .8); 
     3078} 
     3079[role="treeitem"].hover, 
     3080[role="treeitem"] .folder-label.hover { 
     3081  background-color: #DDDDDD; 
     3082} 
     3083#templateside ul[role="group"] { 
     3084        margin: 0; 
     3085        position: relative; 
     3086} 
     3087#templateside [role="group"] [role="group"]::after { 
     3088        content: ' '; 
     3089        display: block; 
     3090        position: absolute; 
     3091        left: 2px; 
     3092        border-left: 1px solid #ccc; 
     3093        top: -13px; 
     3094        bottom: 10px; 
     3095} 
     3096#templateside [role="group"] li:last-child > [role="group"]::after { 
     3097        display: none; 
     3098} 
     3099[role="group"] > li { 
     3100        position: relative; 
     3101} 
     3102[role="group"] > li::before { 
     3103        content: ' '; 
     3104        position: absolute; 
     3105        display: block; 
     3106        border-left: 1px solid #ccc; 
     3107        left: 2px; 
     3108        top: 0; 
     3109        height: 13px; 
     3110        width: 7px; 
     3111        border-bottom: 1px solid #ccc; 
     3112} 
     3113#templateside li.current-file a { 
     3114        padding: 0 3px 0 12px; 
     3115} 
     3116[role="group"] > .current-file::before { 
     3117        left: 4px; 
     3118        height: 15px; 
     3119        width: 6px; 
     3120        border-left: none; 
     3121} 
     3122[role="group"] > .current-file::after { 
     3123        bottom: -4px; 
     3124        height: 4px; 
     3125        left: 2px; 
     3126        top: auto; 
     3127} 
     3128[role="group"] > li::after { 
     3129        content: ' '; 
     3130        position: absolute; 
     3131        display: block; 
     3132        border-left: 1px solid #ccc; 
     3133        left: 2px; 
     3134        bottom: -4px; 
     3135        top: 0; 
     3136} 
     3137[role="group"] > li:last-child::after { 
     3138        display: none; 
     3139} 
    30443140#theme-plugin-editor-label { 
    30453141        display: inline-block; 
    30463142        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/misc.php

     
    270270} 
    271271 
    272272/** 
     273 * Makes a tree structure for the Theme Editor's file list. 
     274 * 
     275 * @since 4.9.0 
     276 * @access private 
     277 * 
     278 * @param string $allowed_files List of theme file paths. 
     279 * 
     280 * @return array Tree structure for listing theme files. 
     281 */ 
     282function wp_make_theme_file_tree( $allowed_files ) { 
     283        $tree_list = array(); 
     284        foreach ( $allowed_files as $file_name => $absolute_filename ) { 
     285                $list = explode( '/', $file_name ); 
     286                $last_dir = &$tree_list; 
     287                foreach ( $list as $dir ) { 
     288                        $last_dir =& $last_dir[ $dir ]; 
     289                } 
     290                $last_dir = $file_name; 
     291        } 
     292        return $tree_list; 
     293} 
     294 
     295/** 
     296 * Outputs the formatted file list for the Theme Editor. 
     297 * 
     298 * @since 4.9.0 
     299 * @access private 
     300 * 
     301 * @param array  $tree List of file/folder paths. 
     302 * @param string $label Name of file or folder to print. 
     303 */ 
     304function wp_print_theme_file_tree( $tree, $label = false, $level = 2, $index = 1, $size = 1 ) { 
     305        global $relative_file, $stylesheet; 
     306 
     307        if ( is_array( $tree ) ) { 
     308                $index = 0; 
     309                $size = count( $tree ); 
     310                foreach ( $tree as $label => $theme_file ) : 
     311                        $index++; 
     312                        if ( ! is_array( $theme_file ) ) { 
     313                                wp_print_theme_file_tree( $theme_file, $label, $level, $index, $size ); 
     314                                continue; 
     315                        } 
     316                        ?> 
     317                        <li role="treeitem" aria-expanded="true" tabindex="-1" 
     318                                aria-level="<?php echo (string) $level; ?>" 
     319                                aria-setsize="<?php echo (string) $size; ?>" 
     320                                aria-posinset="<?php echo (string) $index; ?>"> 
     321                                <span class="folder-label"><?php echo esc_html( $label ); ?> <span class="screen-reader-text">folder</span><span aria-hidden="true" class="icon"></span></span> 
     322                                <ul role="group"><?php wp_print_theme_file_tree( $theme_file, false, $level + 1, $index, $size ); ?></ul> 
     323                        </li> 
     324                        <?php 
     325                endforeach; 
     326        } else { 
     327                $filename = $tree; 
     328                ?> 
     329                <li role="none" class="<?php echo $relative_file === $filename ? 'current-file' : ''; ?>"> 
     330                        <a role="treeitem" tabindex="<?php echo $relative_file === $filename ? '0' : '-1'; ?>"  
     331                                href="theme-editor.php?file=<?php echo urlencode( $tree ); ?>&amp;theme=<?php echo urlencode( $stylesheet ); ?>" 
     332                                aria-level="<?php echo (string) $level; ?>" 
     333                                aria-setsize="<?php echo (string) $size; ?>" 
     334                                aria-posinset="<?php echo (string) $index; ?>"> 
     335                                <?php 
     336                                $file_description = esc_html( get_file_description( $filename ) ); 
     337                                if ( $file_description !== $filename && $file_description !== basename($filename) ) { 
     338                                        $file_description .= '<br /><span class="nonessential">(' . esc_html( $filename ) . ')</span>'; 
     339                                } 
     340 
     341                                if ( $relative_file === $filename ) { 
     342                                        echo '<span class="notice notice-info">' . $file_description . '</span>'; 
     343                                } else { 
     344                                        echo $file_description; 
     345                                } 
     346                                ?> 
     347                        </a> 
     348                </li> 
     349                <?php 
     350        } 
     351} 
     352 
     353/** 
     354 * Makes a tree structure for the Plugin Editor's file list. 
     355 * 
     356 * @since 4.9.0 
     357 * @access private 
     358 * 
     359 * @param string $plugin_editable_files List of plugin file paths. 
     360 * 
     361 * @return array Tree structure for listing plugin files. 
     362 */ 
     363function wp_make_plugin_file_tree( $plugin_editable_files ) { 
     364        $tree_list = array(); 
     365        foreach ( $plugin_editable_files as $plugin_file ) { 
     366                $list = explode( '/', preg_replace( '#^.+?/#', '', $plugin_file ) ); 
     367                $last_dir = &$tree_list; 
     368                foreach ( $list as $dir ) { 
     369                        $last_dir =& $last_dir[ $dir ]; 
     370                } 
     371                $last_dir = $plugin_file; 
     372        } 
     373        return $tree_list; 
     374} 
     375 
     376/** 
     377 * Outputs the formatted file list for the Plugin Editor. 
     378 * 
     379 * @since 4.9.0 
     380 * @access private 
     381 * 
     382 * @param array  $tree List of file/folder paths. 
     383 * @param string $label Name of file or folder to print. 
     384 */ 
     385function wp_print_plugin_file_tree( $tree, $label = false, $level = 2, $index = 1, $size = 1 ) { 
     386        global $file, $plugin; 
     387        if ( is_array( $tree ) ) { 
     388                $index = 0; 
     389                $size = count( $tree ); 
     390                foreach ( $tree as $label => $plugin_file ) : 
     391                        $index++; 
     392                        if ( ! is_array( $plugin_file ) ) { 
     393                                wp_print_plugin_file_tree( $plugin_file, $label, $level, $index, $size ); 
     394                                continue; 
     395                        } 
     396                        ?> 
     397                        <li role="treeitem" aria-expanded="true" tabindex="-1" 
     398                                aria-level="<?php echo (string) $level; ?>" 
     399                                aria-setsize="<?php echo (string) $size; ?>" 
     400                                aria-posinset="<?php echo (string) $index; ?>"> 
     401                                <span class="folder-label"><?php echo esc_html( $label ); ?> <span class="screen-reader-text">folder</span><span aria-hidden="true" class="icon"></span></span> 
     402                                <ul role="group"><?php wp_print_plugin_file_tree( $plugin_file, false, $level + 1, $index, $size ); ?></ul> 
     403                        </li> 
     404                        <?php 
     405                endforeach; 
     406        } else { 
     407                ?> 
     408                <li role="none" class="<?php echo $file === $tree ? 'current-file' : ''; ?>"> 
     409                        <a role="treeitem" tabindex="<?php echo $file === $tree ? '0' : '-1'; ?>"  
     410                                href="plugin-editor.php?file=<?php echo urlencode( $tree ); ?>&amp;plugin=<?php echo urlencode( $plugin ); ?>" 
     411                                aria-level="<?php echo (string) $level; ?>" 
     412                                aria-setsize="<?php echo (string) $size; ?>" 
     413                                aria-posinset="<?php echo (string) $index; ?>"> 
     414                                <?php 
     415                                if ( $file === $tree ) { 
     416                                        echo '<span class="notice notice-info">' . esc_html( $label ) . '</span>'; 
     417                                } else { 
     418                                        echo esc_html( $label ); 
     419                                } 
     420                                ?> 
     421                        </a> 
     422                </li> 
     423                <?php 
     424        } 
     425} 
     426 
     427/** 
    273428 * Flushes rewrite rules if siteurl, home or page_on_front changed. 
    274429 * 
    275430 * @since 2.1.0 
  • 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/js/tree-links.js

     
     1/* 
     2*   This content is licensed according to the W3C Software License at 
     3*   https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document 
     4* 
     5*   File:   TreeLinks.js 
     6* 
     7*   Desc:   Tree widget that implements ARIA Authoring Practices 
     8*           for a tree being used as a file viewer 
     9* 
     10*   Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt 
     11*/ 
     12 
     13/** 
     14 * ARIA Treeview example 
     15 * @function onload 
     16 * @desc  after page has loaded initializ all treeitems based on the role=treeitem 
     17 */ 
     18 
     19window.addEventListener('load', function () { 
     20 
     21    var trees = document.querySelectorAll('[role="tree"]'); 
     22 
     23    for (var i = 0; i < trees.length; i++) { 
     24        var t = new TreeLinks(trees[i]); 
     25        t.init(); 
     26    } 
     27 
     28}); 
     29 
     30/* 
     31*   @constructor 
     32* 
     33*   @desc 
     34*       Tree item object for representing the state and user interactions for a 
     35*       tree widget 
     36* 
     37*   @param node 
     38*       An element with the role=tree attribute 
     39*/ 
     40 
     41var TreeLinks = function (node) { 
     42    // Check whether node is a DOM element 
     43    if (typeof node !== 'object') { 
     44        return; 
     45    } 
     46 
     47    this.domNode = node; 
     48 
     49    this.treeitems = []; 
     50    this.firstChars = []; 
     51 
     52    this.firstTreeitem = null; 
     53    this.lastTreeitem = null; 
     54 
     55}; 
     56 
     57TreeLinks.prototype.init = function () { 
     58 
     59    function findTreeitems(node, tree, group) { 
     60 
     61        var elem = node.firstElementChild; 
     62        var ti = group; 
     63 
     64        while (elem) { 
     65 
     66            if ((elem.tagName.toLowerCase() === 'li' && elem.firstElementChild.tagName.toLowerCase() === 'span') || elem.tagName.toLowerCase() === 'a') { 
     67                ti = new TreeitemLink(elem, tree, group); 
     68                ti.init(); 
     69                tree.treeitems.push(ti); 
     70                tree.firstChars.push(ti.label.substring(0, 1).toLowerCase()); 
     71            } 
     72 
     73            if (elem.firstElementChild) { 
     74                findTreeitems(elem, tree, ti); 
     75            } 
     76 
     77            elem = elem.nextElementSibling; 
     78        } 
     79    } 
     80 
     81    // initialize pop up menus 
     82    if (!this.domNode.getAttribute('role')) { 
     83        this.domNode.setAttribute('role', 'tree'); 
     84    } 
     85 
     86    findTreeitems(this.domNode, this, false); 
     87 
     88    this.updateVisibleTreeitems(); 
     89 
     90    this.firstTreeitem.domNode.tabIndex = 0; 
     91 
     92}; 
     93 
     94TreeLinks.prototype.setFocusToItem = function (treeitem) { 
     95 
     96    for (var i = 0; i < this.treeitems.length; i++) { 
     97        var ti = this.treeitems[i]; 
     98 
     99        if (ti === treeitem) { 
     100            ti.domNode.tabIndex = 0; 
     101            ti.domNode.focus(); 
     102        } 
     103        else { 
     104            ti.domNode.tabIndex = -1; 
     105        } 
     106    } 
     107 
     108}; 
     109 
     110TreeLinks.prototype.setFocusToNextItem = function (currentItem) { 
     111 
     112    var nextItem = false; 
     113 
     114    for (var i = (this.treeitems.length - 1); i >= 0; i--) { 
     115        var ti = this.treeitems[i]; 
     116        if (ti === currentItem) { 
     117            break; 
     118        } 
     119        if (ti.isVisible) { 
     120            nextItem = ti; 
     121        } 
     122    } 
     123 
     124    if (nextItem) { 
     125        this.setFocusToItem(nextItem); 
     126    } 
     127 
     128}; 
     129 
     130TreeLinks.prototype.setFocusToPreviousItem = function (currentItem) { 
     131 
     132    var prevItem = false; 
     133 
     134    for (var i = 0; i < this.treeitems.length; i++) { 
     135        var ti = this.treeitems[i]; 
     136        if (ti === currentItem) { 
     137            break; 
     138        } 
     139        if (ti.isVisible) { 
     140            prevItem = ti; 
     141        } 
     142    } 
     143 
     144    if (prevItem) { 
     145        this.setFocusToItem(prevItem); 
     146    } 
     147}; 
     148 
     149TreeLinks.prototype.setFocusToParentItem = function (currentItem) { 
     150 
     151    if (currentItem.groupTreeitem) { 
     152        this.setFocusToItem(currentItem.groupTreeitem); 
     153    } 
     154}; 
     155 
     156TreeLinks.prototype.setFocusToFirstItem = function () { 
     157    this.setFocusToItem(this.firstTreeitem); 
     158}; 
     159 
     160TreeLinks.prototype.setFocusToLastItem = function () { 
     161    this.setFocusToItem(this.lastTreeitem); 
     162}; 
     163 
     164TreeLinks.prototype.expandTreeitem = function (currentItem) { 
     165 
     166    if (currentItem.isExpandable) { 
     167        currentItem.domNode.setAttribute('aria-expanded', true); 
     168        this.updateVisibleTreeitems(); 
     169    } 
     170 
     171}; 
     172 
     173TreeLinks.prototype.expandAllSiblingItems = function (currentItem) { 
     174    for (var i = 0; i < this.treeitems.length; i++) { 
     175        var ti = this.treeitems[i]; 
     176 
     177        if ((ti.groupTreeitem === currentItem.groupTreeitem) && ti.isExpandable) { 
     178            this.expandTreeitem(ti); 
     179        } 
     180    } 
     181 
     182}; 
     183 
     184TreeLinks.prototype.collapseTreeitem = function (currentItem) { 
     185 
     186    var groupTreeitem = false; 
     187 
     188    if (currentItem.isExpanded()) { 
     189        groupTreeitem = currentItem; 
     190    } 
     191    else { 
     192        groupTreeitem = currentItem.groupTreeitem; 
     193    } 
     194 
     195    if (groupTreeitem) { 
     196        groupTreeitem.domNode.setAttribute('aria-expanded', false); 
     197        this.updateVisibleTreeitems(); 
     198        this.setFocusToItem(groupTreeitem); 
     199    } 
     200 
     201}; 
     202 
     203TreeLinks.prototype.updateVisibleTreeitems = function () { 
     204 
     205    this.firstTreeitem = this.treeitems[0]; 
     206 
     207    for (var i = 0; i < this.treeitems.length; i++) { 
     208        var ti = this.treeitems[i]; 
     209 
     210        var parent = ti.domNode.parentNode; 
     211 
     212        ti.isVisible = true; 
     213 
     214        while (parent && (parent !== this.domNode)) { 
     215 
     216            if (parent.getAttribute('aria-expanded') == 'false') { 
     217                ti.isVisible = false; 
     218            } 
     219            parent = parent.parentNode; 
     220        } 
     221 
     222        if (ti.isVisible) { 
     223            this.lastTreeitem = ti; 
     224        } 
     225    } 
     226 
     227}; 
     228 
     229TreeLinks.prototype.setFocusByFirstCharacter = function (currentItem, char) { 
     230    var start, index, char = char.toLowerCase(); 
     231 
     232    // Get start index for search based on position of currentItem 
     233    start = this.treeitems.indexOf(currentItem) + 1; 
     234    if (start === this.treeitems.length) { 
     235        start = 0; 
     236    } 
     237 
     238    // Check remaining slots in the menu 
     239    index = this.getIndexFirstChars(start, char); 
     240 
     241    // If not found in remaining slots, check from beginning 
     242    if (index === -1) { 
     243        index = this.getIndexFirstChars(0, char); 
     244    } 
     245 
     246    // If match was found... 
     247    if (index > -1) { 
     248        this.setFocusToItem(this.treeitems[index]); 
     249    } 
     250}; 
     251 
     252TreeLinks.prototype.getIndexFirstChars = function (startIndex, char) { 
     253    for (var i = startIndex; i < this.firstChars.length; i++) { 
     254        if (this.treeitems[i].isVisible) { 
     255            if (char === this.firstChars[i]) { 
     256                return i; 
     257            } 
     258        } 
     259    } 
     260    return -1; 
     261}; 
     262 No newline at end of file 
  • src/wp-admin/js/treeitem-links.js

     
     1/* 
     2*   This content is licensed according to the W3C Software License at 
     3*   https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document 
     4* 
     5*   File:   TreeitemLink.js 
     6* 
     7*   Desc:   Treeitem widget that implements ARIA Authoring Practices 
     8*           for a tree being used as a file viewer 
     9* 
     10*   Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt 
     11*/ 
     12 
     13/* 
     14*   @constructor 
     15* 
     16*   @desc 
     17*       Treeitem object for representing the state and user interactions for a 
     18*       treeItem widget 
     19* 
     20*   @param node 
     21*       An element with the role=tree attribute 
     22*/ 
     23 
     24var TreeitemLink = function (node, treeObj, group) { 
     25 
     26    // Check whether node is a DOM element 
     27    if (typeof node !== 'object') { 
     28        return; 
     29    } 
     30 
     31    node.tabIndex = -1; 
     32    this.tree = treeObj; 
     33    this.groupTreeitem = group; 
     34    this.domNode = node; 
     35    this.label = node.textContent.trim(); 
     36    this.stopDefaultClick = false; 
     37 
     38    if (node.getAttribute('aria-label')) { 
     39        this.label = node.getAttribute('aria-label').trim(); 
     40    } 
     41 
     42    this.isExpandable = false; 
     43    this.isVisible = false; 
     44    this.inGroup = false; 
     45 
     46    if (group) { 
     47        this.inGroup = true; 
     48    } 
     49 
     50    var elem = node.firstElementChild; 
     51 
     52    while (elem) { 
     53 
     54        if (elem.tagName.toLowerCase() == 'ul') { 
     55            elem.setAttribute('role', 'group'); 
     56            this.isExpandable = true; 
     57            break; 
     58        } 
     59 
     60        elem = elem.nextElementSibling; 
     61    } 
     62 
     63    this.keyCode = Object.freeze({ 
     64        RETURN: 13, 
     65        SPACE: 32, 
     66        PAGEUP: 33, 
     67        PAGEDOWN: 34, 
     68        END: 35, 
     69        HOME: 36, 
     70        LEFT: 37, 
     71        UP: 38, 
     72        RIGHT: 39, 
     73        DOWN: 40 
     74    }); 
     75}; 
     76 
     77TreeitemLink.prototype.init = function () { 
     78    this.domNode.tabIndex = -1; 
     79 
     80    if (!this.domNode.getAttribute('role')) { 
     81        this.domNode.setAttribute('role', 'treeitem'); 
     82    } 
     83 
     84    this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); 
     85    this.domNode.addEventListener('click', this.handleClick.bind(this)); 
     86    this.domNode.addEventListener('focus', this.handleFocus.bind(this)); 
     87    this.domNode.addEventListener('blur', this.handleBlur.bind(this)); 
     88 
     89    if (this.isExpandable) { 
     90        this.domNode.firstElementChild.addEventListener('mouseover', this.handleMouseOver.bind(this)); 
     91        this.domNode.firstElementChild.addEventListener('mouseout', this.handleMouseOut.bind(this)); 
     92    } 
     93    else { 
     94        this.domNode.addEventListener('mouseover', this.handleMouseOver.bind(this)); 
     95        this.domNode.addEventListener('mouseout', this.handleMouseOut.bind(this)); 
     96    } 
     97}; 
     98 
     99TreeitemLink.prototype.isExpanded = function () { 
     100 
     101    if (this.isExpandable) { 
     102        return this.domNode.getAttribute('aria-expanded') === 'true'; 
     103    } 
     104 
     105    return false; 
     106 
     107}; 
     108 
     109/* EVENT HANDLERS */ 
     110 
     111TreeitemLink.prototype.handleKeydown = function (event) { 
     112    var tgt = event.currentTarget, 
     113        flag = false, 
     114        char = event.key, 
     115        clickEvent; 
     116 
     117    function isPrintableCharacter(str) { 
     118        return str.length === 1 && str.match(/\S/); 
     119    } 
     120 
     121    function printableCharacter(item) { 
     122        if (char == '*') { 
     123            item.tree.expandAllSiblingItems(item); 
     124            flag = true; 
     125        } 
     126        else { 
     127            if (isPrintableCharacter(char)) { 
     128                item.tree.setFocusByFirstCharacter(item, char); 
     129                flag = true; 
     130            } 
     131        } 
     132    } 
     133 
     134    this.stopDefaultClick = false; 
     135 
     136    if (event.altKey || event.ctrlKey || event.metaKey) { 
     137        return; 
     138    } 
     139 
     140    if (event.shift) { 
     141        if (event.keyCode == this.keyCode.SPACE || event.keyCode == this.keyCode.RETURN) { 
     142            event.stopPropagation(); 
     143            this.stopDefaultClick = true; 
     144        } 
     145        else { 
     146            if (isPrintableCharacter(char)) { 
     147                printableCharacter(this); 
     148            } 
     149        } 
     150    } 
     151    else { 
     152        switch (event.keyCode) { 
     153            case this.keyCode.SPACE: 
     154            case this.keyCode.RETURN: 
     155                if (this.isExpandable) { 
     156                    if (this.isExpanded()) { 
     157                        this.tree.collapseTreeitem(this); 
     158                    } 
     159                    else { 
     160                        this.tree.expandTreeitem(this); 
     161                    } 
     162                    flag = true; 
     163                } 
     164                else { 
     165                    event.stopPropagation(); 
     166                    this.stopDefaultClick = true; 
     167                } 
     168                break; 
     169 
     170            case this.keyCode.UP: 
     171                this.tree.setFocusToPreviousItem(this); 
     172                flag = true; 
     173                break; 
     174 
     175            case this.keyCode.DOWN: 
     176                this.tree.setFocusToNextItem(this); 
     177                flag = true; 
     178                break; 
     179 
     180            case this.keyCode.RIGHT: 
     181                if (this.isExpandable) { 
     182                    if (this.isExpanded()) { 
     183                        this.tree.setFocusToNextItem(this); 
     184                    } 
     185                    else { 
     186                        this.tree.expandTreeitem(this); 
     187                    } 
     188                } 
     189                flag = true; 
     190                break; 
     191 
     192            case this.keyCode.LEFT: 
     193                if (this.isExpandable && this.isExpanded()) { 
     194                    this.tree.collapseTreeitem(this); 
     195                    flag = true; 
     196                } 
     197                else { 
     198                    if (this.inGroup) { 
     199                        this.tree.setFocusToParentItem(this); 
     200                        flag = true; 
     201                    } 
     202                } 
     203                break; 
     204 
     205            case this.keyCode.HOME: 
     206                this.tree.setFocusToFirstItem(); 
     207                flag = true; 
     208                break; 
     209 
     210            case this.keyCode.END: 
     211                this.tree.setFocusToLastItem(); 
     212                flag = true; 
     213                break; 
     214 
     215            default: 
     216                if (isPrintableCharacter(char)) { 
     217                    printableCharacter(this); 
     218                } 
     219                break; 
     220        } 
     221    } 
     222 
     223    if (flag) { 
     224        event.stopPropagation(); 
     225        event.preventDefault(); 
     226    } 
     227}; 
     228 
     229TreeitemLink.prototype.handleClick = function (event) { 
     230 
     231    // only process click events that directly happened on this treeitem 
     232    if (event.target !== this.domNode && event.target !== this.domNode.firstElementChild) { 
     233        return; 
     234    } 
     235 
     236    if (this.isExpandable) { 
     237        if (this.isExpanded()) { 
     238            this.tree.collapseTreeitem(this); 
     239        } 
     240        else { 
     241            this.tree.expandTreeitem(this); 
     242        } 
     243        event.stopPropagation(); 
     244    } 
     245}; 
     246 
     247TreeitemLink.prototype.handleFocus = function (event) { 
     248    var node = this.domNode; 
     249    if (this.isExpandable) { 
     250        node = node.firstElementChild; 
     251    } 
     252    node.classList.add('focus'); 
     253}; 
     254 
     255TreeitemLink.prototype.handleBlur = function (event) { 
     256    var node = this.domNode; 
     257    if (this.isExpandable) { 
     258        node = node.firstElementChild; 
     259    } 
     260    node.classList.remove('focus'); 
     261}; 
     262 
     263TreeitemLink.prototype.handleMouseOver = function (event) { 
     264    event.currentTarget.classList.add('hover'); 
     265}; 
     266 
     267TreeitemLink.prototype.handleMouseOut = function (event) { 
     268    event.currentTarget.classList.remove('hover'); 
     269}; 
     270/* 
     271*   This content is licensed according to the W3C Software License at 
     272*   https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document 
     273* 
     274*   File:   TreeitemLink.js 
     275* 
     276*   Desc:   Treeitem widget that implements ARIA Authoring Practices 
     277*           for a tree being used as a file viewer 
     278* 
     279*   Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt 
     280*/ 
     281 
     282/* 
     283*   @constructor 
     284* 
     285*   @desc 
     286*       Treeitem object for representing the state and user interactions for a 
     287*       treeItem widget 
     288* 
     289*   @param node 
     290*       An element with the role=tree attribute 
     291*/ 
     292 
     293var TreeitemLink = function (node, treeObj, group) { 
     294 
     295    // Check whether node is a DOM element 
     296    if (typeof node !== 'object') { 
     297        return; 
     298    } 
     299 
     300    node.tabIndex = -1; 
     301    this.tree = treeObj; 
     302    this.groupTreeitem = group; 
     303    this.domNode = node; 
     304    this.label = node.textContent.trim(); 
     305    this.stopDefaultClick = false; 
     306 
     307    if (node.getAttribute('aria-label')) { 
     308        this.label = node.getAttribute('aria-label').trim(); 
     309    } 
     310 
     311    this.isExpandable = false; 
     312    this.isVisible = false; 
     313    this.inGroup = false; 
     314 
     315    if (group) { 
     316        this.inGroup = true; 
     317    } 
     318 
     319    var elem = node.firstElementChild; 
     320 
     321    while (elem) { 
     322 
     323        if (elem.tagName.toLowerCase() == 'ul') { 
     324            elem.setAttribute('role', 'group'); 
     325            this.isExpandable = true; 
     326            break; 
     327        } 
     328 
     329        elem = elem.nextElementSibling; 
     330    } 
     331 
     332    this.keyCode = Object.freeze({ 
     333        RETURN: 13, 
     334        SPACE: 32, 
     335        PAGEUP: 33, 
     336        PAGEDOWN: 34, 
     337        END: 35, 
     338        HOME: 36, 
     339        LEFT: 37, 
     340        UP: 38, 
     341        RIGHT: 39, 
     342        DOWN: 40 
     343    }); 
     344}; 
     345 
     346TreeitemLink.prototype.init = function () { 
     347    this.domNode.tabIndex = -1; 
     348 
     349    if (!this.domNode.getAttribute('role')) { 
     350        this.domNode.setAttribute('role', 'treeitem'); 
     351    } 
     352 
     353    this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); 
     354    this.domNode.addEventListener('click', this.handleClick.bind(this)); 
     355    this.domNode.addEventListener('focus', this.handleFocus.bind(this)); 
     356    this.domNode.addEventListener('blur', this.handleBlur.bind(this)); 
     357 
     358    if (this.isExpandable) { 
     359        this.domNode.firstElementChild.addEventListener('mouseover', this.handleMouseOver.bind(this)); 
     360        this.domNode.firstElementChild.addEventListener('mouseout', this.handleMouseOut.bind(this)); 
     361    } 
     362    else { 
     363        this.domNode.addEventListener('mouseover', this.handleMouseOver.bind(this)); 
     364        this.domNode.addEventListener('mouseout', this.handleMouseOut.bind(this)); 
     365    } 
     366}; 
     367 
     368TreeitemLink.prototype.isExpanded = function () { 
     369 
     370    if (this.isExpandable) { 
     371        return this.domNode.getAttribute('aria-expanded') === 'true'; 
     372    } 
     373 
     374    return false; 
     375 
     376}; 
     377 
     378/* EVENT HANDLERS */ 
     379 
     380TreeitemLink.prototype.handleKeydown = function (event) { 
     381    var tgt = event.currentTarget, 
     382        flag = false, 
     383        char = event.key, 
     384        clickEvent; 
     385 
     386    function isPrintableCharacter(str) { 
     387        return str.length === 1 && str.match(/\S/); 
     388    } 
     389 
     390    function printableCharacter(item) { 
     391        if (char == '*') { 
     392            item.tree.expandAllSiblingItems(item); 
     393            flag = true; 
     394        } 
     395        else { 
     396            if (isPrintableCharacter(char)) { 
     397                item.tree.setFocusByFirstCharacter(item, char); 
     398                flag = true; 
     399            } 
     400        } 
     401    } 
     402 
     403    this.stopDefaultClick = false; 
     404 
     405    if (event.altKey || event.ctrlKey || event.metaKey) { 
     406        return; 
     407    } 
     408 
     409    if (event.shift) { 
     410        if (event.keyCode == this.keyCode.SPACE || event.keyCode == this.keyCode.RETURN) { 
     411            event.stopPropagation(); 
     412            this.stopDefaultClick = true; 
     413        } 
     414        else { 
     415            if (isPrintableCharacter(char)) { 
     416                printableCharacter(this); 
     417            } 
     418        } 
     419    } 
     420    else { 
     421        switch (event.keyCode) { 
     422            case this.keyCode.SPACE: 
     423            case this.keyCode.RETURN: 
     424                if (this.isExpandable) { 
     425                    if (this.isExpanded()) { 
     426                        this.tree.collapseTreeitem(this); 
     427                    } 
     428                    else { 
     429                        this.tree.expandTreeitem(this); 
     430                    } 
     431                    flag = true; 
     432                } 
     433                else { 
     434                    event.stopPropagation(); 
     435                    this.stopDefaultClick = true; 
     436                } 
     437                break; 
     438 
     439            case this.keyCode.UP: 
     440                this.tree.setFocusToPreviousItem(this); 
     441                flag = true; 
     442                break; 
     443 
     444            case this.keyCode.DOWN: 
     445                this.tree.setFocusToNextItem(this); 
     446                flag = true; 
     447                break; 
     448 
     449            case this.keyCode.RIGHT: 
     450                if (this.isExpandable) { 
     451                    if (this.isExpanded()) { 
     452                        this.tree.setFocusToNextItem(this); 
     453                    } 
     454                    else { 
     455                        this.tree.expandTreeitem(this); 
     456                    } 
     457                } 
     458                flag = true; 
     459                break; 
     460 
     461            case this.keyCode.LEFT: 
     462                if (this.isExpandable && this.isExpanded()) { 
     463                    this.tree.collapseTreeitem(this); 
     464                    flag = true; 
     465                } 
     466                else { 
     467                    if (this.inGroup) { 
     468                        this.tree.setFocusToParentItem(this); 
     469                        flag = true; 
     470                    } 
     471                } 
     472                break; 
     473 
     474            case this.keyCode.HOME: 
     475                this.tree.setFocusToFirstItem(); 
     476                flag = true; 
     477                break; 
     478 
     479            case this.keyCode.END: 
     480                this.tree.setFocusToLastItem(); 
     481                flag = true; 
     482                break; 
     483 
     484            default: 
     485                if (isPrintableCharacter(char)) { 
     486                    printableCharacter(this); 
     487                } 
     488                break; 
     489        } 
     490    } 
     491 
     492    if (flag) { 
     493        event.stopPropagation(); 
     494        event.preventDefault(); 
     495    } 
     496}; 
     497 
     498TreeitemLink.prototype.handleClick = function (event) { 
     499 
     500    // only process click events that directly happened on this treeitem 
     501    if (event.target !== this.domNode && event.target !== this.domNode.firstElementChild) { 
     502        return; 
     503    } 
     504 
     505    if (this.isExpandable) { 
     506        if (this.isExpanded()) { 
     507            this.tree.collapseTreeitem(this); 
     508        } 
     509        else { 
     510            this.tree.expandTreeitem(this); 
     511        } 
     512        event.stopPropagation(); 
     513    } 
     514}; 
     515 
     516TreeitemLink.prototype.handleFocus = function (event) { 
     517    var node = this.domNode; 
     518    if (this.isExpandable) { 
     519        node = node.firstElementChild; 
     520    } 
     521    node.classList.add('focus'); 
     522}; 
     523 
     524TreeitemLink.prototype.handleBlur = function (event) { 
     525    var node = this.domNode; 
     526    if (this.isExpandable) { 
     527        node = node.firstElementChild; 
     528    } 
     529    node.classList.remove('focus'); 
     530}; 
     531 
     532TreeitemLink.prototype.handleMouseOver = function (event) { 
     533    event.currentTarget.classList.add('hover'); 
     534}; 
     535 
     536TreeitemLink.prototype.handleMouseOut = function (event) { 
     537    event.currentTarget.classList.remove('hover'); 
     538}; 
     539 No newline at end of file 
  • 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( 
     
    144146        wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'jQuery( function( $ ) { wp.themePluginEditor.init( $( "#template" ), %s ); } )', wp_json_encode( $settings ) ) ); 
    145147        wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'wp.themePluginEditor.themeOrPlugin = "plugin";' ) ); 
    146148 
    147         require_once(ABSPATH . 'wp-admin/admin-header.php'); 
     149        wp_enqueue_script( 'tree-links' ); 
     150        wp_add_inline_script( 'tree-links', 'jQuery( function( $ ) { $(\'#templateside [role="group"]\').parent().attr(\'aria-expanded\', false); $(\'#templateside .notice\').parents(\'[aria-expanded]\').attr(\'aria-expanded\', true); } )' ); 
    148151 
    149         update_recently_edited(WP_PLUGIN_DIR . '/' . $file); 
     152        require_once( ABSPATH . 'wp-admin/admin-header.php' ); 
    150153 
     154        update_recently_edited( WP_PLUGIN_DIR . '/' . $file ); 
     155 
    151156        if ( ! empty( $posted_content ) ) { 
    152157                $content = $posted_content; 
    153158        } else { 
     
    157162        if ( '.php' == substr( $real_file, strrpos( $real_file, '.' ) ) ) { 
    158163                $functions = wp_doc_link_parse( $content ); 
    159164 
    160                 if ( !empty($functions) ) { 
     165                if ( ! empty( $functions ) ) { 
    161166                        $docs_select = '<select name="docs-list" id="docs-list">'; 
    162167                        $docs_select .= '<option value="">' . __( 'Function Name&hellip;' ) . '</option>'; 
    163                         foreach ( $functions as $function) { 
     168                        foreach ( $functions as $function ) { 
    164169                                $docs_select .= '<option value="' . esc_attr( $function ) . '">' . esc_html( $function ) . '()</option>'; 
    165170                        } 
    166171                        $docs_select .= '</select>'; 
     
    209214</div> 
    210215<div class="alignright"> 
    211216        <form action="plugin-editor.php" method="get"> 
    212                 <strong><label for="plugin"><?php _e('Select plugin to edit:'); ?> </label></strong> 
     217                <strong><label for="plugin"><?php _e( 'Select plugin to edit:' ); ?> </label></strong> 
    213218                <select name="plugin" id="plugin"> 
    214219<?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>"; 
     220foreach ( $plugins as $plugin_key => $a_plugin ) { 
     221        $plugin_name = $a_plugin['Name']; 
     222        if ( $plugin_key == $plugin ) { 
     223                $selected = " selected='selected'"; 
     224        } else { 
     225                $selected = ''; 
    224226        } 
     227        $plugin_name = esc_attr( $plugin_name ); 
     228        $plugin_key = esc_attr( $plugin_key ); 
     229        echo "\n\t<option value=\"$plugin_key\" $selected>$plugin_name</option>"; 
     230} 
    225231?> 
    226232                </select> 
    227233                <?php submit_button( __( 'Select' ), '', 'Submit', false ); ?> 
     
    230236<br class="clear" /> 
    231237</div> 
    232238 
     239<?php 
     240$plugin_editable_files = array(); 
     241foreach ( $plugin_files as $plugin_file ) { 
     242        if ( preg_match( '/\.([^.]+)$/', $plugin_file, $matches ) && in_array( $matches[1], $editable_extensions ) ) { 
     243                $plugin_editable_files[] = $plugin_file; 
     244        } 
     245} 
     246 
     247?> 
    233248<div id="templateside"> 
    234         <h2><?php _e( 'Plugin Files' ); ?></h2> 
    235  
    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                 } 
    242         } 
    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> 
     249        <h2 id="plugin-files-label"><?php _e( 'Plugin Files' ); ?></h2> 
     250        <ul role="tree" aria-labelledby="plugin-files-label"> 
     251                <li role="treeitem" tabindex="-1" aria-expanded="true" 
     252                        aria-level="1" 
     253                        aria-posinset="1" 
     254                        aria-setsize="1"> 
     255                        <ul role="group" style="padding-left: 0;"> 
     256                                <?php wp_print_plugin_file_tree( wp_make_plugin_file_tree( $plugin_editable_files ) ); ?> 
     257                        </ul> 
    248258                </li> 
    249         <?php endforeach; ?> 
    250259        </ul> 
    251260</div> 
    252261<form name="template" id="template" action="plugin-editor.php" method="post"> 
     
    258267                        <input type="hidden" name="file" value="<?php echo esc_attr( $file ); ?>" /> 
    259268                        <input type="hidden" name="plugin" value="<?php echo esc_attr( $plugin ); ?>" /> 
    260269                </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> 
     270                <?php if ( ! empty( $docs_select ) ) : ?> 
     271                <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> 
    263272                <?php endif; ?> 
    264 <?php if ( is_writeable($real_file) ) : ?> 
     273<?php if ( is_writeable( $real_file ) ) : ?> 
    265274        <div class="editor-notices"> 
    266275                <?php if ( in_array( $plugin, (array) get_option( 'active_plugins', array() ) ) ) { ?> 
    267276                        <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> 
     277                        <p><?php _e( '<strong>Warning:</strong> Making changes to active plugins is not recommended.' ); ?></p> 
    269278                </div> 
    270279                <?php } ?> 
    271280        </div> 
     
    274283                <span class="spinner"></span> 
    275284        </p> 
    276285<?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> 
     286        <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> 
    278287<?php endif; ?> 
    279288<?php wp_print_file_editor_templates(); ?> 
    280289</form> 
     
    298307<?php 
    299308endif; // editor warning notice 
    300309 
    301 include(ABSPATH . "wp-admin/admin-footer.php"); 
     310include( 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} 
    9193 
     94// Move functions.php and style.css to the top. 
     95if ( isset( $allowed_files['functions.php'] ) ) { 
     96        $allowed_files = array( 'functions.php' => $allowed_files['functions.php'] ) + $allowed_files; 
     97} 
     98if ( isset( $allowed_files['style.css'] ) ) { 
     99        $allowed_files = array( 'style.css' => $allowed_files['style.css'] ) + $allowed_files; 
     100} 
     101 
    92102if ( empty( $file ) ) { 
    93103        $relative_file = 'style.css'; 
    94104        $file = $allowed_files['style.css']; 
     
    103113$edit_error = null; 
    104114$posted_content = null; 
    105115if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) { 
    106         $r = wp_edit_theme_plugin_file( wp_unslash( $_POST ) ); 
     116        $r = wp_edit_theme_theme_file( wp_unslash( $_POST ) ); 
    107117        if ( is_wp_error( $r ) ) { 
    108118                $edit_error = $r; 
    109119                if ( check_ajax_referer( 'edit-theme_' . $file . $stylesheet, 'nonce', false ) && isset( $_POST['newcontent'] ) ) { 
     
    122132        } 
    123133} 
    124134 
    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";' ); 
     135$settings = array( 
     136        'codeEditor' => wp_enqueue_code_editor( compact( 'file' ) ), 
     137); 
     138wp_enqueue_script( 'wp-theme-plugin-editor' ); 
     139wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'jQuery( function( $ ) { wp.themePluginEditor.init( $( "#template" ), %s ); } )', wp_json_encode( $settings ) ) ); 
     140wp_add_inline_script( 'wp-theme-plugin-editor', 'wp.themePluginEditor.themeOrPlugin = "theme";' ); 
    131141 
    132         require_once( ABSPATH . 'wp-admin/admin-header.php' ); 
     142wp_enqueue_script( 'tree-links' ); 
     143wp_add_inline_script( 'tree-links', 'jQuery( function( $ ) { $(\'#templateside [role="group"]\').parent().attr(\'aria-expanded\', false); $(\'#templateside .notice\').parents(\'[aria-expanded]\').attr(\'aria-expanded\', true); } )' ); 
    133144 
    134         update_recently_edited( $file ); 
     145require_once( ABSPATH . 'wp-admin/admin-header.php' ); 
    135146 
    136         if ( ! is_file( $file ) ) 
    137                 $error = true; 
     147update_recently_edited( $file ); 
    138148 
    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)); 
     149if ( ! is_file( $file ) ) { 
     150        $error = true; 
     151} 
    145152 
    146                 if ( '.php' == substr( $file, strrpos( $file, '.' ) ) ) { 
    147                         $functions = wp_doc_link_parse( $content ); 
     153$content = ''; 
     154if ( ! empty( $posted_content ) ) { 
     155        $content = $posted_content; 
     156} elseif ( ! $error && filesize( $file ) > 0 ) { 
     157        $f = fopen( $file, 'r' ); 
     158        $content = fread( $f, filesize( $file ) ); 
    148159 
    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>'; 
     160        if ( '.php' == substr( $file, strrpos( $file, '.' ) ) ) { 
     161                $functions = wp_doc_link_parse( $content ); 
     162 
     163                $docs_select = '<select name="docs-list" id="docs-list">'; 
     164                $docs_select .= '<option value="">' . esc_attr__( 'Function Name&hellip;' ) . '</option>'; 
     165                foreach ( $functions as $function ) { 
     166                        $docs_select .= '<option value="' . esc_attr( urlencode( $function ) ) . '">' . htmlspecialchars( $function ) . '()</option>'; 
    155167                } 
    156  
    157                 $content = esc_textarea( $content ); 
     168                $docs_select .= '</select>'; 
    158169        } 
    159170 
     171        $content = esc_textarea( $content ); 
     172} 
     173 
    160174$file_description = get_file_description( $relative_file ); 
    161175$file_show = array_search( $file, array_filter( $allowed_files ) ); 
    162176$description = esc_html( $file_description ); 
     
    180194 
    181195<div class="fileedit-sub"> 
    182196<div class="alignleft"> 
    183 <h2><?php echo $theme->display( 'Name' ); if ( $description ) echo ': ' . $description; ?></h2> 
     197<h2> 
     198<?php 
     199echo $theme->display( 'Name' ); 
     200if ( $description ) { 
     201        echo ': ' . $description; 
     202} 
     203?> 
     204</h2> 
    184205</div> 
    185206<div class="alignright"> 
    186207        <form action="theme-editor.php" method="get"> 
    187                 <strong><label for="theme"><?php _e('Select theme to edit:'); ?> </label></strong> 
     208                <strong><label for="theme"><?php _e( 'Select theme to edit:' ); ?> </label></strong> 
    188209                <select name="theme" id="theme"> 
    189210<?php 
    190211foreach ( 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() ) 
     212        if ( $a_theme->errors() && 'theme_no_stylesheet' == $a_theme->errors()->get_error_code() ) { 
    192213                continue; 
     214        } 
    193215 
    194216        $selected = $a_stylesheet == $stylesheet ? ' selected="selected"' : ''; 
    195         echo "\n\t" . '<option value="' . esc_attr( $a_stylesheet ) . '"' . $selected . '>' . $a_theme->display('Name') . '</option>'; 
     217        echo "\n\t" . '<option value="' . esc_attr( $a_stylesheet ) . '"' . $selected . '>' . $a_theme->display( 'Name' ) . '</option>'; 
    196218} 
    197219?> 
    198220                </select> 
     
    202224<br class="clear" /> 
    203225</div> 
    204226<?php 
    205 if ( $theme->errors() ) 
     227if ( $theme->errors() ) { 
    206228        echo '<div class="error"><p><strong>' . __( 'This theme is broken.' ) . '</strong> ' . $theme->errors()->get_error_message() . '</p></div>'; 
     229} 
    207230?> 
    208231        <div id="templateside"> 
    209 <?php 
    210 if ( $allowed_files ) : 
    211         $previous_file_type = ''; 
    212  
    213         foreach ( $allowed_files as $filename => $absolute_filename ) : 
    214                 $file_type = substr( $filename, strrpos( $filename, '.' ) ); 
    215  
    216                 if ( $file_type !== $previous_file_type ) { 
    217                         if ( '' !== $previous_file_type ) { 
    218                                 echo "\t</ul>\n"; 
     232                <h2 id="theme-files-label"><?php _e( 'Theme Files' ); ?></h2> 
     233                <?php 
     234                if ( $has_templates || $theme->parent() ) : 
     235                        if ( $theme->parent() ) { 
     236                                /* translators: %s: link to edit parent theme */ 
     237                                echo '<p class="howto">' . sprintf( __( 'This child theme inherits templates from a parent theme, %s.' ), 
     238                                        sprintf( '<a href="%s">%s</a>', 
     239                                                self_admin_url( 'theme-editor.php?theme=' . urlencode( $theme->get_template() ) ), 
     240                                                $theme->parent()->display( 'Name' ) 
     241                                        ) 
     242                                ) . "</p>\n"; 
    219243                        } 
    220  
    221                         switch ( $file_type ) { 
    222                                 case '.php': 
    223                                         if ( $has_templates || $theme->parent() ) : 
    224                                                 echo "\t<h2>" . __( 'Templates' ) . "</h2>\n"; 
    225                                                 if ( $theme->parent() ) { 
    226                                                         echo '<p class="howto">' . sprintf( __( 'This child theme inherits templates from a parent theme, %s.' ), 
    227                                                                 sprintf( '<a href="%s">%s</a>', 
    228                                                                         self_admin_url( 'theme-editor.php?theme=' . urlencode( $theme->get_template() ) ), 
    229                                                                         $theme->parent()->display( 'Name' ) 
    230                                                                 ) 
    231                                                         ) . "</p>\n"; 
    232                                                 } 
    233                                         endif; 
    234                                         break; 
    235                                 case '.css': 
    236                                         echo "\t<h2>" . _x( 'Styles', 'Theme stylesheets in theme editor' ) . "</h2>\n"; 
    237                                         break; 
    238                                 default: 
    239                                         /* translators: %s: file extension */ 
    240                                         echo "\t<h2>" . sprintf( __( '%s files' ), $file_type ) . "</h2>\n"; 
    241                                         break; 
    242                         } 
    243  
    244                         echo "\t<ul>\n"; 
    245                 } 
    246  
    247                 $file_description = esc_html( get_file_description( $filename ) ); 
    248                 if ( $filename !== basename( $absolute_filename ) || $file_description !== $filename ) { 
    249                         $file_description .= '<br /><span class="nonessential">(' . esc_html( $filename ) . ')</span>'; 
    250                 } 
    251  
    252                 if ( $absolute_filename === $file ) { 
    253                         $file_description = '<span class="notice notice-info">' . $file_description . '</span>'; 
    254                 } 
    255  
    256                 $previous_file_type = $file_type; 
    257 ?> 
    258                 <li><a href="theme-editor.php?file=<?php echo urlencode( $filename ) ?>&amp;theme=<?php echo urlencode( $stylesheet ) ?>"><?php echo $file_description; ?></a></li> 
     244                endif; 
     245                ?> 
     246                <ul role="tree" aria-labelledby="theme-files-label"> 
     247                        <li role="treeitem" tabindex="-1" aria-expanded="true" 
     248                                aria-level="1" 
     249                                aria-posinset="1" 
     250                                aria-setsize="1"> 
     251                                <ul role="group" style="padding-left: 0;"> 
     252                                        <?php wp_print_theme_file_tree( wp_make_theme_file_tree( $allowed_files ) ); ?> 
     253                                </ul> 
     254                        </li> 
     255                </ul> 
     256        </div> 
    259257<?php 
    260         endforeach; 
    261 ?> 
    262 </ul> 
    263 <?php endif; ?> 
    264 </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 : ?> 
     258if ( $error ) : 
     259        echo '<div class="error"><p>' . __( 'Oops, no such file exists! Double check the name and try again, merci.' ) . '</p></div>'; 
     260else : 
     261        ?> 
    268262        <form name="template" id="template" action="theme-editor.php" method="post"> 
    269263                <?php wp_nonce_field( 'edit-theme_' . $file . $stylesheet, 'nonce' ); ?> 
    270264                <div> 
     
    276270                </div> 
    277271        <?php if ( ! empty( $functions ) ) : ?> 
    278272                <div id="documentation" class="hide-if-no-js"> 
    279                 <label for="docs-list"><?php _e('Documentation:') ?></label> 
     273                <label for="docs-list"><?php _e( 'Documentation:' ); ?></label> 
    280274                <?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'); }" /> 
     275                <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'); }" /> 
    282276                </div> 
    283277        <?php endif; ?> 
    284278 
     
    287281                        <?php if ( is_child_theme() && $theme->get_stylesheet() == get_template() ) : ?> 
    288282                                <div class="notice notice-warning inline"> 
    289283                                        <p> 
    290                                                 <?php if ( is_writeable( $file ) ) { ?><strong><?php _e( 'Caution:' ); ?></strong><?php } ?> 
     284                                                <?php if ( is_writeable( $file ) ) { ?> 
     285                                                        <strong><?php _e( 'Caution:' ); ?></strong> 
     286                                                <?php } ?> 
    291287                                                <?php _e( 'This is a file in your current parent theme.' ); ?> 
    292288                                        </p> 
    293289                                </div> 
     
    299295                        <span class="spinner"></span> 
    300296                </p> 
    301297        <?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> 
     298                <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> 
    303299        <?php endif; ?> 
    304300        </div> 
    305301        <?php wp_print_file_editor_templates(); ?> 
     
    335331<?php 
    336332endif; // editor warning notice 
    337333 
    338 include(ABSPATH . 'wp-admin/admin-footer.php' ); 
     334include( 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

     
    885885                $scripts->add( 'media-gallery', "/wp-admin/js/media-gallery$suffix.js", array('jquery'), false, 1 ); 
    886886 
    887887                $scripts->add( 'svg-painter', '/wp-admin/js/svg-painter.js', array( 'jquery' ), false, 1 ); 
     888 
     889                $scripts->add( 'treeitem-links', "/wp-admin/js/treeitem-links$suffix.js", array( 'jquery' ), false, 1 ); 
     890                $scripts->add( 'tree-links', "/wp-admin/js/tree-links$suffix.js", array( 'treeitem-links' ), false, 1 ); 
    888891        } 
    889892} 
    890893 
  • 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() { 
  • tests/phpunit/tests/functions/listFiles.php

     
     1<?php 
     2 
     3/** 
     4 * Test list_files(). 
     5 * 
     6 * @group functions.php 
     7 */ 
     8class Tests_Functions_ListFiles extends WP_UnitTestCase { 
     9 
     10        public function test_list_files_returns_a_list_of_files() { 
     11                $admin_files = list_files( ABSPATH . 'wp-admin/' ); 
     12                $this->assertInternalType( 'array', $admin_files ); 
     13                $this->assertNotEmpty( $admin_files ); 
     14                $this->assertTrue( in_array( ABSPATH . 'wp-admin/about.php', $admin_files, true ) ); 
     15        } 
     16 
     17        public function test_list_files_can_exclude_files() { 
     18                $admin_files = list_files( ABSPATH . 'wp-admin/', 100, array( 'about.php' ) ); 
     19                $this->assertFalse( in_array( ABSPATH . 'wp-admin/about.php', $admin_files, true ) ); 
     20        } 
     21} 
     22<?php 
     23 
     24/** 
     25 * Test list_files(). 
     26 * 
     27 * @group functions.php 
     28 */ 
     29class Tests_Functions_ListFiles extends WP_UnitTestCase { 
     30 
     31        public function test_list_files_returns_a_list_of_files() { 
     32                $admin_files = list_files( ABSPATH . 'wp-admin/' ); 
     33                $this->assertInternalType( 'array', $admin_files ); 
     34                $this->assertNotEmpty( $admin_files ); 
     35                $this->assertTrue( in_array( ABSPATH . 'wp-admin/about.php', $admin_files, true ) ); 
     36        } 
     37 
     38        public function test_list_files_can_exclude_files() { 
     39                $admin_files = list_files( ABSPATH . 'wp-admin/', 100, array( 'about.php' ) ); 
     40                $this->assertFalse( in_array( ABSPATH . 'wp-admin/about.php', $admin_files, true ) ); 
     41        } 
     42} 
     43<?php 
     44 
     45/** 
     46 * Test list_files(). 
     47 * 
     48 * @group functions.php 
     49 */ 
     50class Tests_Functions_ListFiles extends WP_UnitTestCase { 
     51 
     52        public function test_list_files_returns_a_list_of_files() { 
     53                $admin_files = list_files( ABSPATH . 'wp-admin/' ); 
     54                $this->assertInternalType( 'array', $admin_files ); 
     55                $this->assertNotEmpty( $admin_files ); 
     56                $this->assertTrue( in_array( ABSPATH . 'wp-admin/about.php', $admin_files, true ) ); 
     57        } 
     58 
     59        public function test_list_files_can_exclude_files() { 
     60                $admin_files = list_files( ABSPATH . 'wp-admin/', 100, array( 'about.php' ) ); 
     61                $this->assertFalse( in_array( ABSPATH . 'wp-admin/about.php', $admin_files, true ) ); 
     62        } 
     63} 
     64<?php 
     65 
     66/** 
     67 * Test list_files(). 
     68 * 
     69 * @group functions.php 
     70 */ 
     71class Tests_Functions_ListFiles extends WP_UnitTestCase { 
     72 
     73        public function test_list_files_returns_a_list_of_files() { 
     74                $admin_files = list_files( ABSPATH . 'wp-admin/' ); 
     75                $this->assertInternalType( 'array', $admin_files ); 
     76                $this->assertNotEmpty( $admin_files ); 
     77                $this->assertTrue( in_array( ABSPATH . 'wp-admin/about.php', $admin_files, true ) ); 
     78        } 
     79 
     80        public function test_list_files_can_exclude_files() { 
     81                $admin_files = list_files( ABSPATH . 'wp-admin/', 100, array( 'about.php' ) ); 
     82                $this->assertFalse( in_array( ABSPATH . 'wp-admin/about.php', $admin_files, true ) ); 
     83        } 
     84}