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

/sally/core/lib/Scaffold/libraries/Scaffold/Scaffold.php

https://bitbucket.org/SallyCMS/0.6
PHP | 978 lines | 454 code | 123 blank | 401 comment | 55 complexity | 617371cfe45004aefa415f3828eab98b MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * CSScaffold
  4. *
  5. * CSScaffold is a CSS compiler and preprocessor that allows you to extend
  6. * the CSS language easily. You can add your own properities, rules and at-rules
  7. * and abstract the language as much as you want.
  8. *
  9. * Requires PHP 5.1.2
  10. * Tested on PHP 5.3.0
  11. *
  12. * @package CSScaffold
  13. * @author Anthony Short <anthonyshort@me.com>
  14. * @copyright 2009 Anthony Short. All rights reserved.
  15. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  16. * @link https://github.com/anthonyshort/csscaffold/master
  17. */
  18. class Scaffold extends Scaffold_Utils
  19. {
  20. const VERSION = '2.0.0';
  21. /**
  22. * The configuration for Scaffold and all of it's modules.
  23. * The config for Scaffold itself should just be inside the
  24. * config array, and module configs should be inside an array
  25. * with the key as the name of the module.
  26. *
  27. * @var array
  28. */
  29. public static $config;
  30. /**
  31. * CSS object for each processing phase. As Scaffold loops
  32. * through the files, it creates an new CSS object in this member
  33. * variable. It is through this variable that modules can access
  34. * the current CSS string being processed
  35. *
  36. * @var object
  37. */
  38. public static $css;
  39. /**
  40. * The level of logged message to be thrown as an error. Setting this
  41. * to 0 will mean only error-level messages are thrown. However, setting
  42. * it to 1 will throw warnings as errors and halt the process.
  43. *
  44. * @var int
  45. */
  46. private static $error_threshold;
  47. /**
  48. * The final, combined output of the CSS.
  49. *
  50. * @var Mixed
  51. */
  52. public static $output = null;
  53. /**
  54. * Include paths
  55. *
  56. * These are used for finding files on the system. Rather than
  57. * using PHP's built-in include paths, we just store the paths
  58. * in this array and use the find_file function to locate it.
  59. *
  60. * @var array
  61. */
  62. private static $include_paths = array();
  63. /**
  64. * Any files that are found with find_file are stored here so that
  65. * any further requestes for the files are just given the path
  66. * from this array, rather than searching for the file again.
  67. *
  68. * @var array
  69. */
  70. private static $find_file_paths;
  71. /**
  72. * List of included modules. They are stored with the module name
  73. * as the key, and the path to the module as the value. However,
  74. * calling the modules method will return just the names of the modules.
  75. *
  76. * @var array
  77. */
  78. public static $modules;
  79. /**
  80. * Flags allow Scaffold to create cache variants based on particular
  81. * parameters. This could be the browser, the time etc.
  82. *
  83. * @var array
  84. */
  85. public static $flags = array();
  86. /**
  87. * Options are used by modules to check if the user wants a paricular
  88. * action to occur. They don't affect the cache, like flags do, so
  89. * modules shouldn't modify the CSS string based on options. They
  90. * can be used to modify the output or to perform some secondary
  91. * action, like validating the CSS.
  92. *
  93. * @var array
  94. */
  95. public static $options;
  96. /**
  97. * If Scaffold encounted an error. You can check this variable to
  98. * see if there were any errors when in_production is set to true.
  99. *
  100. * @var boolean
  101. */
  102. public static $has_error = false;
  103. /**
  104. * Stores the headers for sending to the browser.
  105. *
  106. * @var array
  107. */
  108. private static $headers;
  109. /**
  110. * Parse the CSS. This takes an array of files, options and configs
  111. * and parses the CSS, outputing the processed CSS string.
  112. *
  113. * @param array List of files
  114. * @param array Configuration options
  115. * @param string Options
  116. * @param boolean Return the CSS rather than displaying it
  117. * @return string The processed css file as a string
  118. */
  119. public static function parse( $files, $config, $options = array(), $display = false )
  120. {
  121. # Benchmark will do the entire run from start to finish
  122. Scaffold_Benchmark::start('system');
  123. try
  124. {
  125. # Setup the cache and other variables/constants
  126. Scaffold::setup($config);
  127. self::$options = $options;
  128. $css = false;
  129. # Time it takes to get the flags
  130. Scaffold_Benchmark::start('system.flags');
  131. # Get the flags from each of the loaded modules.
  132. $flags = (self::$flags === false) ? array() : self::flags();
  133. # Time it takes to get the flags
  134. Scaffold_Benchmark::stop('system.flags');
  135. # The final, combined CSS file in the cache
  136. $combined = md5(serialize(array($files,$flags))) . '.css';
  137. /**
  138. * Check if we should use the combined cache right now and skip unneeded processing
  139. */
  140. if(SCAFFOLD_PRODUCTION === true AND Scaffold_Cache::exists($combined) AND Scaffold_Cache::is_fresh($combined))
  141. {
  142. Scaffold::$output = Scaffold_Cache::open($combined);
  143. }
  144. if(Scaffold::$output === null)
  145. {
  146. # We're processing the files
  147. Scaffold_Benchmark::start('system.check_files');
  148. foreach($files as $file)
  149. {
  150. # The time to process a single file
  151. Scaffold_Benchmark::start('system.file.' . basename($file));
  152. # Make sure this file is allowed
  153. if(substr($file, 0, 4) == "http" OR substr($file, -4, 4) != ".css")
  154. {
  155. Scaffold::error('Scaffold cannot the requested file - ' . $file);
  156. }
  157. /**
  158. * If there are flags, we'll include them in the filename
  159. */
  160. if(!empty($flags))
  161. {
  162. $cached_file = dirname($file) . DIRECTORY_SEPARATOR . pathinfo($file, PATHINFO_FILENAME) . '_' . implode('_', $flags) . '.css';
  163. }
  164. else
  165. {
  166. $cached_file = $file;
  167. }
  168. $request = Scaffold::find_file($file, false, true);
  169. // webvariants-Patch: Entferne alle fĂźhrenden "../", um nicht aus dem Cache-Verzeichnis zu fliegen.
  170. $cached_file = preg_replace('#(\.\.[/\\\\])+#', '', $cached_file);
  171. /**
  172. * While not in production, we want to to always recache, so we'll fake the time
  173. */
  174. $modified = (SCAFFOLD_PRODUCTION) ? Scaffold_Cache::modified($cached_file) : 0;
  175. /**
  176. * If the CSS file has been changed, or the cached version doesn't exist
  177. */
  178. if(!Scaffold_Cache::exists($cached_file) OR $modified < filemtime($request))
  179. {
  180. Scaffold_Cache::write( Scaffold::process($request), $cached_file );
  181. Scaffold_Cache::remove($combined);
  182. }
  183. $css .= Scaffold_Cache::open($cached_file);
  184. # The time it's taken to process this file
  185. Scaffold_Benchmark::stop('system.file.' . basename($file));
  186. }
  187. Scaffold::$output = $css;
  188. /**
  189. * If any of the files have changed we need to recache the combined
  190. */
  191. if(!Scaffold_Cache::exists($combined))
  192. {
  193. Scaffold_Cache::write(self::$output,$combined);
  194. }
  195. # The time it takes to process the files
  196. Scaffold_Benchmark::stop('system.check_files');
  197. /**
  198. * Hook to modify what is sent to the browser
  199. */
  200. if(SCAFFOLD_PRODUCTION === false) Scaffold::hook('display');
  201. }
  202. /**
  203. * Set the HTTP headers for the request. Scaffold will set
  204. * all the headers required to score an A grade on YSlow. This
  205. * means your CSS will be sent as quickly as possible to the browser.
  206. */
  207. $length = strlen(Scaffold::$output);
  208. $modified = Scaffold_Cache::modified($combined);
  209. $lifetime = (SCAFFOLD_PRODUCTION === true) ? $config['cache_lifetime'] : 0;
  210. Scaffold::set_headers($modified,$lifetime,$length);
  211. /**
  212. * If the user wants us to render the CSS to the browser, we run this event.
  213. * This will send the headers and output the processed CSS.
  214. */
  215. if($display === true)
  216. {
  217. Scaffold::render(Scaffold::$output,$config['gzip_compression']);
  218. }
  219. # Benchmark will do the entire run from start to finish
  220. Scaffold_Benchmark::stop('system');
  221. }
  222. /**
  223. * If any errors were encountered
  224. */
  225. catch( Exception $e )
  226. {
  227. /**
  228. * The message returned by the error
  229. */
  230. $message = $e->getMessage();
  231. /**
  232. * Load in the error view
  233. */
  234. if(SCAFFOLD_PRODUCTION === false && $display === true)
  235. {
  236. Scaffold::send_headers();
  237. require Scaffold::find_file('scaffold_error.php','views');
  238. }
  239. }
  240. # Log the final execution time
  241. #$benchmark = Scaffold_Benchmark::get('system');
  242. #Scaffold_Log::log('Total Execution - ' . $benchmark['time']);
  243. # Save the logs and exit
  244. Scaffold_Event::run('system.shutdown');
  245. return self::$output;
  246. }
  247. /**
  248. * Sets the initial variables, checks if we need to process the css
  249. * and then sends whichever file to the browser.
  250. *
  251. * @return void
  252. */
  253. public static function setup($config)
  254. {
  255. /**
  256. * Choose whether to show or hide errors
  257. */
  258. if(SCAFFOLD_PRODUCTION === false)
  259. {
  260. ini_set('display_errors', true);
  261. error_reporting(E_ALL & ~E_STRICT);
  262. }
  263. else
  264. {
  265. ini_set('display_errors', false);
  266. error_reporting(0);
  267. }
  268. /**
  269. * Define contstants for system paths for easier access.
  270. */
  271. if(!defined('SCAFFOLD_SYSPATH') && !defined('SCAFFOLD_DOCROOT'))
  272. {
  273. define('SCAFFOLD_SYSPATH', self::fix_path($config['system']));
  274. define('SCAFFOLD_DOCROOT', $config['document_root']);
  275. define('SCAFFOLD_URLPATH', str_replace(SCAFFOLD_DOCROOT, '',SCAFFOLD_SYSPATH));
  276. }
  277. /**
  278. * Add include paths for finding files
  279. */
  280. Scaffold::add_include_path(SCAFFOLD_SYSPATH,SCAFFOLD_DOCROOT);
  281. /**
  282. * Tell the cache where to save files and for how long to keep them for
  283. */
  284. Scaffold_Cache::setup( Scaffold::fix_path($config['cache']), $config['cache_lifetime'] );
  285. /**
  286. * The level at which logged messages will halt processing and be thrown as errors
  287. */
  288. self::$error_threshold = $config['error_threshold'];
  289. /**
  290. * Disabling flags allows for quicker processing
  291. */
  292. if($config['disable_flags'] === true)
  293. {
  294. self::$flags = false;
  295. }
  296. /**
  297. * Tell the log where to save it's files. Set it to automatically save the log on exit
  298. */
  299. if($config['enable_log'] === true)
  300. {
  301. Scaffold_Log::log_directory(SCAFFOLD_SYSPATH.'logs');
  302. Scaffold_Event::add('system.shutdown', array('Scaffold_Log','save'));
  303. }
  304. /**
  305. * Load each of the modules
  306. */
  307. foreach(Scaffold::list_files(SCAFFOLD_SYSPATH.'modules') as $module)
  308. {
  309. $name = basename($module);
  310. $module_config = SCAFFOLD_SYSPATH.'config/' . $name . '.php';
  311. if(file_exists($module_config))
  312. {
  313. unset($config);
  314. include $module_config;
  315. self::$config[$name] = $config;
  316. }
  317. self::add_include_path($module);
  318. if( $controller = Scaffold::find_file($name.'.php', SCAFFOLD_SYSPATH.'modules'.DIRECTORY_SEPARATOR.$name, true) )
  319. {
  320. require_once($controller);
  321. self::$modules[$name] = new $name;
  322. }
  323. }
  324. /**
  325. * Module Initialization Hook
  326. * This hook allows modules to load libraries and create events
  327. * before any processing is done at all.
  328. */
  329. self::hook('initialize');
  330. /**
  331. * Create the shutdown event
  332. */
  333. Scaffold_Event::add('system.shutdown', array('Scaffold','shutdown'));
  334. }
  335. /**
  336. * Parses the single CSS file
  337. *
  338. * @param $file The file to the parsed
  339. * @return $css string
  340. */
  341. public static function process($file)
  342. {
  343. /**
  344. * This allows Scaffold to find files in the directory of the CSS file
  345. */
  346. Scaffold::add_include_path($file);
  347. /**
  348. * We create a new CSS object for each file. This object
  349. * allows modules to easily manipulate the CSS string.
  350. * Note:Inline comments are stripped when the file is loaded.
  351. */
  352. Scaffold::$css = new Scaffold_CSS($file);
  353. /**
  354. * Import Process Hook
  355. * This hook is for doing any type of importing/including in the CSS
  356. */
  357. self::hook('import_process');
  358. /**
  359. * Pre-process Hook
  360. * There shouldn't be any heavy processing of the string here. Just pulling
  361. * out @ rules, constants and other bits and pieces.
  362. */
  363. self::hook('pre_process');
  364. /**
  365. * Process Hook
  366. * The main process. None of the processes should conflict in any of the modules
  367. */
  368. self::hook('process');
  369. /**
  370. * Post-process Hook
  371. * After any non-standard CSS has been processed and removed. This is where
  372. * the nested selectors are parsed. It's not perfectly standard CSS yet, but
  373. * there shouldn't be an Scaffold syntax left at all.
  374. */
  375. self::hook('post_process');
  376. /**
  377. * Formatting Hook
  378. * Stylise the string, rewriting urls and other parts of the string. No heavy processing.
  379. */
  380. self::hook('formatting_process');
  381. /**
  382. * Clean up the include paths
  383. */
  384. self::remove_include_path($file);
  385. return Scaffold::$css->string;
  386. }
  387. /**
  388. * Sets the HTTP headers for a particular file
  389. *
  390. * @param $param
  391. * @return return type
  392. */
  393. private static function set_headers($modified,$lifetime,$length)
  394. {
  395. self::$headers = array();
  396. /**
  397. * Set the expires headers
  398. */
  399. $now = $expires = time();
  400. // Set the expiration timestamp
  401. $expires += $lifetime;
  402. Scaffold::header('Last-Modified',gmdate('D, d M Y H:i:s T', $now));
  403. Scaffold::header('Expires',gmdate('D, d M Y H:i:s T', $expires));
  404. Scaffold::header('Cache-Control','max-age='.$lifetime);
  405. /**
  406. * Further caching headers
  407. */
  408. Scaffold::header('ETag', md5(serialize(array($length,$modified))) );
  409. Scaffold::header('Content-Type','text/css');
  410. /**
  411. * Content Length
  412. * Sending Content-Length in CGI can result in unexpected behavior
  413. */
  414. if(stripos(PHP_SAPI, 'cgi') === FALSE)
  415. {
  416. Scaffold::header('Content-Length',$length);
  417. }
  418. /**
  419. * Set the expiration headers
  420. */
  421. if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']))
  422. {
  423. if (($strpos = strpos($_SERVER['HTTP_IF_MODIFIED_SINCE'], ';')) !== FALSE)
  424. {
  425. // IE6 and perhaps other IE versions send length too, compensate here
  426. $mod_time = substr($_SERVER['HTTP_IF_MODIFIED_SINCE'], 0, $strpos);
  427. }
  428. else
  429. {
  430. $mod_time = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
  431. }
  432. $mod_time = strtotime($mod_time);
  433. $mod_time_diff = $mod_time + $lifetime - time();
  434. if ($mod_time_diff > 0)
  435. {
  436. // Re-send headers
  437. Scaffold::header('Last-Modified', gmdate('D, d M Y H:i:s T', $mod_time) );
  438. Scaffold::header('Expires', gmdate('D, d M Y H:i:s T', time() + $mod_time_diff) );
  439. Scaffold::header('Cache-Control', 'max-age='.$mod_time_diff);
  440. Scaffold::header('_status',304);
  441. // Prevent any output
  442. Scaffold::$output = '';
  443. }
  444. }
  445. }
  446. /**
  447. * Allows modules to hook into the processing at any point
  448. *
  449. * @param $method The method to check for in each of the modules
  450. * @return boolean
  451. */
  452. private static function hook($method)
  453. {
  454. foreach(self::$modules as $module_name => $module)
  455. {
  456. if(method_exists($module,$method))
  457. {
  458. call_user_func(array($module_name,$method));
  459. }
  460. }
  461. }
  462. /**
  463. * Renders the CSS
  464. *
  465. * @param $output What to display
  466. * @return void
  467. */
  468. public static function render($output,$level = false)
  469. {
  470. if ($level AND ini_get('output_handler') !== 'ob_gzhandler' AND (int) ini_get('zlib.output_compression') === 0)
  471. {
  472. if ($level < 1 OR $level > 9)
  473. {
  474. # Normalize the level to be an integer between 1 and 9. This
  475. # step must be done to prevent gzencode from triggering an error
  476. $level = max(1, min($level, 9));
  477. }
  478. if (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
  479. {
  480. $compress = 'gzip';
  481. }
  482. elseif (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') !== FALSE)
  483. {
  484. $compress = 'deflate';
  485. }
  486. }
  487. if (isset($compress) AND $level > 0)
  488. {
  489. switch ($compress)
  490. {
  491. case 'gzip':
  492. # Compress output using gzip
  493. $output = gzencode($output, $level);
  494. break;
  495. case 'deflate':
  496. # Compress output using zlib (HTTP deflate)
  497. $output = gzdeflate($output, $level);
  498. break;
  499. }
  500. # This header must be sent with compressed content to prevent browser caches from breaking
  501. Scaffold::header('Vary','Accept-Encoding');
  502. # Send the content encoding header
  503. Scaffold::header('Content-Encoding',$compress);
  504. }
  505. # Send the headers
  506. Scaffold::send_headers();
  507. echo $output;
  508. exit;
  509. }
  510. /**
  511. * Sends all of the stored headers to the browser
  512. *
  513. * @return void
  514. */
  515. private static function send_headers()
  516. {
  517. if(!headers_sent())
  518. {
  519. self::$headers = array_unique(self::$headers);
  520. foreach(self::$headers as $name => $value)
  521. {
  522. if($name != '_status')
  523. {
  524. header($name . ':' . $value);
  525. }
  526. else
  527. {
  528. if($value === 304)
  529. {
  530. header('Status: 304 Not Modified', TRUE, 304);
  531. }
  532. elseif($value === 500)
  533. {
  534. header('HTTP/1.1 500 Internal Server Error');
  535. }
  536. }
  537. }
  538. }
  539. }
  540. /**
  541. * Prepares the final output and cleans up
  542. *
  543. * @return void
  544. */
  545. public static function shutdown()
  546. {
  547. return self::$output = array(
  548. 'status' => self::$has_error,
  549. 'content' => self::$output,
  550. 'headers' => self::$headers,
  551. 'flags' => self::$flags,
  552. 'log' => Scaffold_Log::$log,
  553. );
  554. }
  555. /**
  556. * Displays an error and halts the parsing.
  557. *
  558. * @param $message
  559. * @return void
  560. */
  561. public static function error($message)
  562. {
  563. /**
  564. * Log the message before we throw the error
  565. */
  566. Scaffold_Log::log($message,0);
  567. /**
  568. * Useful variable to let other objects know there was an error with the parsing
  569. */
  570. self::$has_error = true;
  571. /**
  572. * Add the error header. If the CSS is rendered, this will be sent
  573. */
  574. self::header('_status',500);
  575. /**
  576. * This will be caught in the parse method
  577. */
  578. throw new Exception($message);
  579. }
  580. /**
  581. * Uses the logging class to log a message
  582. *
  583. * @author your name
  584. * @param $message
  585. * @return void
  586. */
  587. public static function log($message,$level)
  588. {
  589. if ($level <= self::$error_threshold)
  590. {
  591. self::error($message);
  592. }
  593. else
  594. {
  595. Scaffold_Log::log($message,$level);
  596. }
  597. }
  598. /**
  599. * Adds a new HTTP header for sending later.
  600. *
  601. * @author your name
  602. * @param $name
  603. * @param $value
  604. * @return boolean
  605. */
  606. private static function header($name,$value)
  607. {
  608. return self::$headers[$name] = $value;
  609. }
  610. /**
  611. * Sets a cache flag
  612. *
  613. * @param $name The name of the flag to set
  614. * @return void
  615. */
  616. public static function flag_set($name)
  617. {
  618. return self::$flags[] = $name;
  619. }
  620. /**
  621. * Checks if a flag is set
  622. *
  623. * @param $flag
  624. * @return boolean
  625. */
  626. public static function flag($flag)
  627. {
  628. return (in_array($flag,self::$flags)) ? true : false;
  629. }
  630. /**
  631. * Gets the flags from each of the modules
  632. *
  633. * @param $param
  634. * @return $array The array of flags
  635. */
  636. public static function flags()
  637. {
  638. if(!empty(self::$flags))
  639. return self::$flags;
  640. self::hook('flag');
  641. return (isset(self::$flags)) ? self::$flags : false;
  642. }
  643. /**
  644. * Get all include paths.
  645. *
  646. * @return array
  647. */
  648. public static function include_paths()
  649. {
  650. return self::$include_paths;
  651. }
  652. /**
  653. * Adds a path to the include paths list
  654. *
  655. * @param $path The server path to add
  656. * @return void
  657. */
  658. public static function add_include_path($path)
  659. {
  660. if(func_num_args() > 1)
  661. {
  662. $args = func_get_args();
  663. foreach($args as $inc)
  664. self::add_include_path($inc);
  665. }
  666. if(is_file($path))
  667. {
  668. $path = dirname($path);
  669. }
  670. if(!in_array($path,self::$include_paths))
  671. {
  672. self::$include_paths[] = Scaffold_Utils::fix_path($path);
  673. }
  674. }
  675. /**
  676. * Removes an include path
  677. *
  678. * @param $path The server path to remove
  679. * @return void
  680. */
  681. public static function remove_include_path($path)
  682. {
  683. if(in_array($path, self::$include_paths))
  684. {
  685. unset(self::$include_paths[array_search($path, self::$include_paths)]);
  686. }
  687. }
  688. /**
  689. * Checks to see if an option is set
  690. *
  691. * @param $name
  692. * @return boolean
  693. */
  694. public static function option($name)
  695. {
  696. return isset(self::$options[$name]);
  697. }
  698. /**
  699. * Loads a view file
  700. *
  701. * @param string The name of the view
  702. * @param boolean Render the view immediately
  703. * @param boolean Return the contents of the view
  704. * @return void If the view is rendered
  705. * @return string The contents of the view
  706. */
  707. public static function view( $view, $render = false )
  708. {
  709. # Find the view file
  710. $view = self::find_file($view . '.php', 'views', true);
  711. # Display the view
  712. if ($render === true)
  713. {
  714. include $view;
  715. return;
  716. }
  717. # Return the view
  718. else
  719. {
  720. ob_start();
  721. echo file_get_contents($view);
  722. return ob_get_clean();
  723. }
  724. }
  725. /**
  726. * Find a resource file in a given directory. Files will be located according
  727. * to the order of the include paths.
  728. *
  729. * @throws error if file is required and not found
  730. * @param string filename to look for
  731. * @param string directory to search in
  732. * @param boolean file required
  733. * @return string if the file is found
  734. * @return FALSE if the file is not found
  735. */
  736. public static function find_file($filename, $directory = '', $required = FALSE)
  737. {
  738. # Search path
  739. $search = $directory.DIRECTORY_SEPARATOR.$filename;
  740. if(file_exists($filename))
  741. {
  742. return self::$find_file_paths[$filename] = $filename;
  743. }
  744. elseif(file_exists($search))
  745. {
  746. return self::$find_file_paths[$search] = realpath($search);
  747. }
  748. if (isset(self::$find_file_paths[$search]))
  749. return self::$find_file_paths[$search];
  750. # Load include paths
  751. $paths = self::include_paths();
  752. # Nothing found, yet
  753. $found = NULL;
  754. if(in_array($directory, $paths))
  755. {
  756. if (is_file($directory.$filename))
  757. {
  758. # A matching file has been found
  759. $found = $search;
  760. }
  761. }
  762. else
  763. {
  764. foreach ($paths as $path)
  765. {
  766. if (is_file($path.$search))
  767. {
  768. # A matching file has been found
  769. $found = realpath($path.$search);
  770. # Stop searching
  771. break;
  772. }
  773. elseif (is_file(realpath($path.$search)))
  774. {
  775. # A matching file has been found
  776. $found = realpath($path.$search);
  777. # Stop searching
  778. break;
  779. }
  780. }
  781. }
  782. if ($found === NULL)
  783. {
  784. if ($required === TRUE)
  785. {
  786. # If the file is required, throw an exception
  787. self::error("Cannot find the file: " . str_replace($_SERVER['DOCUMENT_ROOT'], DIRECTORY_SEPARATOR, $search));
  788. }
  789. else
  790. {
  791. # Nothing was found, return FALSE
  792. $found = FALSE;
  793. }
  794. }
  795. return self::$find_file_paths[$search] = $found;
  796. }
  797. /**
  798. * Lists all files and directories in a resource path.
  799. *
  800. * @param string directory to search
  801. * @param boolean list all files to the maximum depth?
  802. * @param string full path to search (used for recursion, *never* set this manually)
  803. * @return array filenames and directories
  804. */
  805. public static function list_files($directory, $recursive = FALSE, $path = FALSE)
  806. {
  807. $files = array();
  808. if ($path === FALSE)
  809. {
  810. if(is_dir($directory))
  811. {
  812. $files = array_merge($files, self::list_files($directory, $recursive, $directory));
  813. }
  814. else
  815. {
  816. foreach (array_reverse(self::include_paths()) as $path)
  817. {
  818. $files = array_merge($files, self::list_files($directory, $recursive, $path.$directory));
  819. }
  820. }
  821. }
  822. else
  823. {
  824. $path = rtrim($path, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
  825. if (is_readable($path))
  826. {
  827. $items = (array) glob($path.'*');
  828. if ( ! empty($items))
  829. {
  830. foreach ($items as $index => $item)
  831. {
  832. $name = pathinfo($item, PATHINFO_BASENAME);
  833. if(substr($name, 0, 1) == '.' || substr($name, 0, 1) == '-')
  834. {
  835. continue;
  836. }
  837. $files[] = $item = str_replace('\\', DIRECTORY_SEPARATOR, $item);
  838. // Handle recursion
  839. if (is_dir($item) AND $recursive == TRUE)
  840. {
  841. // Filename should only be the basename
  842. $item = pathinfo($item, PATHINFO_BASENAME);
  843. // Append sub-directory search
  844. $files = array_merge($files, self::list_files($directory, TRUE, $path.$item));
  845. }
  846. }
  847. }
  848. }
  849. }
  850. return $files;
  851. }
  852. }