Core.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074
  1. <?php
  2. /**
  3. * Contains the most low-level helpers methods in KO7:
  4. *
  5. * - Environment initialization
  6. * - Locating files within the cascading filesystem
  7. * - Auto-loading and transparent extension of classes
  8. * - Variable and path debugging
  9. *
  10. * @package KO7
  11. * @category Base
  12. *
  13. * @copyright (c) 2007-2016 Kohana Team
  14. * @copyright (c) since 2016 Koseven Team
  15. * @license https://koseven.dev/LICENSE
  16. */
  17. class KO7_Core {
  18. // Release version and codename
  19. const VERSION = '3.3.9';
  20. const CODENAME = 'karlsruhe';
  21. // Common environment type constants for consistency and convenience
  22. const PRODUCTION = 10;
  23. const STAGING = 20;
  24. const TESTING = 30;
  25. const DEVELOPMENT = 40;
  26. // Format of cache files: header, cache name, and data
  27. const FILE_CACHE = ":header \n\n// :name\n\n:data\n";
  28. /**
  29. * @var string Current environment name
  30. */
  31. public static $environment = KO7::DEVELOPMENT;
  32. /**
  33. * Run KO7 with backwards compatibility module "kohana"
  34. * @var bool
  35. */
  36. public static $compatibility = TRUE;
  37. /**
  38. * @var boolean True if KO7 is running on windows
  39. */
  40. public static $is_windows = FALSE;
  41. /**
  42. * @var string
  43. */
  44. public static $content_type = 'text/html';
  45. /**
  46. * @var string character set of input and output
  47. */
  48. public static $charset = 'utf-8';
  49. /**
  50. * @var string the name of the server KO7 is hosted upon
  51. */
  52. public static $server_name = '';
  53. /**
  54. * @var array list of valid host names for this instance
  55. */
  56. public static $hostnames = [];
  57. /**
  58. * @var string base URL to the application
  59. */
  60. public static $base_url = '/';
  61. /**
  62. * @var string Application index file, added to links generated by KO7. Set by [KO7::init]
  63. */
  64. public static $index_file = 'index.php';
  65. /**
  66. * @var string Cache directory, used by [KO7::cache]. Set by [KO7::init]
  67. */
  68. public static $cache_dir;
  69. /**
  70. * @var integer Default lifetime for caching, in seconds, used by [KO7::cache]. Set by [KO7::init]
  71. */
  72. public static $cache_life = 60;
  73. /**
  74. * @var boolean Whether to use internal caching for [KO7::find_file], does not apply to [KO7::cache]. Set by [KO7::init]
  75. */
  76. public static $caching = FALSE;
  77. /**
  78. * @var boolean Whether to enable [profiling](KO7/profiling). Set by [KO7::init]
  79. */
  80. public static $profiling = TRUE;
  81. /**
  82. * @var boolean Enable KO7 catching and displaying PHP errors and exceptions. Set by [KO7::init]
  83. */
  84. public static $errors = TRUE;
  85. /**
  86. * @var array Types of errors to display at shutdown
  87. */
  88. public static $shutdown_errors = [E_PARSE, E_ERROR, E_USER_ERROR];
  89. /**
  90. * @var boolean set the X-Powered-By header
  91. */
  92. public static $expose = FALSE;
  93. /**
  94. * @var Log logging object
  95. */
  96. public static $log;
  97. /**
  98. * @var Config config object
  99. */
  100. public static $config;
  101. /**
  102. * @var boolean Has [KO7::init] been called?
  103. */
  104. protected static $_init = FALSE;
  105. /**
  106. * @var array Currently active modules
  107. */
  108. protected static $_modules = [];
  109. /**
  110. * @var array Include paths that are used to find files
  111. */
  112. protected static $_paths = [APPPATH, SYSPATH];
  113. /**
  114. * @var array File path cache, used when caching is true in [KO7::init]
  115. */
  116. protected static $_files = [];
  117. /**
  118. * @var boolean Has the file path cache changed during this execution? Used internally when when caching is true in [KO7::init]
  119. */
  120. protected static $_files_changed = FALSE;
  121. /**
  122. * Initializes the environment:
  123. *
  124. * - Determines the current environment
  125. * - Set global settings
  126. * - Sanitizes GET, POST, and COOKIE variables
  127. * - Converts GET, POST, and COOKIE variables to the global character set
  128. *
  129. * The following settings can be set:
  130. *
  131. * Type | Setting | Description | Default Value
  132. * ----------|------------|------------------------------------------------|---------------
  133. * `string` | base_url | The base URL for your application. This should be the *relative* path from your DOCROOT to your `index.php` file, in other words, if KO7 is in a subfolder, set this to the subfolder name, otherwise leave it as the default. **The leading slash is required**, trailing slash is optional. | `"/"`
  134. * `string` | index_file | The name of the [front controller](http://en.wikipedia.org/wiki/Front_Controller_pattern). This is used by KO7 to generate relative urls like [HTML::anchor()] and [URL::base()]. This is usually `index.php`. To [remove index.php from your urls](tutorials/clean-urls), set this to `FALSE`. | `"index.php"`
  135. * `string` | charset | Character set used for all input and output | `"utf-8"`
  136. * `string` | cache_dir | KO7's cache directory. Used by [KO7::cache] for simple internal caching, like [Fragments](ko7/fragments) and **\[caching database queries](this should link somewhere)**. This has nothing to do with the [Cache module](cache). | `APPPATH."cache"`
  137. * `integer` | cache_life | Lifetime, in seconds, of items cached by [KO7::cache] | `60`
  138. * `boolean` | errors | Should KO7 catch PHP errors and uncaught Exceptions and show the `error_view`. See [Error Handling](ko7/errors) for more info. <br /> <br /> Recommended setting: `TRUE` while developing, `FALSE` on production servers. | `TRUE`
  139. * `boolean` | profile | Whether to enable the [Profiler](ko7/profiling). <br /> <br />Recommended setting: `TRUE` while developing, `FALSE` on production servers. | `TRUE`
  140. * `boolean` | caching | Cache file locations to speed up [KO7::find_file]. This has nothing to do with [KO7::cache], [Fragments](ko7/fragments) or the [Cache module](cache). <br /> <br /> Recommended setting: `FALSE` while developing, `TRUE` on production servers. | `FALSE`
  141. * `boolean` | expose | Set the X-Powered-By header
  142. *
  143. * @throws KO7_Exception
  144. * @param array $settings Array of settings. See above.
  145. * @return void
  146. * @uses KO7::sanitize
  147. * @uses KO7::cache
  148. * @uses Profiler
  149. */
  150. public static function init(array $settings = NULL)
  151. {
  152. if (KO7::$_init)
  153. {
  154. // Do not allow execution twice
  155. return;
  156. }
  157. // KO7 is now initialized
  158. KO7::$_init = TRUE;
  159. if (isset($settings['profile']))
  160. {
  161. // Enable profiling
  162. KO7::$profiling = (bool) $settings['profile'];
  163. }
  164. // Start an output buffer
  165. ob_start();
  166. if (isset($settings['errors']))
  167. {
  168. // Enable error handling
  169. KO7::$errors = (bool) $settings['errors'];
  170. }
  171. if (KO7::$errors === TRUE)
  172. {
  173. // Enable KO7 exception handling, adds stack traces and error source.
  174. set_exception_handler(['KO7_Exception', 'handler']);
  175. // Enable KO7 error handling, converts all PHP errors to exceptions.
  176. set_error_handler(['KO7', 'error_handler']);
  177. }
  178. /**
  179. * Enable xdebug parameter collection in development mode to improve fatal stack traces.
  180. */
  181. if (KO7::$environment == KO7::DEVELOPMENT AND extension_loaded('xdebug'))
  182. {
  183. ini_set('xdebug.collect_params', 3);
  184. }
  185. // Enable the KO7 shutdown handler, which catches E_FATAL errors.
  186. register_shutdown_function(['KO7', 'shutdown_handler']);
  187. if (isset($settings['expose']))
  188. {
  189. KO7::$expose = (bool) $settings['expose'];
  190. }
  191. // Determine if we are running in a Windows environment
  192. KO7::$is_windows = (DIRECTORY_SEPARATOR === '\\');
  193. if (isset($settings['cache_dir']))
  194. {
  195. if ( ! is_dir($settings['cache_dir']))
  196. {
  197. try
  198. {
  199. // Create the cache directory
  200. mkdir($settings['cache_dir'], 0755, TRUE);
  201. // Set permissions (must be manually set to fix umask issues)
  202. chmod($settings['cache_dir'], 0755);
  203. }
  204. catch (Exception $e)
  205. {
  206. throw new KO7_Exception('Could not create cache directory :dir',
  207. [':dir' => Debug::path($settings['cache_dir'])]);
  208. }
  209. }
  210. // Set the cache directory path
  211. KO7::$cache_dir = realpath($settings['cache_dir']);
  212. }
  213. else
  214. {
  215. // Use the default cache directory
  216. KO7::$cache_dir = APPPATH.'cache';
  217. }
  218. if ( ! is_writable(KO7::$cache_dir))
  219. {
  220. throw new KO7_Exception('Directory :dir must be writable',
  221. [':dir' => Debug::path(KO7::$cache_dir)]);
  222. }
  223. if (isset($settings['cache_life']))
  224. {
  225. // Set the default cache lifetime
  226. KO7::$cache_life = (int) $settings['cache_life'];
  227. }
  228. if (isset($settings['caching']))
  229. {
  230. // Enable or disable internal caching
  231. KO7::$caching = (bool) $settings['caching'];
  232. }
  233. if (KO7::$caching === TRUE)
  234. {
  235. // Load the file path cache
  236. KO7::$_files = KO7::cache('KO7::find_file()');
  237. }
  238. if (isset($settings['charset']))
  239. {
  240. // Set the system character set
  241. KO7::$charset = strtolower($settings['charset']);
  242. }
  243. if (function_exists('mb_internal_encoding'))
  244. {
  245. // Set the MB extension encoding to the same character set
  246. mb_internal_encoding(KO7::$charset);
  247. }
  248. if (isset($settings['base_url']))
  249. {
  250. // Set the base URL
  251. KO7::$base_url = rtrim($settings['base_url'], '/').'/';
  252. }
  253. if (isset($settings['index_file']))
  254. {
  255. // Set the index file
  256. KO7::$index_file = trim($settings['index_file'], '/');
  257. }
  258. // Sanitize all request variables
  259. $_GET = KO7::sanitize($_GET);
  260. $_POST = KO7::sanitize($_POST);
  261. $_COOKIE = KO7::sanitize($_COOKIE);
  262. // Load the logger if one doesn't already exist
  263. if ( ! KO7::$log instanceof Log)
  264. {
  265. KO7::$log = Log::instance();
  266. }
  267. // Load the config if one doesn't already exist
  268. if ( ! KO7::$config instanceof Config)
  269. {
  270. KO7::$config = new Config;
  271. }
  272. }
  273. /**
  274. * Cleans up the environment:
  275. *
  276. * - Restore the previous error and exception handlers
  277. * - Destroy the KO7::$log and KO7::$config objects
  278. *
  279. * @return void
  280. */
  281. public static function deinit()
  282. {
  283. if (KO7::$_init)
  284. {
  285. // Removed the autoloader
  286. spl_autoload_unregister(['KO7', 'auto_load']);
  287. if (KO7::$errors)
  288. {
  289. // Go back to the previous error handler
  290. restore_error_handler();
  291. // Go back to the previous exception handler
  292. restore_exception_handler();
  293. }
  294. // Destroy objects created by init
  295. KO7::$log = KO7::$config = NULL;
  296. // Reset internal storage
  297. KO7::$_modules = KO7::$_files = [];
  298. KO7::$_paths = [APPPATH, SYSPATH];
  299. // Reset file cache status
  300. KO7::$_files_changed = FALSE;
  301. // KO7 is no longer initialized
  302. KO7::$_init = FALSE;
  303. }
  304. }
  305. /**
  306. * Recursively sanitizes an input variable:
  307. *
  308. * - Normalizes all newlines to LF
  309. *
  310. * @param mixed $value any variable
  311. * @return mixed sanitized variable
  312. */
  313. public static function sanitize($value)
  314. {
  315. if (is_array($value) OR is_object($value))
  316. {
  317. foreach ($value as $key => $val)
  318. {
  319. // Recursively clean each value
  320. $value[$key] = KO7::sanitize($val);
  321. }
  322. }
  323. elseif (is_string($value))
  324. {
  325. if (strpos($value, "\r") !== FALSE)
  326. {
  327. // Standardize newlines
  328. $value = str_replace(["\r\n", "\r"], "\n", $value);
  329. }
  330. }
  331. return $value;
  332. }
  333. /**
  334. * Provides auto-loading support of classes that follow KO7's [class
  335. * naming conventions](ko7/conventions#class-names-and-file-location).
  336. * See [Loading Classes](ko7/autoloading) for more information.
  337. *
  338. * // Loads classes/My/Class/Name.php
  339. * KO7::auto_load('My_Class_Name');
  340. *
  341. * or with a custom directory:
  342. *
  343. * // Loads vendor/My/Class/Name.php
  344. * KO7::auto_load('My_Class_Name', 'vendor');
  345. *
  346. * You should never have to call this function, as simply calling a class
  347. * will cause it to be called.
  348. *
  349. * This function must be enabled as an autoloader in the bootstrap:
  350. *
  351. * spl_autoload_register(array('KO7', 'auto_load'));
  352. *
  353. * @param string $class Class name
  354. * @param string $directory Directory to load from
  355. * @return boolean
  356. */
  357. public static function auto_load($class, $directory = 'classes')
  358. {
  359. // Transform the class name according to PSR-0
  360. $class = ltrim($class, '\\');
  361. $file = '';
  362. $namespace = '';
  363. if ($last_namespace_position = strripos($class, '\\'))
  364. {
  365. $namespace = substr($class, 0, $last_namespace_position);
  366. $class = substr($class, $last_namespace_position + 1);
  367. $file = str_replace('\\', DIRECTORY_SEPARATOR, $namespace).DIRECTORY_SEPARATOR;
  368. }
  369. $file .= str_replace('_', DIRECTORY_SEPARATOR, $class);
  370. if ($path = KO7::find_file($directory, $file))
  371. {
  372. // Load the class file
  373. require $path;
  374. // Class has been found
  375. return TRUE;
  376. }
  377. // Class is not in the filesystem
  378. return FALSE;
  379. }
  380. /**
  381. * Provides auto-loading support of classes that follow KO7's old class
  382. * naming conventions.
  383. *
  384. * This is included for compatibility purposes with older modules.
  385. *
  386. * @param string $class Class name
  387. * @param string $directory Directory to load from
  388. * @return boolean
  389. */
  390. public static function auto_load_lowercase($class, $directory = 'classes')
  391. {
  392. // Transform the class name into a path
  393. $file = str_replace('_', DIRECTORY_SEPARATOR, strtolower($class));
  394. if ($path = KO7::find_file($directory, $file))
  395. {
  396. // Load the class file
  397. require $path;
  398. // Class has been found
  399. return TRUE;
  400. }
  401. // Class is not in the filesystem
  402. return FALSE;
  403. }
  404. /**
  405. * Changes the currently enabled modules. Module paths may be relative
  406. * or absolute, but must point to a directory:
  407. *
  408. * KO7::modules(array('modules/foo', MODPATH.'bar'));
  409. *
  410. * @param array $modules list of module paths
  411. * @return array enabled modules
  412. */
  413. public static function modules(array $modules = NULL)
  414. {
  415. if ($modules === NULL)
  416. {
  417. // Not changing modules, just return the current set
  418. return KO7::$_modules;
  419. }
  420. // Start a new list of include paths, APPPATH first
  421. $paths = [APPPATH];
  422. foreach ($modules as $name => $path)
  423. {
  424. if (is_dir($path))
  425. {
  426. // Add the module to include paths
  427. $paths[] = $modules[$name] = realpath($path).DIRECTORY_SEPARATOR;
  428. }
  429. else
  430. {
  431. // This module is invalid, remove it
  432. throw new KO7_Exception('Attempted to load an invalid or missing module \':module\' at \':path\'', [
  433. ':module' => $name,
  434. ':path' => Debug::path($path),
  435. ]);
  436. }
  437. }
  438. // Finish the include paths by adding SYSPATH
  439. $paths[] = SYSPATH;
  440. // Set the new include paths
  441. KO7::$_paths = $paths;
  442. // Set the current module list
  443. KO7::$_modules = $modules;
  444. foreach (KO7::$_modules as $path)
  445. {
  446. $init = $path.'init'.EXT;
  447. if (is_file($init))
  448. {
  449. // Include the module initialization file once
  450. require_once $init;
  451. }
  452. }
  453. return KO7::$_modules;
  454. }
  455. /**
  456. * Returns the the currently active include paths, including the
  457. * application, system, and each module's path.
  458. *
  459. * @return array
  460. */
  461. public static function include_paths()
  462. {
  463. return KO7::$_paths;
  464. }
  465. /**
  466. * Searches for a file in the [Cascading Filesystem](ko7/files), and
  467. * returns the path to the file that has the highest precedence, so that it
  468. * can be included.
  469. *
  470. * When searching the "config", "messages", or "i18n" directories, or when
  471. * the `$array` flag is set to true, an array of all the files that match
  472. * that path in the [Cascading Filesystem](ko7/files) will be returned.
  473. * These files will return arrays which must be merged together.
  474. *
  475. * If no extension is given, the default extension (`EXT` set in
  476. * `index.php`) will be used.
  477. *
  478. * // Returns an absolute path to views/template.php
  479. * KO7::find_file('views', 'template');
  480. *
  481. * // Returns an absolute path to media/css/style.css
  482. * KO7::find_file('media', 'css/style', 'css');
  483. *
  484. * // Returns an array of all the "mimes" configuration files
  485. * KO7::find_file('config', 'mimes');
  486. *
  487. * @param string $dir directory name (views, i18n, classes, extensions, etc.)
  488. * @param string $file filename with subdirectory
  489. * @param string $ext extension to search for
  490. * @param boolean $array return an array of files?
  491. * @return array a list of files when $array is TRUE
  492. * @return string single file path
  493. */
  494. public static function find_file($dir, $file, $ext = NULL, $array = FALSE)
  495. {
  496. if ($ext === NULL)
  497. {
  498. // Use the default extension
  499. $ext = EXT;
  500. }
  501. elseif ($ext)
  502. {
  503. // Prefix the extension with a period
  504. $ext = ".{$ext}";
  505. }
  506. else
  507. {
  508. // Use no extension
  509. $ext = '';
  510. }
  511. // Create a partial path of the filename
  512. $path = $dir.DIRECTORY_SEPARATOR.$file.$ext;
  513. if (KO7::$caching === TRUE AND isset(KO7::$_files[$path.($array ? '_array' : '_path')]))
  514. {
  515. // This path has been cached
  516. return KO7::$_files[$path.($array ? '_array' : '_path')];
  517. }
  518. if (KO7::$profiling === TRUE AND class_exists('Profiler', FALSE))
  519. {
  520. // Start a new benchmark
  521. $benchmark = Profiler::start('KO7', __FUNCTION__);
  522. }
  523. if ($array OR $dir === 'config' OR $dir === 'i18n' OR $dir === 'messages')
  524. {
  525. // Include paths must be searched in reverse
  526. $paths = array_reverse(KO7::$_paths);
  527. // Array of files that have been found
  528. $found = [];
  529. foreach ($paths as $dir)
  530. {
  531. if (is_file($dir.$path))
  532. {
  533. // This path has a file, add it to the list
  534. $found[] = $dir.$path;
  535. }
  536. }
  537. }
  538. else
  539. {
  540. // The file has not been found yet
  541. $found = FALSE;
  542. /**
  543. * NOTE: This check is only needed if we are PRE-Initialization and in Compatibility mode
  544. * Only performing before Initialization makes sure that no `strpos, etc. operations get called
  545. * without being necessary
  546. */
  547. if (!KO7::$_init && KO7::$compatibility && strpos($path, 'kohana') !== FALSE)
  548. {
  549. $found = MODPATH.'kohana'.DIRECTORY_SEPARATOR.$path;
  550. if (!is_file($found)) {
  551. $found = FALSE;
  552. }
  553. }
  554. // If still not found. Search through $_paths
  555. if (! $found)
  556. {
  557. foreach (KO7::$_paths as $dir)
  558. {
  559. if (is_file($dir.$path))
  560. {
  561. // A path has been found
  562. $found = $dir.$path;
  563. // Stop searching
  564. break;
  565. }
  566. }
  567. }
  568. }
  569. if (KO7::$caching === TRUE)
  570. {
  571. // Add the path to the cache
  572. KO7::$_files[$path.($array ? '_array' : '_path')] = $found;
  573. // Files have been changed
  574. KO7::$_files_changed = TRUE;
  575. }
  576. if (isset($benchmark))
  577. {
  578. // Stop the benchmark
  579. Profiler::stop($benchmark);
  580. }
  581. return $found;
  582. }
  583. /**
  584. * Recursively finds all of the files in the specified directory at any
  585. * location in the [Cascading Filesystem](ko7/files), and returns an
  586. * array of all the files found, sorted alphabetically.
  587. *
  588. * // Find all view files.
  589. * $views = KO7::list_files('views');
  590. *
  591. * @param string $directory directory name
  592. * @param array $paths list of paths to search
  593. * @param string|array $ext only list files with this extension
  594. * @param bool $sort sort alphabetically
  595. *
  596. * @return array
  597. */
  598. public static function list_files($directory = NULL, array $paths = NULL, $ext = NULL, $sort = NULL)
  599. {
  600. if ($directory !== NULL)
  601. {
  602. // Add the directory separator
  603. $directory .= DIRECTORY_SEPARATOR;
  604. }
  605. if ($paths === NULL)
  606. {
  607. // Use the default paths
  608. $paths = KO7::$_paths;
  609. }
  610. if (is_string($ext))
  611. {
  612. // convert string extension to array
  613. $ext = [$ext];
  614. }
  615. if ($sort === NULL)
  616. {
  617. // sort results by default
  618. $sort = TRUE;
  619. }
  620. // Create an array for the files
  621. $found = [];
  622. foreach ($paths as $path)
  623. {
  624. if (is_dir($path.$directory))
  625. {
  626. // Create a new directory iterator
  627. $dir = new DirectoryIterator($path.$directory);
  628. foreach ($dir as $file)
  629. {
  630. // Get the file name
  631. $filename = $file->getFilename();
  632. if ($filename[0] === '.' OR $filename[strlen($filename)-1] === '~')
  633. {
  634. // Skip all hidden files and UNIX backup files
  635. continue;
  636. }
  637. // Relative filename is the array key
  638. $key = $directory.$filename;
  639. if ($file->isDir())
  640. {
  641. if ($sub_dir = KO7::list_files($key, $paths, $ext, $sort))
  642. {
  643. if (isset($found[$key]))
  644. {
  645. // Append the sub-directory list
  646. $found[$key] += $sub_dir;
  647. }
  648. else
  649. {
  650. // Create a new sub-directory list
  651. $found[$key] = $sub_dir;
  652. }
  653. }
  654. }
  655. elseif ($ext === NULL || in_array('.'.$file->getExtension(), $ext, TRUE))
  656. {
  657. if ( ! isset($found[$key]))
  658. {
  659. // Add new files to the list
  660. $found[$key] = realpath($file->getPathname());
  661. }
  662. }
  663. }
  664. }
  665. }
  666. if ($sort)
  667. {
  668. // Sort the results alphabetically
  669. ksort($found);
  670. }
  671. return $found;
  672. }
  673. /**
  674. * Loads a file within a totally empty scope and returns the output:
  675. *
  676. * $foo = KO7::load('foo.php');
  677. *
  678. * @param string $file
  679. * @return mixed
  680. */
  681. public static function load($file)
  682. {
  683. return include $file;
  684. }
  685. /**
  686. * Cache variables using current cache module if enabled, if not uses KO7::file_cache
  687. *
  688. * // Set the "foo" cache
  689. * KO7::cache('foo', 'hello, world');
  690. *
  691. * // Get the "foo" cache
  692. * $foo = KO7::cache('foo');
  693. *
  694. * @throws KO7_Exception
  695. * @param string $name name of the cache
  696. * @param mixed $data data to cache
  697. * @param integer $lifetime number of seconds the cache is valid for
  698. * @return mixed for getting
  699. * @return boolean for setting
  700. */
  701. public static function cache($name, $data = NULL, $lifetime = NULL)
  702. {
  703. //in case the KO7_Cache is not yet loaded we need to use the normal cache...sucks but happens onload
  704. if (class_exists('KO7_Cache'))
  705. {
  706. //deletes the cache
  707. if ($lifetime===0)
  708. return Cache::instance()->delete($name);
  709. //no data provided we read
  710. if ($data===NULL)
  711. return Cache::instance()->get($name);
  712. //saves data
  713. else
  714. return Cache::instance()->set($name,$data, $lifetime);
  715. }
  716. else
  717. return self::file_cache($name, $data, $lifetime);
  718. }
  719. /**
  720. * Provides simple file-based caching for strings and arrays:
  721. *
  722. * // Set the "foo" cache
  723. * KO7::file_cache('foo', 'hello, world');
  724. *
  725. * // Get the "foo" cache
  726. * $foo = KO7::file_cache('foo');
  727. *
  728. * All caches are stored as PHP code, generated with [var_export][ref-var].
  729. * Caching objects may not work as expected. Storing references or an
  730. * object or array that has recursion will cause an E_FATAL.
  731. *
  732. * The cache directory and default cache lifetime is set by [KO7::init]
  733. *
  734. * [ref-var]: http://php.net/var_export
  735. *
  736. * @throws KO7_Exception
  737. * @param string $name name of the cache
  738. * @param mixed $data data to cache
  739. * @param integer $lifetime number of seconds the cache is valid for
  740. * @return mixed for getting
  741. * @return boolean for setting
  742. */
  743. public static function file_cache($name, $data = NULL, $lifetime = NULL)
  744. {
  745. // Cache file is a hash of the name
  746. $file = sha1($name).'.txt';
  747. // Cache directories are split by keys to prevent filesystem overload
  748. $dir = KO7::$cache_dir.DIRECTORY_SEPARATOR.$file[0].$file[1].DIRECTORY_SEPARATOR;
  749. if ($lifetime === NULL)
  750. {
  751. // Use the default lifetime
  752. $lifetime = KO7::$cache_life;
  753. }
  754. if ($data === NULL)
  755. {
  756. if (is_file($dir.$file))
  757. {
  758. if ((time() - filemtime($dir.$file)) < $lifetime)
  759. {
  760. // Return the cache
  761. try
  762. {
  763. return unserialize(file_get_contents($dir.$file));
  764. }
  765. catch (Exception $e)
  766. {
  767. // Cache is corrupt, let return happen normally.
  768. }
  769. }
  770. else
  771. {
  772. try
  773. {
  774. // Cache has expired
  775. unlink($dir.$file);
  776. }
  777. catch (Exception $e)
  778. {
  779. // Cache has mostly likely already been deleted,
  780. // let return happen normally.
  781. }
  782. }
  783. }
  784. // Cache not found
  785. return NULL;
  786. }
  787. if ( ! is_dir($dir))
  788. {
  789. // Create the cache directory
  790. mkdir($dir, 0777, TRUE);
  791. // Set permissions (must be manually set to fix umask issues)
  792. chmod($dir, 0777);
  793. }
  794. // Force the data to be a string
  795. $data = serialize($data);
  796. try
  797. {
  798. // Write the cache
  799. return (bool) file_put_contents($dir.$file, $data, LOCK_EX);
  800. }
  801. catch (Exception $e)
  802. {
  803. // Failed to write cache
  804. return FALSE;
  805. }
  806. }
  807. /**
  808. * Get a message from a file. Messages are arbitrary strings that are stored
  809. * in the `messages/` directory and reference by a key. Translation is not
  810. * performed on the returned values. See [message files](ko7/files/messages)
  811. * for more information.
  812. *
  813. * // Get "username" from messages/text.php
  814. * $username = KO7::message('text', 'username');
  815. *
  816. * @param string $file file name
  817. * @param string $path key path to get
  818. * @param mixed $default default value if the path does not exist
  819. * @return string message string for the given path
  820. * @return array complete message list, when no path is specified
  821. * @uses Arr::merge
  822. * @uses Arr::path
  823. */
  824. public static function message($file, $path = NULL, $default = NULL)
  825. {
  826. static $messages;
  827. if ( ! isset($messages[$file]))
  828. {
  829. // Create a new message list
  830. $messages[$file] = [];
  831. if ($files = KO7::find_file('messages', $file))
  832. {
  833. foreach ($files as $f)
  834. {
  835. // Combine all the messages recursively
  836. $messages[$file] = Arr::merge($messages[$file], KO7::load($f));
  837. }
  838. }
  839. }
  840. if ($path === NULL)
  841. {
  842. // Return all of the messages
  843. return $messages[$file];
  844. }
  845. else
  846. {
  847. // Get a message using the path
  848. return Arr::path($messages[$file], $path, $default);
  849. }
  850. }
  851. /**
  852. * PHP error handler, converts all errors into Error_Exceptions. This handler
  853. * respects error_reporting settings.
  854. *
  855. * @throws Error_Exception
  856. * @return TRUE
  857. */
  858. public static function error_handler($code, $error, $file = NULL, $line = NULL)
  859. {
  860. if (error_reporting() & $code)
  861. {
  862. // This error is not suppressed by current error reporting settings
  863. // Convert the error into an Error_Exception
  864. throw new Error_Exception($error, NULL, $code, 0, $file, $line);
  865. }
  866. // Do not execute the PHP error handler
  867. return TRUE;
  868. }
  869. /**
  870. * Catches errors that are not caught by the error handler, such as E_PARSE.
  871. *
  872. * @uses KO7_Exception::handler
  873. * @return void
  874. */
  875. public static function shutdown_handler()
  876. {
  877. if ( ! KO7::$_init)
  878. {
  879. // Do not execute when not active
  880. return;
  881. }
  882. try
  883. {
  884. if (KO7::$caching === TRUE AND KO7::$_files_changed === TRUE)
  885. {
  886. // Write the file path cache
  887. KO7::cache('KO7::find_file()', KO7::$_files);
  888. }
  889. }
  890. catch (Exception $e)
  891. {
  892. // Pass the exception to the handler
  893. KO7_Exception::handler($e);
  894. }
  895. if (KO7::$errors AND $error = error_get_last() AND in_array($error['type'], KO7::$shutdown_errors))
  896. {
  897. // Clean the output buffer
  898. ob_get_level() AND ob_clean();
  899. // Fake an exception for nice debugging
  900. KO7_Exception::handler(new Error_Exception($error['message'], NULL, $error['type'], 0, $error['file'], $error['line']));
  901. // Shutdown now to avoid a "death loop"
  902. exit(1);
  903. }
  904. }
  905. /**
  906. * Generates a version string based on the variables defined above.
  907. *
  908. * @return string
  909. */
  910. public static function version()
  911. {
  912. return 'Koseven '.KO7::VERSION.' ('.KO7::CODENAME.')';
  913. }
  914. /**
  915. * Call this within your function to mark it deprecated.
  916. *
  917. * @param string $since Version since this function shall be marked deprecated.
  918. * @param string $replacement [optional] replacement function to use instead
  919. */
  920. public static function deprecated(string $since, string $replacement = '') : void
  921. {
  922. // Get current debug backtrace
  923. $calling = debug_backtrace()[1];
  924. // Extract calling class and calling function
  925. $class = $calling['class'];
  926. $function = $calling['function'];
  927. // Build message
  928. $msg = 'Function "' . $function . '" inside class "' . $class . '" is deprecated since version ' . $since .
  929. ' and will be removed within the next major release.';
  930. // Check if replacement function is provided
  931. if ($replacement)
  932. {
  933. $msg .= ' Please consider replacing it with "' . $replacement . '".';
  934. }
  935. // Log the deprecation
  936. $log = static::$log;
  937. $log->add(Log::WARNING, $msg);
  938. $log->write();
  939. }
  940. }