PageRenderTime 52ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/phpBB/phpbb/finder.php

http://github.com/phpbb/phpbb
PHP | 546 lines | 296 code | 54 blank | 196 comment | 31 complexity | 3d516ded0033a269da8ad21be97ce6e2 MD5 | raw file
Possible License(s): GPL-3.0, AGPL-1.0
  1. <?php
  2. /**
  3. *
  4. * This file is part of the phpBB Forum Software package.
  5. *
  6. * @copyright (c) phpBB Limited <https://www.phpbb.com>
  7. * @license GNU General Public License, version 2 (GPL-2.0)
  8. *
  9. * For full copyright and license information, please see
  10. * the docs/CREDITS.txt file.
  11. *
  12. */
  13. namespace phpbb;
  14. use phpbb\filesystem\helper as filesystem_helper;
  15. /**
  16. * The finder provides a simple way to locate files in the core and a set of extensions
  17. */
  18. class finder
  19. {
  20. protected $extensions;
  21. protected $phpbb_root_path;
  22. protected $cache;
  23. protected $php_ext;
  24. /**
  25. * The cache variable name used to store $this->cached_queries in $this->cache.
  26. *
  27. * Allows the use of multiple differently configured finders with the same cache.
  28. * @var string
  29. */
  30. protected $cache_name;
  31. /**
  32. * An associative array, containing all search parameters set in methods.
  33. * @var array
  34. */
  35. protected $query;
  36. /**
  37. * A map from md5 hashes of serialized queries to their previously retrieved
  38. * results.
  39. * @var array
  40. */
  41. protected $cached_queries;
  42. /**
  43. * Creates a new finder instance with its dependencies
  44. *
  45. * @param string $phpbb_root_path Path to the phpbb root directory
  46. * @param \phpbb\cache\service $cache A cache instance or null
  47. * @param string $php_ext php file extension
  48. * @param string $cache_name The name of the cache variable, defaults to
  49. * _ext_finder
  50. */
  51. public function __construct($phpbb_root_path = '', \phpbb\cache\service $cache = null, $php_ext = 'php', $cache_name = '_ext_finder')
  52. {
  53. $this->phpbb_root_path = $phpbb_root_path;
  54. $this->cache = $cache;
  55. $this->php_ext = $php_ext;
  56. $this->cache_name = $cache_name;
  57. $this->query = array(
  58. 'core_path' => false,
  59. 'core_suffix' => false,
  60. 'core_prefix' => false,
  61. 'core_directory' => false,
  62. 'extension_suffix' => false,
  63. 'extension_prefix' => false,
  64. 'extension_directory' => false,
  65. );
  66. $this->extensions = array();
  67. $this->cached_queries = ($this->cache) ? $this->cache->get($this->cache_name) : false;
  68. }
  69. /**
  70. * Set the array of extensions
  71. *
  72. * @param array $extensions A list of extensions that should be searched as well
  73. * @param bool $replace_list Should the list be emptied before adding the extensions
  74. * @return \phpbb\finder This object for chaining calls
  75. */
  76. public function set_extensions(array $extensions, $replace_list = true)
  77. {
  78. if ($replace_list)
  79. {
  80. $this->extensions = array();
  81. }
  82. foreach ($extensions as $ext_name)
  83. {
  84. $this->extensions[$ext_name] = $this->phpbb_root_path . 'ext/' . $ext_name . '/';
  85. }
  86. return $this;
  87. }
  88. /**
  89. * Sets a core path to be searched in addition to extensions
  90. *
  91. * @param string $core_path The path relative to phpbb_root_path
  92. * @return \phpbb\finder This object for chaining calls
  93. */
  94. public function core_path($core_path)
  95. {
  96. $this->query['core_path'] = $core_path;
  97. return $this;
  98. }
  99. /**
  100. * Sets the suffix all files found in extensions and core must match.
  101. *
  102. * There is no default file extension, so to find PHP files only, you will
  103. * have to specify .php as a suffix. However when using get_classes, the .php
  104. * file extension is automatically added to suffixes.
  105. *
  106. * @param string $suffix A filename suffix
  107. * @return \phpbb\finder This object for chaining calls
  108. */
  109. public function suffix($suffix)
  110. {
  111. $this->core_suffix($suffix);
  112. $this->extension_suffix($suffix);
  113. return $this;
  114. }
  115. /**
  116. * Sets a suffix all files found in extensions must match
  117. *
  118. * There is no default file extension, so to find PHP files only, you will
  119. * have to specify .php as a suffix. However when using get_classes, the .php
  120. * file extension is automatically added to suffixes.
  121. *
  122. * @param string $extension_suffix A filename suffix
  123. * @return \phpbb\finder This object for chaining calls
  124. */
  125. public function extension_suffix($extension_suffix)
  126. {
  127. $this->query['extension_suffix'] = $extension_suffix;
  128. return $this;
  129. }
  130. /**
  131. * Sets a suffix all files found in the core path must match
  132. *
  133. * There is no default file extension, so to find PHP files only, you will
  134. * have to specify .php as a suffix. However when using get_classes, the .php
  135. * file extension is automatically added to suffixes.
  136. *
  137. * @param string $core_suffix A filename suffix
  138. * @return \phpbb\finder This object for chaining calls
  139. */
  140. public function core_suffix($core_suffix)
  141. {
  142. $this->query['core_suffix'] = $core_suffix;
  143. return $this;
  144. }
  145. /**
  146. * Sets the prefix all files found in extensions and core must match
  147. *
  148. * @param string $prefix A filename prefix
  149. * @return \phpbb\finder This object for chaining calls
  150. */
  151. public function prefix($prefix)
  152. {
  153. $this->core_prefix($prefix);
  154. $this->extension_prefix($prefix);
  155. return $this;
  156. }
  157. /**
  158. * Sets a prefix all files found in extensions must match
  159. *
  160. * @param string $extension_prefix A filename prefix
  161. * @return \phpbb\finder This object for chaining calls
  162. */
  163. public function extension_prefix($extension_prefix)
  164. {
  165. $this->query['extension_prefix'] = $extension_prefix;
  166. return $this;
  167. }
  168. /**
  169. * Sets a prefix all files found in the core path must match
  170. *
  171. * @param string $core_prefix A filename prefix
  172. * @return \phpbb\finder This object for chaining calls
  173. */
  174. public function core_prefix($core_prefix)
  175. {
  176. $this->query['core_prefix'] = $core_prefix;
  177. return $this;
  178. }
  179. /**
  180. * Sets a directory all files found in extensions and core must be contained in
  181. *
  182. * Automatically sets the core_directory if its value does not differ from
  183. * the current directory.
  184. *
  185. * @param string $directory
  186. * @return \phpbb\finder This object for chaining calls
  187. */
  188. public function directory($directory)
  189. {
  190. $this->core_directory($directory);
  191. $this->extension_directory($directory);
  192. return $this;
  193. }
  194. /**
  195. * Sets a directory all files found in extensions must be contained in
  196. *
  197. * @param string $extension_directory
  198. * @return \phpbb\finder This object for chaining calls
  199. */
  200. public function extension_directory($extension_directory)
  201. {
  202. $this->query['extension_directory'] = $this->sanitise_directory($extension_directory);
  203. return $this;
  204. }
  205. /**
  206. * Sets a directory all files found in the core path must be contained in
  207. *
  208. * @param string $core_directory
  209. * @return \phpbb\finder This object for chaining calls
  210. */
  211. public function core_directory($core_directory)
  212. {
  213. $this->query['core_directory'] = $this->sanitise_directory($core_directory);
  214. return $this;
  215. }
  216. /**
  217. * Removes occurrences of /./ and makes sure path ends without trailing slash
  218. *
  219. * @param string $directory A directory pattern
  220. * @return string A cleaned up directory pattern
  221. */
  222. protected function sanitise_directory($directory)
  223. {
  224. $directory = filesystem_helper::clean_path($directory);
  225. $dir_len = strlen($directory);
  226. if ($dir_len > 1 && $directory[$dir_len - 1] === '/')
  227. {
  228. $directory = substr($directory, 0, -1);
  229. }
  230. return $directory;
  231. }
  232. /**
  233. * Finds classes matching the configured options if they follow phpBB naming rules.
  234. *
  235. * The php file extension is automatically added to suffixes.
  236. *
  237. * Note: If a file is matched but contains a class name not following the
  238. * phpBB naming rules an incorrect class name will be returned.
  239. *
  240. * @param bool $cache Whether the result should be cached
  241. * @return array An array of found class names
  242. */
  243. public function get_classes($cache = true)
  244. {
  245. $this->query['extension_suffix'] .= '.' . $this->php_ext;
  246. $this->query['core_suffix'] .= '.' . $this->php_ext;
  247. $files = $this->find($cache, false);
  248. return $this->get_classes_from_files($files);
  249. }
  250. /**
  251. * Get class names from a list of files
  252. *
  253. * @param array $files Array of files (from find())
  254. * @return array Array of class names
  255. */
  256. public function get_classes_from_files($files)
  257. {
  258. $classes = array();
  259. foreach ($files as $file => $ext_name)
  260. {
  261. $class = substr($file, 0, -strlen('.' . $this->php_ext));
  262. if ($ext_name === '/' && preg_match('#^includes/#', $file))
  263. {
  264. $class = preg_replace('#^includes/#', '', $class);
  265. $classes[] = 'phpbb_' . str_replace('/', '_', $class);
  266. }
  267. else
  268. {
  269. $class = preg_replace('#^ext/#', '', $class);
  270. $classes[] = '\\' . str_replace('/', '\\', $class);
  271. }
  272. }
  273. return $classes;
  274. }
  275. /**
  276. * Finds all directories matching the configured options
  277. *
  278. * @param bool $cache Whether the result should be cached
  279. * @param bool $extension_keys Whether the result should have extension name as array key
  280. * @return array An array of paths to found directories
  281. */
  282. public function get_directories($cache = true, $extension_keys = false)
  283. {
  284. return $this->find_with_root_path($cache, true, $extension_keys);
  285. }
  286. /**
  287. * Finds all files matching the configured options.
  288. *
  289. * @param bool $cache Whether the result should be cached
  290. * @return array An array of paths to found files
  291. */
  292. public function get_files($cache = true)
  293. {
  294. return $this->find_with_root_path($cache, false);
  295. }
  296. /**
  297. * A wrapper around the general find which prepends a root path to results
  298. *
  299. * @param bool $cache Whether the result should be cached
  300. * @param bool $is_dir Directories will be returned when true, only files
  301. * otherwise
  302. * @param bool $extension_keys If true, result will be associative array
  303. * with extension name as key
  304. * @return array An array of paths to found items
  305. */
  306. protected function find_with_root_path($cache = true, $is_dir = false, $extension_keys = false)
  307. {
  308. $items = $this->find($cache, $is_dir);
  309. $result = array();
  310. foreach ($items as $item => $ext_name)
  311. {
  312. if ($extension_keys)
  313. {
  314. $result[$ext_name] = $this->phpbb_root_path . $item;
  315. }
  316. else
  317. {
  318. $result[] = $this->phpbb_root_path . $item;
  319. }
  320. }
  321. return $result;
  322. }
  323. /**
  324. * Finds all file system entries matching the configured options
  325. *
  326. * @param bool $cache Whether the result should be cached
  327. * @param bool $is_dir Directories will be returned when true, only files
  328. * otherwise
  329. * @return array An array of paths to found items
  330. */
  331. public function find($cache = true, $is_dir = false)
  332. {
  333. $extensions = $this->extensions;
  334. if ($this->query['core_path'])
  335. {
  336. $extensions['/'] = $this->phpbb_root_path . $this->query['core_path'];
  337. }
  338. $files = array();
  339. $file_list = $this->find_from_paths($extensions, $cache, $is_dir);
  340. foreach ($file_list as $file)
  341. {
  342. $files[$file['named_path']] = $file['ext_name'];
  343. }
  344. return $files;
  345. }
  346. /**
  347. * Finds all file system entries matching the configured options for one
  348. * specific extension
  349. *
  350. * @param string $extension_name Name of the extension
  351. * @param string $extension_path Relative path to the extension root directory
  352. * @param bool $cache Whether the result should be cached
  353. * @param bool $is_dir Directories will be returned when true, only files
  354. * otherwise
  355. * @return array An array of paths to found items
  356. */
  357. public function find_from_extension($extension_name, $extension_path, $cache = true, $is_dir = false)
  358. {
  359. $extensions = array(
  360. $extension_name => $extension_path,
  361. );
  362. $files = array();
  363. $file_list = $this->find_from_paths($extensions, $cache, $is_dir);
  364. foreach ($file_list as $file)
  365. {
  366. $files[$file['named_path']] = $file['ext_name'];
  367. }
  368. return $files;
  369. }
  370. /**
  371. * Finds all file system entries matching the configured options from
  372. * an array of paths
  373. *
  374. * @param array $extensions Array of extensions (name => full relative path)
  375. * @param bool $cache Whether the result should be cached
  376. * @param bool $is_dir Directories will be returned when true, only files
  377. * otherwise
  378. * @return array An array of paths to found items
  379. */
  380. public function find_from_paths($extensions, $cache = true, $is_dir = false)
  381. {
  382. $this->query['is_dir'] = $is_dir;
  383. $query = md5(serialize($this->query) . serialize($extensions));
  384. if (!defined('DEBUG') && $cache && isset($this->cached_queries[$query]))
  385. {
  386. return $this->cached_queries[$query];
  387. }
  388. $files = array();
  389. foreach ($extensions as $name => $path)
  390. {
  391. $ext_name = $name;
  392. if (!file_exists($path))
  393. {
  394. continue;
  395. }
  396. if ($name === '/')
  397. {
  398. $location = $this->query['core_path'];
  399. $name = '';
  400. $suffix = $this->query['core_suffix'];
  401. $prefix = $this->query['core_prefix'];
  402. $directory = $this->query['core_directory'];
  403. }
  404. else
  405. {
  406. $location = 'ext/';
  407. $name .= '/';
  408. $suffix = $this->query['extension_suffix'];
  409. $prefix = $this->query['extension_prefix'];
  410. $directory = $this->query['extension_directory'];
  411. }
  412. // match only first directory if leading slash is given
  413. if ($directory === '/')
  414. {
  415. $directory_pattern = '^' . preg_quote(DIRECTORY_SEPARATOR, '#');
  416. }
  417. else if ($directory && $directory[0] === '/')
  418. {
  419. if (!$is_dir)
  420. {
  421. $path .= substr($directory, 1);
  422. }
  423. $directory_pattern = '^' . preg_quote(str_replace('/', DIRECTORY_SEPARATOR, $directory) . DIRECTORY_SEPARATOR, '#');
  424. }
  425. else
  426. {
  427. $directory_pattern = preg_quote(DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $directory) . DIRECTORY_SEPARATOR, '#');
  428. }
  429. if ($is_dir)
  430. {
  431. $directory_pattern .= '$';
  432. }
  433. $directory_pattern = '#' . $directory_pattern . '#';
  434. if (is_dir($path))
  435. {
  436. $iterator = new \RecursiveIteratorIterator(
  437. new \phpbb\recursive_dot_prefix_filter_iterator(
  438. new \RecursiveDirectoryIterator(
  439. $path,
  440. \FilesystemIterator::SKIP_DOTS
  441. )
  442. ),
  443. \RecursiveIteratorIterator::SELF_FIRST
  444. );
  445. foreach ($iterator as $file_info)
  446. {
  447. $filename = $file_info->getFilename();
  448. if ($file_info->isDir() == $is_dir)
  449. {
  450. if ($is_dir)
  451. {
  452. $relative_path = $iterator->getInnerIterator()->getSubPath() . DIRECTORY_SEPARATOR . basename($filename) . DIRECTORY_SEPARATOR;
  453. if ($relative_path[0] !== DIRECTORY_SEPARATOR)
  454. {
  455. $relative_path = DIRECTORY_SEPARATOR . $relative_path;
  456. }
  457. }
  458. else
  459. {
  460. $relative_path = $iterator->getInnerIterator()->getSubPathname();
  461. if ($directory && $directory[0] === '/')
  462. {
  463. $relative_path = str_replace('/', DIRECTORY_SEPARATOR, $directory) . DIRECTORY_SEPARATOR . $relative_path;
  464. }
  465. else
  466. {
  467. $relative_path = DIRECTORY_SEPARATOR . $relative_path;
  468. }
  469. }
  470. if ((!$suffix || substr($relative_path, -strlen($suffix)) === $suffix) &&
  471. (!$prefix || substr($filename, 0, strlen($prefix)) === $prefix) &&
  472. (!$directory || preg_match($directory_pattern, $relative_path)))
  473. {
  474. $files[] = array(
  475. 'named_path' => str_replace(DIRECTORY_SEPARATOR, '/', $location . $name . substr($relative_path, 1)),
  476. 'ext_name' => $ext_name,
  477. 'path' => str_replace(array(DIRECTORY_SEPARATOR, $this->phpbb_root_path), array('/', ''), $file_info->getPath()) . '/',
  478. 'filename' => $filename,
  479. );
  480. }
  481. }
  482. }
  483. }
  484. }
  485. if ($cache && $this->cache)
  486. {
  487. $this->cached_queries[$query] = $files;
  488. $this->cache->put($this->cache_name, $this->cached_queries);
  489. }
  490. return $files;
  491. }
  492. }