Ticket #24048: 24048.4.diff
File 24048.4.diff, 72.1 KB (added by , 7 years ago) |
---|
-
src/wp-admin/css/common.css
1466 1466 .wrap #templateside .notice { 1467 1467 display: block; 1468 1468 margin: 0; 1469 padding: 5px 12px;1470 1469 font-weight: 600; 1470 padding: 5px 8px; 1471 1471 text-decoration: none; 1472 1472 } 1473 1473 … … 1476 1476 } 1477 1477 1478 1478 #templateside li.notice a { 1479 1479 padding: 0; 1480 1480 } 1481 1481 1482 1482 /* Update icon. */ … … 3038 3038 } 3039 3039 #templateside { 3040 3040 margin-top: 31px; 3041 overflow: scroll; 3041 overflow: auto; 3042 padding: 2px; 3042 3043 } 3043 3044 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 } 3044 3140 #theme-plugin-editor-label { 3045 3141 display: inline-block; 3046 3142 margin-bottom: 1em; -
src/wp-admin/includes/file.php
123 123 * 124 124 * @param string $folder Optional. Full path to folder. Default empty. 125 125 * @param int $levels Optional. Levels of folders to follow, Default 100 (PHP Loop limit). 126 * @param array $exclusions Optional. List of folders to skip. 126 127 * @return bool|array False on failure, Else array of files 127 128 */ 128 function list_files( $folder = '', $levels = 100 ) {129 if ( empty( $folder) )129 function list_files( $folder = '', $levels = 100, $exclusions = array() ) { 130 if ( empty( $folder ) ) { 130 131 return false; 132 } 131 133 132 if ( ! $levels ) 134 $folder = trailingslashit( $folder ); 135 136 if ( ! $levels ) { 133 137 return false; 138 } 134 139 135 140 $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 ) ) { 139 147 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 ) { 143 158 $files = array_merge($files, $files2 ); 144 else 145 $files[] = $folder . '/' . $file . '/'; 159 } else { 160 $files[] = $folder . $file . '/'; 161 } 146 162 } else { 147 $files[] = $folder . '/' .$file;163 $files[] = $folder . $file; 148 164 } 149 165 } 150 166 } 151 167 @closedir( $dir ); 168 152 169 return $files; 153 170 } 154 171 -
src/wp-admin/includes/misc.php
270 270 } 271 271 272 272 /** 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 */ 282 function 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 */ 304 function 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 ); ?>&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 */ 363 function 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 */ 385 function 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 ); ?>&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 /** 273 428 * Flushes rewrite rules if siteurl, home or page_on_front changed. 274 429 * 275 430 * @since 2.1.0 -
src/wp-admin/includes/plugin.php
190 190 * @param string $plugin Path to the main plugin file from plugins directory. 191 191 * @return array List of files relative to the plugin root. 192 192 */ 193 function get_plugin_files( $plugin) {193 function get_plugin_files( $plugin ) { 194 194 $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; 220 206 } 221 207 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 222 230 return $plugin_files; 223 231 } 224 232 -
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 19 window.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 41 var 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 57 TreeLinks.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 94 TreeLinks.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 110 TreeLinks.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 130 TreeLinks.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 149 TreeLinks.prototype.setFocusToParentItem = function (currentItem) { 150 151 if (currentItem.groupTreeitem) { 152 this.setFocusToItem(currentItem.groupTreeitem); 153 } 154 }; 155 156 TreeLinks.prototype.setFocusToFirstItem = function () { 157 this.setFocusToItem(this.firstTreeitem); 158 }; 159 160 TreeLinks.prototype.setFocusToLastItem = function () { 161 this.setFocusToItem(this.lastTreeitem); 162 }; 163 164 TreeLinks.prototype.expandTreeitem = function (currentItem) { 165 166 if (currentItem.isExpandable) { 167 currentItem.domNode.setAttribute('aria-expanded', true); 168 this.updateVisibleTreeitems(); 169 } 170 171 }; 172 173 TreeLinks.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 184 TreeLinks.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 203 TreeLinks.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 229 TreeLinks.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 252 TreeLinks.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 24 var 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 77 TreeitemLink.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 99 TreeitemLink.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 111 TreeitemLink.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 229 TreeitemLink.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 247 TreeitemLink.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 255 TreeitemLink.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 263 TreeitemLink.prototype.handleMouseOver = function (event) { 264 event.currentTarget.classList.add('hover'); 265 }; 266 267 TreeitemLink.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 293 var 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 346 TreeitemLink.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 368 TreeitemLink.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 380 TreeitemLink.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 498 TreeitemLink.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 516 TreeitemLink.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 524 TreeitemLink.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 532 TreeitemLink.prototype.handleMouseOver = function (event) { 533 event.currentTarget.classList.add('hover'); 534 }; 535 536 TreeitemLink.prototype.handleMouseOut = function (event) { 537 event.currentTarget.classList.remove('hover'); 538 }; 539 No newline at end of file -
src/wp-admin/plugin-editor.php
14 14 exit(); 15 15 } 16 16 17 if ( !current_user_can('edit_plugins') ) 18 wp_die( __('Sorry, you are not allowed to edit plugins for this site.') ); 17 if ( ! current_user_can( 'edit_plugins' ) ) { 18 wp_die( __( 'Sorry, you are not allowed to edit plugins for this site.' ) ); 19 } 19 20 20 $title = __( "Edit Plugins");21 $title = __( 'Edit Plugins' ); 21 22 $parent_file = 'plugins.php'; 22 23 23 24 $plugins = get_plugins(); … … 66 67 } 67 68 } 68 69 69 $plugin_files = get_plugin_files( $plugin);70 $plugin_files = get_plugin_files( $plugin ); 70 71 71 72 if ( empty( $file ) ) { 72 73 $file = $plugin_files[0]; 73 74 } 74 75 75 $file = validate_file_to_edit( $file, $plugin_files);76 $file = validate_file_to_edit( $file, $plugin_files ); 76 77 $real_file = WP_PLUGIN_DIR . '/' . $file; 77 78 78 79 // Handle fallback editing of file when JavaScript is not available. … … 98 99 } 99 100 } 100 101 101 102 102 // List of allowable extensions 103 $editable_extensions = wp_get_plugin_file_editable_extensions( $plugin ); 103 104 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 107 108 if ( preg_match('/\.([^.]+)$/', $real_file, $matches) ) {109 $ext = strtolower($matches[1]);110 111 if ( !in_array( $ext, $editable_extensions) )112 wp_die(sprintf('<p>%s</p>', __('Files of this type are not editable.')));105 if ( ! 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.' ) ) ); 113 114 } 114 115 } 116 } 115 117 116 118 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’ 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’t forget to save your changes (Update File) when you’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’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’ 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’t forget to save your changes (Update File) when you’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’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>' : '' ), 131 133 ) ); 132 134 133 135 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>' 138 140 ); 139 141 140 142 $settings = array( … … 144 146 wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'jQuery( function( $ ) { wp.themePluginEditor.init( $( "#template" ), %s ); } )', wp_json_encode( $settings ) ) ); 145 147 wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'wp.themePluginEditor.themeOrPlugin = "plugin";' ) ); 146 148 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); } )' ); 148 151 149 update_recently_edited(WP_PLUGIN_DIR . '/' . $file);152 require_once( ABSPATH . 'wp-admin/admin-header.php' ); 150 153 154 update_recently_edited( WP_PLUGIN_DIR . '/' . $file ); 155 151 156 if ( ! empty( $posted_content ) ) { 152 157 $content = $posted_content; 153 158 } else { … … 157 162 if ( '.php' == substr( $real_file, strrpos( $real_file, '.' ) ) ) { 158 163 $functions = wp_doc_link_parse( $content ); 159 164 160 if ( ! empty($functions) ) {165 if ( ! empty( $functions ) ) { 161 166 $docs_select = '<select name="docs-list" id="docs-list">'; 162 167 $docs_select .= '<option value="">' . __( 'Function Name…' ) . '</option>'; 163 foreach ( $functions as $function ) {168 foreach ( $functions as $function ) { 164 169 $docs_select .= '<option value="' . esc_attr( $function ) . '">' . esc_html( $function ) . '()</option>'; 165 170 } 166 171 $docs_select .= '</select>'; … … 209 214 </div> 210 215 <div class="alignright"> 211 216 <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> 213 218 <select name="plugin" id="plugin"> 214 219 <?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>"; 220 foreach ( $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 = ''; 224 226 } 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 } 225 231 ?> 226 232 </select> 227 233 <?php submit_button( __( 'Select' ), '', 'Submit', false ); ?> … … 230 236 <br class="clear" /> 231 237 </div> 232 238 239 <?php 240 $plugin_editable_files = array(); 241 foreach ( $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 ?> 233 248 <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 ); ?>&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> 248 258 </li> 249 <?php endforeach; ?>250 259 </ul> 251 260 </div> 252 261 <form name="template" id="template" action="plugin-editor.php" method="post"> … … 258 267 <input type="hidden" name="file" value="<?php echo esc_attr( $file ); ?>" /> 259 268 <input type="hidden" name="plugin" value="<?php echo esc_attr( $plugin ); ?>" /> 260 269 </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() ) + '&locale=<?php echo urlencode( get_user_locale() ) ?>&version=<?php echo urlencode( get_bloginfo( 'version' ) )?>&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() ) + '&locale=<?php echo urlencode( get_user_locale() ); ?>&version=<?php echo urlencode( get_bloginfo( 'version' ) ); ?>&redirect=true'); }" /></div> 263 272 <?php endif; ?> 264 <?php if ( is_writeable( $real_file) ) : ?>273 <?php if ( is_writeable( $real_file ) ) : ?> 265 274 <div class="editor-notices"> 266 275 <?php if ( in_array( $plugin, (array) get_option( 'active_plugins', array() ) ) ) { ?> 267 276 <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> 269 278 </div> 270 279 <?php } ?> 271 280 </div> … … 274 283 <span class="spinner"></span> 275 284 </p> 276 285 <?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> 278 287 <?php endif; ?> 279 288 <?php wp_print_file_editor_templates(); ?> 280 289 </form> … … 298 307 <?php 299 308 endif; // editor warning notice 300 309 301 include( ABSPATH . "wp-admin/admin-footer.php");310 include( ABSPATH . 'wp-admin/admin-footer.php' ); -
src/wp-admin/theme-editor.php
14 14 exit(); 15 15 } 16 16 17 if ( !current_user_can('edit_themes') ) 18 wp_die('<p>'.__('Sorry, you are not allowed to edit templates for this site.').'</p>'); 17 if ( ! current_user_can( 'edit_themes' ) ) { 18 wp_die( '<p>' . __( 'Sorry, you are not allowed to edit templates for this site.' ) . '</p>' ); 19 } 19 20 20 $title = __( "Edit Themes");21 $title = __( 'Edit Themes' ); 21 22 $parent_file = 'themes.php'; 22 23 23 24 get_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’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’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>' : '' ), 41 42 ) ); 42 43 43 44 get_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>' 50 51 ); 51 52 52 53 wp_reset_vars( array( 'action', 'error', 'file', 'theme' ) ); … … 67 68 wp_die( __( 'The requested theme does not exist.' ) . ' ' . $theme->errors()->get_error_message() ); 68 69 } 69 70 70 $allowed_files = $style_files = array(); 71 $allowed_files = array(); 72 $style_files = array(); 71 73 $has_templates = false; 72 74 73 75 $file_types = wp_get_theme_file_editable_extensions( $theme ); … … 75 77 foreach ( $file_types as $type ) { 76 78 switch ( $type ) { 77 79 case 'php': 78 $allowed_files += $theme->get_files( 'php', 1 );80 $allowed_files += $theme->get_files( 'php', -1 ); 79 81 $has_templates = ! empty( $allowed_files ); 80 82 break; 81 83 case 'css': 82 $style_files = $theme->get_files( 'css' );84 $style_files = $theme->get_files( 'css', -1 ); 83 85 $allowed_files['style.css'] = $style_files['style.css']; 84 86 $allowed_files += $style_files; 85 87 break; 86 88 default: 87 $allowed_files += $theme->get_files( $type );89 $allowed_files += $theme->get_files( $type, -1 ); 88 90 break; 89 91 } 90 92 } 91 93 94 // Move functions.php and style.css to the top. 95 if ( isset( $allowed_files['functions.php'] ) ) { 96 $allowed_files = array( 'functions.php' => $allowed_files['functions.php'] ) + $allowed_files; 97 } 98 if ( isset( $allowed_files['style.css'] ) ) { 99 $allowed_files = array( 'style.css' => $allowed_files['style.css'] ) + $allowed_files; 100 } 101 92 102 if ( empty( $file ) ) { 93 103 $relative_file = 'style.css'; 94 104 $file = $allowed_files['style.css']; … … 103 113 $edit_error = null; 104 114 $posted_content = null; 105 115 if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) { 106 $r = wp_edit_theme_ plugin_file( wp_unslash( $_POST ) );116 $r = wp_edit_theme_theme_file( wp_unslash( $_POST ) ); 107 117 if ( is_wp_error( $r ) ) { 108 118 $edit_error = $r; 109 119 if ( check_ajax_referer( 'edit-theme_' . $file . $stylesheet, 'nonce', false ) && isset( $_POST['newcontent'] ) ) { … … 122 132 } 123 133 } 124 134 125 126 127 128 129 130 135 $settings = array( 136 'codeEditor' => wp_enqueue_code_editor( compact( 'file' ) ), 137 ); 138 wp_enqueue_script( 'wp-theme-plugin-editor' ); 139 wp_add_inline_script( 'wp-theme-plugin-editor', sprintf( 'jQuery( function( $ ) { wp.themePluginEditor.init( $( "#template" ), %s ); } )', wp_json_encode( $settings ) ) ); 140 wp_add_inline_script( 'wp-theme-plugin-editor', 'wp.themePluginEditor.themeOrPlugin = "theme";' ); 131 141 132 require_once( ABSPATH . 'wp-admin/admin-header.php' ); 142 wp_enqueue_script( 'tree-links' ); 143 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); } )' ); 133 144 134 update_recently_edited( $file);145 require_once( ABSPATH . 'wp-admin/admin-header.php' ); 135 146 136 if ( ! is_file( $file ) ) 137 $error = true; 147 update_recently_edited( $file ); 138 148 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)); 149 if ( ! is_file( $file ) ) { 150 $error = true; 151 } 145 152 146 if ( '.php' == substr( $file, strrpos( $file, '.' ) ) ) { 147 $functions = wp_doc_link_parse( $content ); 153 $content = ''; 154 if ( ! empty( $posted_content ) ) { 155 $content = $posted_content; 156 } elseif ( ! $error && filesize( $file ) > 0 ) { 157 $f = fopen( $file, 'r' ); 158 $content = fread( $f, filesize( $file ) ); 148 159 149 $docs_select = '<select name="docs-list" id="docs-list">'; 150 $docs_select .= '<option value="">' . esc_attr__( 'Function Name…' ) . '</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…' ) . '</option>'; 165 foreach ( $functions as $function ) { 166 $docs_select .= '<option value="' . esc_attr( urlencode( $function ) ) . '">' . htmlspecialchars( $function ) . '()</option>'; 155 167 } 156 157 $content = esc_textarea( $content ); 168 $docs_select .= '</select>'; 158 169 } 159 170 171 $content = esc_textarea( $content ); 172 } 173 160 174 $file_description = get_file_description( $relative_file ); 161 175 $file_show = array_search( $file, array_filter( $allowed_files ) ); 162 176 $description = esc_html( $file_description ); … … 180 194 181 195 <div class="fileedit-sub"> 182 196 <div class="alignleft"> 183 <h2><?php echo $theme->display( 'Name' ); if ( $description ) echo ': ' . $description; ?></h2> 197 <h2> 198 <?php 199 echo $theme->display( 'Name' ); 200 if ( $description ) { 201 echo ': ' . $description; 202 } 203 ?> 204 </h2> 184 205 </div> 185 206 <div class="alignright"> 186 207 <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> 188 209 <select name="theme" id="theme"> 189 210 <?php 190 211 foreach ( 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() ) { 192 213 continue; 214 } 193 215 194 216 $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>'; 196 218 } 197 219 ?> 198 220 </select> … … 202 224 <br class="clear" /> 203 225 </div> 204 226 <?php 205 if ( $theme->errors() ) 227 if ( $theme->errors() ) { 206 228 echo '<div class="error"><p><strong>' . __( 'This theme is broken.' ) . '</strong> ' . $theme->errors()->get_error_message() . '</p></div>'; 229 } 207 230 ?> 208 231 <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"; 219 243 } 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 ) ?>&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> 259 257 <?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 : ?> 258 if ( $error ) : 259 echo '<div class="error"><p>' . __( 'Oops, no such file exists! Double check the name and try again, merci.' ) . '</p></div>'; 260 else : 261 ?> 268 262 <form name="template" id="template" action="theme-editor.php" method="post"> 269 263 <?php wp_nonce_field( 'edit-theme_' . $file . $stylesheet, 'nonce' ); ?> 270 264 <div> … … 276 270 </div> 277 271 <?php if ( ! empty( $functions ) ) : ?> 278 272 <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> 280 274 <?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() ) + '&locale=<?php echo urlencode( get_user_locale() ) ?>&version=<?php echo urlencode( get_bloginfo( 'version' ) )?>&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() ) + '&locale=<?php echo urlencode( get_user_locale() ); ?>&version=<?php echo urlencode( get_bloginfo( 'version' ) ); ?>&redirect=true'); }" /> 282 276 </div> 283 277 <?php endif; ?> 284 278 … … 287 281 <?php if ( is_child_theme() && $theme->get_stylesheet() == get_template() ) : ?> 288 282 <div class="notice notice-warning inline"> 289 283 <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 } ?> 291 287 <?php _e( 'This is a file in your current parent theme.' ); ?> 292 288 </p> 293 289 </div> … … 299 295 <span class="spinner"></span> 300 296 </p> 301 297 <?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> 303 299 <?php endif; ?> 304 300 </div> 305 301 <?php wp_print_file_editor_templates(); ?> … … 335 331 <?php 336 332 endif; // editor warning notice 337 333 338 include( ABSPATH . 'wp-admin/admin-footer.php' );334 include( ABSPATH . 'wp-admin/admin-footer.php' ); -
src/wp-includes/class-wp-theme.php
981 981 * @param int $depth Optional. How deep to search for files. Defaults to a flat scan (0 depth). -1 depth is infinite. 982 982 * @param bool $search_parent Optional. Whether to return parent files. Defaults to false. 983 983 * @return array Array of files, keyed by the path to the file relative to the theme's directory, with the values 984 * 984 * being absolute paths. 985 985 */ 986 986 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 ); 988 990 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 ); 991 994 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 992 1017 return $files; 993 1018 } 994 1019 … … 1107 1132 * with `$relative_path`, with the values being absolute paths. False otherwise. 1108 1133 */ 1109 1134 private static function scandir( $path, $extensions = null, $depth = 0, $relative_path = '' ) { 1110 if ( ! is_dir( $path ) ) 1135 if ( ! is_dir( $path ) ) { 1111 1136 return false; 1137 } 1112 1138 1113 1139 if ( $extensions ) { 1114 1140 $extensions = (array) $extensions; … … 1116 1142 } 1117 1143 1118 1144 $relative_path = trailingslashit( $relative_path ); 1119 if ( '/' == $relative_path ) 1145 if ( '/' == $relative_path ) { 1120 1146 $relative_path = ''; 1147 } 1121 1148 1122 1149 $results = scandir( $path ); 1123 1150 $files = array(); … … 1125 1152 /** 1126 1153 * Filters the array of excluded directories and files while scanning theme folder. 1127 1154 * 1128 1155 * @since 4.7.4 1129 1156 * 1130 1157 * @param array $exclusions Array of excluded directories and files. 1131 1158 */ 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' ) ); 1133 1160 1134 1161 foreach ( $results as $result ) { 1135 1162 if ( '.' == $result[0] || in_array( $result, $exclusions, true ) ) { … … 1136 1163 continue; 1137 1164 } 1138 1165 if ( is_dir( $path . '/' . $result ) ) { 1139 if ( ! $depth ) 1166 if ( ! $depth ) { 1140 1167 continue; 1168 } 1141 1169 $found = self::scandir( $path . '/' . $result, $extensions, $depth - 1 , $relative_path . $result ); 1142 1170 $files = array_merge_recursive( $files, $found ); 1143 1171 } elseif ( ! $extensions || preg_match( '~\.(' . $_extensions . ')$~', $result ) ) { -
src/wp-includes/script-loader.php
885 885 $scripts->add( 'media-gallery', "/wp-admin/js/media-gallery$suffix.js", array('jquery'), false, 1 ); 886 886 887 887 $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 ); 888 891 } 889 892 } 890 893 -
tests/phpunit/tests/admin/includesPlugin.php
94 94 } 95 95 96 96 /** 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 /** 97 122 * @covers ::get_mu_plugins 98 123 */ 99 124 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 */ 8 class 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 */ 29 class 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 */ 50 class 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 */ 71 class 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 }