<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[41806] trunk: File Editor: Add support for more than one sub-directory level.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/41806">41806</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/41806","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>pento</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2017-10-10 05:33:57 +0000 (Tue, 10 Oct 2017)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>File Editor: Add support for more than one sub-directory level.

The theme and plugin editors now list all files in the selected theme or plugin, recursing through subdirectories as necessary.

Props WraithKenny, schlessera, chsxf, MikeHansenMe, Daedalon, valendesigns, westonruter, pento.
Fixes <a href="https://core.trac.wordpress.org/ticket/6531">#6531</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpadminincludesfilephp">trunk/src/wp-admin/includes/file.php</a></li>
<li><a href="#trunksrcwpadminincludespluginphp">trunk/src/wp-admin/includes/plugin.php</a></li>
<li><a href="#trunksrcwpadminthemeeditorphp">trunk/src/wp-admin/theme-editor.php</a></li>
<li><a href="#trunksrcwpincludesclasswpthemephp">trunk/src/wp-includes/class-wp-theme.php</a></li>
<li><a href="#trunktestsphpunittestsadminincludesPluginphp">trunk/tests/phpunit/tests/admin/includesPlugin.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunktestsphpunittestsfunctionslistFilesphp">trunk/tests/phpunit/tests/functions/listFiles.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpadminincludesfilephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/includes/file.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/includes/file.php      2017-10-10 05:26:53 UTC (rev 41805)
+++ trunk/src/wp-admin/includes/file.php        2017-10-10 05:33:57 UTC (rev 41806)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -120,35 +120,53 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * The depth of the recursiveness can be controlled by the $levels param.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 2.6.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 4.9.0 Added the `$exclusions` parameter.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @param string $folder Optional. Full path to folder. Default empty.
</span><span class="cx" style="display: block; padding: 0 10px">  * @param int    $levels Optional. Levels of folders to follow, Default 100 (PHP Loop limit).
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @param array  $exclusions Optional. List of folders and files to skip.
</ins><span class="cx" style="display: block; padding: 0 10px">  * @return bool|array False on failure, Else array of files
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-function list_files( $folder = '', $levels = 100 ) {
-       if ( empty($folder) )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+function list_files( $folder = '', $levels = 100, $exclusions = array() ) {
+       if ( empty( $folder ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 return false;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( ! $levels )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $folder = trailingslashit( $folder );
+
+       if ( ! $levels ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 return false;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        $files = array();
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( $dir = @opendir( $folder ) ) {
-               while (($file = readdir( $dir ) ) !== false ) {
-                       if ( in_array($file, array('.', '..') ) )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       $dir = @opendir( $folder );
+       if ( $dir ) {
+               while ( ( $file = readdir( $dir ) ) !== false ) {
+                       // Skip current and parent folder links.
+                       if ( in_array( $file, array( '.', '..' ), true ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                 continue;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( is_dir( $folder . '/' . $file ) ) {
-                               $files2 = list_files( $folder . '/' . $file, $levels - 1);
-                               if ( $files2 )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 }
+
+                       // Skip hidden and excluded files.
+                       if ( '.' === $file[0] || in_array( $file, $exclusions, true ) ) {
+                               continue;
+                       }
+
+                       if ( is_dir( $folder . $file ) ) {
+                               $files2 = list_files( $folder . $file, $levels - 1 );
+                               if ( $files2 ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                         $files = array_merge($files, $files2 );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                else
-                                       $files[] = $folder . '/' . $file . '/';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         } else {
+                                       $files[] = $folder . $file . '/';
+                               }
</ins><span class="cx" style="display: block; padding: 0 10px">                         } else {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                $files[] = $folder . '/' . $file;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         $files[] = $folder . $file;
</ins><span class="cx" style="display: block; padding: 0 10px">                         }
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px">        @closedir( $dir );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px">         return $files;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span></span></pre></div>
<a id="trunksrcwpadminincludespluginphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/includes/plugin.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/includes/plugin.php    2017-10-10 05:26:53 UTC (rev 41805)
+++ trunk/src/wp-admin/includes/plugin.php      2017-10-10 05:33:57 UTC (rev 41806)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -190,35 +190,43 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @param string $plugin Path to the main plugin file from plugins directory.
</span><span class="cx" style="display: block; padding: 0 10px">  * @return array List of files relative to the plugin root.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-function get_plugin_files($plugin) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+function get_plugin_files( $plugin ) {
</ins><span class="cx" style="display: block; padding: 0 10px">         $plugin_file = WP_PLUGIN_DIR . '/' . $plugin;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $dir = dirname($plugin_file);
-       $plugin_files = array($plugin);
-       if ( is_dir($dir) && $dir != WP_PLUGIN_DIR ) {
-               $plugins_dir = @ opendir( $dir );
-               if ( $plugins_dir ) {
-                       while (($file = readdir( $plugins_dir ) ) !== false ) {
-                               if ( substr($file, 0, 1) == '.' )
-                                       continue;
-                               if ( is_dir( $dir . '/' . $file ) ) {
-                                       $plugins_subdir = @ opendir( $dir . '/' . $file );
-                                       if ( $plugins_subdir ) {
-                                               while (($subfile = readdir( $plugins_subdir ) ) !== false ) {
-                                                       if ( substr($subfile, 0, 1) == '.' )
-                                                               continue;
-                                                       $plugin_files[] = plugin_basename("$dir/$file/$subfile");
-                                               }
-                                               @closedir( $plugins_subdir );
-                                       }
-                               } else {
-                                       if ( plugin_basename("$dir/$file") != $plugin )
-                                               $plugin_files[] = plugin_basename("$dir/$file");
-                               }
-                       }
-                       @closedir( $plugins_dir );
-               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $dir = dirname( $plugin_file );
+
+       $data = get_plugin_data( $plugin_file );
+       $label = isset( $data['Version'] )
+               ? sanitize_key( 'files_' . $plugin . '-' . $data['Version'] )
+               : sanitize_key( 'files_' . $plugin );
+       $transient_key = substr( $label, 0, 29 ) . md5( $label );
+
+       $plugin_files = get_transient( $transient_key );
+       if ( false !== $plugin_files ) {
+               return $plugin_files;
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        $plugin_files = array( plugin_basename( $plugin_file ) );
+
+       if ( is_dir( $dir ) && WP_PLUGIN_DIR !== $dir ) {
+
+               /**
+                * Filters the array of excluded directories and files while scanning the folder.
+                *
+                * @since 4.9.0
+                *
+                * @param array $exclusions Array of excluded directories and files.
+                */
+               $exclusions = (array) apply_filters( 'plugin_files_exclusions', array( 'CVS', 'node_modules', 'vendor', 'bower_components' ) );
+
+               $list_files = list_files( $dir, 100, $exclusions );
+               $list_files = array_map( 'plugin_basename', $list_files );
+
+               $plugin_files = array_merge( $plugin_files, $list_files );
+               $plugin_files = array_values( array_unique( $plugin_files ) );
+       }
+
+       set_transient( $transient_key, $plugin_files, HOUR_IN_SECONDS );
+
</ins><span class="cx" style="display: block; padding: 0 10px">         return $plugin_files;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span></span></pre></div>
<a id="trunksrcwpadminthemeeditorphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/theme-editor.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/theme-editor.php       2017-10-10 05:26:53 UTC (rev 41805)
+++ trunk/src/wp-admin/theme-editor.php 2017-10-10 05:33:57 UTC (rev 41806)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -75,16 +75,16 @@
</span><span class="cx" style="display: block; padding: 0 10px"> foreach ( $file_types as $type ) {
</span><span class="cx" style="display: block; padding: 0 10px">        switch ( $type ) {
</span><span class="cx" style="display: block; padding: 0 10px">                case 'php':
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $allowed_files += $theme->get_files( 'php', 1 );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $allowed_files += $theme->get_files( 'php', -1 );
</ins><span class="cx" style="display: block; padding: 0 10px">                         $has_templates = ! empty( $allowed_files );
</span><span class="cx" style="display: block; padding: 0 10px">                        break;
</span><span class="cx" style="display: block; padding: 0 10px">                case 'css':
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $style_files = $theme->get_files( 'css' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $style_files = $theme->get_files( 'css', -1 );
</ins><span class="cx" style="display: block; padding: 0 10px">                         $allowed_files['style.css'] = $style_files['style.css'];
</span><span class="cx" style="display: block; padding: 0 10px">                        $allowed_files += $style_files;
</span><span class="cx" style="display: block; padding: 0 10px">                        break;
</span><span class="cx" style="display: block; padding: 0 10px">                default:
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $allowed_files += $theme->get_files( $type );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $allowed_files += $theme->get_files( $type, -1 );
</ins><span class="cx" style="display: block; padding: 0 10px">                         break;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunksrcwpincludesclasswpthemephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-theme.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-theme.php  2017-10-10 05:26:53 UTC (rev 41805)
+++ trunk/src/wp-includes/class-wp-theme.php    2017-10-10 05:33:57 UTC (rev 41806)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -981,14 +981,39 @@
</span><span class="cx" style="display: block; padding: 0 10px">         * @param int $depth Optional. How deep to search for files. Defaults to a flat scan (0 depth). -1 depth is infinite.
</span><span class="cx" style="display: block; padding: 0 10px">         * @param bool $search_parent Optional. Whether to return parent files. Defaults to false.
</span><span class="cx" style="display: block; padding: 0 10px">         * @return array Array of files, keyed by the path to the file relative to the theme's directory, with the values
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         *                   being absolute paths.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  *               being absolute paths.
</ins><span class="cx" style="display: block; padding: 0 10px">          */
</span><span class="cx" style="display: block; padding: 0 10px">        public function get_files( $type = null, $depth = 0, $search_parent = false ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $files = (array) self::scandir( $this->get_stylesheet_directory(), $type, $depth );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // get and cache all theme files to start with.
+               $label = sanitize_key( 'files_' . $this->cache_hash . '-' . $this->get( 'Version' ) );
+               $transient_key = substr( $label, 0, 29 ) . md5( $label );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( $search_parent && $this->parent() )
-                       $files += (array) self::scandir( $this->get_template_directory(), $type, $depth );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $all_files = get_transient( $transient_key );
+               if ( false === $all_files ) {
+                       $all_files = (array) self::scandir( $this->get_stylesheet_directory(), null, -1 );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        if ( $search_parent && $this->parent() ) {
+                               $all_files += (array) self::scandir( $this->get_template_directory(), null, -1 );
+                       }
+
+                       set_transient( $transient_key, $all_files, HOUR_IN_SECONDS );
+               }
+
+               // Filter $all_files by $type & $depth.
+               $files = array();
+               if ( $type ) {
+                       $type = (array) $type;
+                       $_extensions = implode( '|', $type );
+               }
+               foreach ( $all_files as $key => $file ) {
+                       if ( $depth >= 0 && substr_count( $key, '/' ) > $depth ) {
+                               continue; // Filter by depth.
+                       }
+                       if ( ! $type || preg_match( '~\.(' . $_extensions . ')$~', $file ) ) { // Filter by type.
+                               $files[ $key ] = $file;
+                       }
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 return $files;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1107,8 +1132,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *                     with `$relative_path`, with the values being absolute paths. False otherwise.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        private static function scandir( $path, $extensions = null, $depth = 0, $relative_path = '' ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! is_dir( $path ) )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! is_dir( $path ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         return false;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( $extensions ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $extensions = (array) $extensions;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1116,8 +1142,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $relative_path = trailingslashit( $relative_path );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( '/' == $relative_path )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( '/' == $relative_path ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         $relative_path = '';
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $results = scandir( $path );
</span><span class="cx" style="display: block; padding: 0 10px">                $files = array();
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1125,19 +1152,20 @@
</span><span class="cx" style="display: block; padding: 0 10px">                /**
</span><span class="cx" style="display: block; padding: 0 10px">                 * Filters the array of excluded directories and files while scanning theme folder.
</span><span class="cx" style="display: block; padding: 0 10px">                 *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                 * @since 4.7.4
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+          * @since 4.7.4
</ins><span class="cx" style="display: block; padding: 0 10px">                  *
</span><span class="cx" style="display: block; padding: 0 10px">                 * @param array $exclusions Array of excluded directories and files.
</span><span class="cx" style="display: block; padding: 0 10px">                 */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $exclusions = (array) apply_filters( 'theme_scandir_exclusions', array( 'CVS', 'node_modules' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $exclusions = (array) apply_filters( 'theme_scandir_exclusions', array( 'CVS', 'node_modules', 'vendor', 'bower_components' ) );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $results as $result ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( '.' == $result[0] || in_array( $result, $exclusions, true ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                                continue;
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( is_dir( $path . '/' . $result ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                if ( ! $depth )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         if ( ! $depth ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                                         continue;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                }
</ins><span class="cx" style="display: block; padding: 0 10px">                                 $found = self::scandir( $path . '/' . $result, $extensions, $depth - 1 , $relative_path . $result );
</span><span class="cx" style="display: block; padding: 0 10px">                                $files = array_merge_recursive( $files, $found );
</span><span class="cx" style="display: block; padding: 0 10px">                        } elseif ( ! $extensions || preg_match( '~\.(' . $_extensions . ')$~', $result ) ) {
</span></span></pre></div>
<a id="trunktestsphpunittestsadminincludesPluginphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/admin/includesPlugin.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/admin/includesPlugin.php        2017-10-10 05:26:53 UTC (rev 41805)
+++ trunk/tests/phpunit/tests/admin/includesPlugin.php  2017-10-10 05:33:57 UTC (rev 41806)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -94,6 +94,31 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @covers ::get_plugin_files
+        */
+       public function test_get_plugin_files_folder() {
+               $plugin_dir = WP_PLUGIN_DIR . '/list_files_test_plugin';
+               @mkdir( $plugin_dir );
+               $plugin = $this->_create_plugin(null, 'list_files_test_plugin.php', $plugin_dir );
+
+               $sub_dir = trailingslashit( dirname( $plugin[1] ) ) . 'subdir';
+               @mkdir( $sub_dir );
+               @file_put_contents( $sub_dir . '/subfile.php', '<?php // Silence.' );
+
+               $plugin_files = get_plugin_files( plugin_basename( $plugin[1] ) );
+               $expected = array(
+                       'list_files_test_plugin/list_files_test_plugin.php',
+                       'list_files_test_plugin/subdir/subfile.php',
+               );
+               $this->assertEquals( $expected, $plugin_files );
+
+               unlink( $sub_dir . '/subfile.php' );
+               unlink( $plugin[1] );
+               rmdir( $sub_dir );
+               rmdir( $plugin_dir );
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * @covers ::get_mu_plugins
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_get_mu_plugins_when_mu_plugins_exists_but_is_empty() {
</span></span></pre></div>
<a id="trunktestsphpunittestsfunctionslistFilesphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/functions/listFiles.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/functions/listFiles.php                         (rev 0)
+++ trunk/tests/phpunit/tests/functions/listFiles.php   2017-10-10 05:33:57 UTC (rev 41806)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,20 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ * Test list_files().
+ *
+ * @group functions.php
+ */
+class Tests_Functions_ListFiles extends WP_UnitTestCase {
+       public function test_list_files_returns_a_list_of_files() {
+               $admin_files = list_files( ABSPATH . 'wp-admin/' );
+               $this->assertInternalType( 'array', $admin_files );
+               $this->assertNotEmpty( $admin_files );
+               $this->assertContains( ABSPATH . 'wp-admin/index.php', $admin_files );
+       }
+
+       public function test_list_files_can_exclude_files() {
+               $admin_files = list_files( ABSPATH . 'wp-admin/', 100, array( 'index.php' ) );
+               $this->assertNotContains( ABSPATH . 'wp-admin/index.php', $admin_files );
+       }
+}
</ins></span></pre>
</div>
</div>

</body>
</html>