PageRenderTime 54ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-content/plugins/loco-translate/src/fs/FileFinder.php

https://bitbucket.org/bryanhui/wordpress-wptouchpoc
PHP | 462 lines | 238 code | 73 blank | 151 comment | 34 complexity | d89de94c8b863d74172b95c02bbaa94a MD5 | raw file
Possible License(s): GPL-2.0, Apache-2.0, BSD-3-Clause, GPL-3.0
  1. <?php
  2. /**
  3. * Lazy file iterator. Pulls directory listings when required.
  4. */
  5. class Loco_fs_FileFinder implements Iterator, Countable, Loco_fs_FileListInterface {
  6. /**
  7. * Top-level search directories
  8. * @var Loco_fs_FileList
  9. */
  10. private $roots;
  11. /**
  12. * All directories to search, including those recursed into
  13. * @var Loco_fs_FileList
  14. */
  15. private $subdir;
  16. /**
  17. * whether directories all read into memory
  18. * @var Loco_fs_FileList
  19. */
  20. private $cached;
  21. /**
  22. * File listing already matched
  23. * @var Loco_fs_FileList
  24. */
  25. private $cache;
  26. /**
  27. * internal array pointer for whole list of paths
  28. * @var int
  29. */
  30. private $i;
  31. /**
  32. * internal pointer for directory being read
  33. * @var int
  34. */
  35. private $d;
  36. /**
  37. * current directory being read
  38. * @var resource
  39. */
  40. private $dir;
  41. /**
  42. * Path of current directory being read
  43. * @var string
  44. */
  45. private $cwd;
  46. /**
  47. * Whether directories added to search will be recursive by default
  48. * @var bool
  49. */
  50. private $recursive = false;
  51. /**
  52. * Whether currently recursing into subdirectories
  53. * This is switched on and off as each directories is opened
  54. * @var bool
  55. */
  56. private $recursing;
  57. /**
  58. * Whether to follow symlinks when recursing into subdirectories
  59. * Root-level symlinks are always resolved when possible
  60. * @var bool
  61. */
  62. private $symlinks = true;
  63. /**
  64. * List of file extensions to filter on and group by
  65. * @var array
  66. */
  67. private $exts;
  68. /**
  69. * List of directory names to exclude from recursion
  70. * @var array
  71. */
  72. private $excluded;
  73. /**
  74. * Create initial list of directories to search
  75. */
  76. public function __construct( $root = '' ){
  77. $this->roots = new Loco_fs_FileList;
  78. $this->excluded = array();
  79. if( $root ){
  80. $this->addRoot( $root );
  81. }
  82. }
  83. /**
  84. * Set recursive state of all defined roots
  85. * @return Loco_fs_FileFinder
  86. */
  87. public function setRecursive( $bool ){
  88. $this->invalidate();
  89. $this->recursive = $bool;
  90. /* @var $dir Loco_fs_Directory */
  91. foreach( $this->roots as $dir ){
  92. $dir->setRecursive( $bool );
  93. }
  94. return $this;
  95. }
  96. /**
  97. * @return Loco_fs_FileFinder
  98. */
  99. public function followLinks( $bool ){
  100. $this->invalidate();
  101. $this->symlinks = (bool) $bool;
  102. return $this;
  103. }
  104. /**
  105. *
  106. */
  107. private function invalidate(){
  108. $this->cached = false;
  109. $this->cache = null;
  110. $this->subdir = null;
  111. }
  112. /**
  113. * @return Loco_fs_FileList
  114. */
  115. public function export(){
  116. if( ! $this->cached ){
  117. $this->rewind();
  118. while( $this->valid() ){
  119. $this->next();
  120. }
  121. }
  122. return $this->cache;
  123. }
  124. /**
  125. * @return array
  126. */
  127. public function exportGroups(){
  128. $this->cached || $this->export();
  129. return $this->exts;
  130. }
  131. /**
  132. * Add a directory root to search.
  133. * @return Loco_fs_FileFinder
  134. */
  135. public function addRoot( $root, $recursive = null ){
  136. $this->invalidate();
  137. $dir = new Loco_fs_Directory($root);
  138. $this->roots->add( $dir );
  139. // new directory inherits current global setting unless set explicitly
  140. $dir->setRecursive( is_bool($recursive) ? $recursive : $this->recursive );
  141. return $this;
  142. }
  143. /**
  144. * Get all root directories to be searched
  145. * @return Loco_fs_FileList
  146. */
  147. public function getRootDirectories(){
  148. return $this->roots;
  149. }
  150. /**
  151. * Group results by file extension
  152. * @return Loco_fs_FileFinder
  153. */
  154. public function group(){
  155. return $this->groupBy( func_get_args() );
  156. }
  157. /**
  158. * Group results by file extensions given in array
  159. * @return Loco_fs_FileFinder
  160. */
  161. public function groupBy( array $exts ){
  162. $this->invalidate();
  163. $this->exts = array();
  164. foreach( $exts as $ext ){
  165. $this->exts[ trim($ext,'*.') ] = new Loco_fs_FileList;
  166. }
  167. return $this;
  168. }
  169. /**
  170. * Add one or more paths to exclude from listing
  171. * @param string e.g "node_modules"
  172. * @return Loco_fs_FileFinder
  173. */
  174. public function exclude(){
  175. $this->invalidate();
  176. foreach( func_get_args() as $path ){
  177. $file = new Loco_fs_File($path);
  178. // if path is absolute, add straight onto list
  179. if( $file->isAbsolute() ){
  180. $file->normalize();
  181. $this->excluded[] = $file;
  182. }
  183. // else append to all defined roots
  184. else {
  185. foreach( $this->roots as $dir ) {
  186. $file = new Loco_fs_File( $dir.'/'.$path );
  187. $file->normalize();
  188. $this->excluded[] = $file;
  189. }
  190. }
  191. }
  192. return $this;
  193. }
  194. /**
  195. * Export excluded paths as file objects
  196. * @return array<Loco_fs_File>
  197. */
  198. public function getExcluded(){
  199. return $this->excluded;
  200. }
  201. /**
  202. * @return resource
  203. */
  204. private function open( Loco_fs_Directory $dir ){
  205. $path = $dir->getPath();
  206. $recursive = $dir->isRecursive();
  207. if( is_link($path) ){
  208. $link = new Loco_fs_Link($path);
  209. if( $dir = $link->resolve() ){
  210. $dir->setRecursive( $recursive );
  211. return $this->open( $dir );
  212. }
  213. }// @codeCoverageIgnore
  214. /*if( ! is_dir($path) ){
  215. throw new InvalidArgumentException('Path is not a readable directory, '.$path );
  216. }*/
  217. $this->cwd = $path;
  218. $this->recursing = $recursive;
  219. return $this->dir = opendir( $path );
  220. }
  221. private function close(){
  222. closedir( $this->dir );
  223. $this->dir = null;
  224. $this->recursing = null;
  225. }
  226. /**
  227. * Test if given path is matched by one of our exclude rules
  228. * TODO would prefer a method that didn't require iteration
  229. * @param string
  230. * @return bool
  231. */
  232. public function isExcluded( $path ){
  233. /* @var $excl Loco_fs_File */
  234. foreach( $this->excluded as $excl ){
  235. if( $excl->equal($path) ){
  236. return true;
  237. }
  238. }
  239. return false;
  240. }
  241. /**
  242. * Read next valid file path from root directories
  243. * @return Loco_fs_File
  244. */
  245. private function read(){
  246. $path = null;
  247. if( is_resource($this->dir) ){
  248. while( $f = readdir($this->dir) ){
  249. if( '.' === $f{0} ){
  250. continue;
  251. }
  252. $path = $this->cwd.'/'.$f;
  253. // follow symlinks (subdir hash ensures against loops)
  254. if( is_link($path) ){
  255. if( ! $this->symlinks ){
  256. continue;
  257. }
  258. $link = new Loco_fs_Link($path);
  259. if( $file = $link->resolve() ){
  260. $path = $file->getPath();
  261. }
  262. else {
  263. continue;
  264. }
  265. }
  266. // add subdirectory to recursion list
  267. // this will result in breadth-first listing
  268. if( is_dir($path) ){
  269. if( $this->recursing && ! $this->isExcluded($path) ){
  270. $subdir = new Loco_fs_Directory($path);
  271. $subdir->setRecursive(true);
  272. $this->subdir->add( $subdir );
  273. }
  274. continue;
  275. }
  276. else if( $this->isExcluded($path) ){
  277. continue;
  278. }
  279. // file represented as object containing original path
  280. $file = new Loco_fs_File( $path );
  281. $this->add( $file );
  282. $this->i++;
  283. return $file;
  284. }
  285. $this->close();
  286. }
  287. // try next dir if nothing matched in this one
  288. $d = $this->d + 1;
  289. if( isset($this->subdir[$d]) ){
  290. $this->d = $d;
  291. $this->open( $this->subdir[$d] );
  292. return $this->read();
  293. }
  294. // else at end of all available files
  295. $this->cached = true;
  296. }
  297. /**
  298. * Implement FileListInterface::add
  299. */
  300. public function add( Loco_fs_File $file ){
  301. if( $this->exts ){
  302. $ext = $file->extension();
  303. if( ! isset($this->exts[$ext]) ){
  304. return;
  305. }
  306. $this->exts[$ext]->add( $file );
  307. }
  308. $this->cache->add( $file );
  309. }
  310. /**
  311. * @return int
  312. */
  313. public function count(){
  314. return count( $this->export() );
  315. }
  316. /**
  317. * @return Loco_fs_File|null
  318. */
  319. public function current(){
  320. $i = $this->i;
  321. if( is_int($i) && isset($this->cache[$i]) ){
  322. return $this->cache[$i];
  323. }
  324. }
  325. /**
  326. * @return Loco_fs_File|null
  327. */
  328. public function next(){
  329. if( $this->cached ){
  330. $i = $this->i + 1;
  331. if( isset($this->cache[$i]) ){
  332. $this->i = $i;
  333. return $this->cache[$i];
  334. }
  335. }
  336. else if( $path = $this->read() ){
  337. return $path;
  338. }
  339. // else at end of all directory listings
  340. $this->i = null;
  341. return null;
  342. }
  343. public function key(){
  344. return $this->i;
  345. }
  346. public function valid(){
  347. // may be in lazy state after rewind
  348. // must do initial read now in case list is empty
  349. return is_int($this->i);
  350. }
  351. /**
  352. * @return void
  353. */
  354. public function rewind(){
  355. if( $this->cached ){
  356. reset( $this->cache );
  357. $this->i = key($this->cache);
  358. }
  359. else {
  360. $this->d = 0;
  361. $this->dir = null;
  362. $this->cache = new Loco_fs_FileList;
  363. // add only root directories that exist
  364. $this->subdir = new Loco_fs_FileList;
  365. /* @var Loco_fs_Directory */
  366. foreach( $this->roots as $root ){
  367. if( $root->exists() && ! $this->isExcluded( $root->getPath() ) ){
  368. $this->subdir->add( $root );
  369. }
  370. }
  371. if( $root = reset($this->subdir) ){
  372. $this->i = -1;
  373. $this->open( $root );
  374. $this->next();
  375. }
  376. else {
  377. $this->i = null;
  378. $this->subdir = null;
  379. $this->cached = true;
  380. }
  381. }
  382. }
  383. /**
  384. * test whether internal list has been fully cached in memory
  385. */
  386. public function isCached(){
  387. return $this->cached;
  388. }
  389. }