Recommended setting: `TRUE` while developing, `FALSE` on production servers. | `TRUE` * `boolean` | profile | Whether to enable the [Profiler](ko7/profiling).

Recommended setting: `TRUE` while developing, `FALSE` on production servers. | `TRUE` * `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).

Recommended setting: `FALSE` while developing, `TRUE` on production servers. | `FALSE` * `boolean` | expose | Set the X-Powered-By header * * @throws KO7_Exception * @param array $settings Array of settings. See above. * @return void * @uses KO7::sanitize * @uses KO7::cache * @uses Profiler */ public static function init(array $settings = NULL) { if (KO7::$_init) { // Do not allow execution twice return; } // KO7 is now initialized KO7::$_init = TRUE; if (isset($settings['profile'])) { // Enable profiling KO7::$profiling = (bool) $settings['profile']; } // Start an output buffer ob_start(); if (isset($settings['errors'])) { // Enable error handling KO7::$errors = (bool) $settings['errors']; } if (KO7::$errors === TRUE) { // Enable KO7 exception handling, adds stack traces and error source. set_exception_handler(['KO7_Exception', 'handler']); // Enable KO7 error handling, converts all PHP errors to exceptions. set_error_handler(['KO7', 'error_handler']); } /** * Enable xdebug parameter collection in development mode to improve fatal stack traces. */ if (KO7::$environment == KO7::DEVELOPMENT AND extension_loaded('xdebug')) { ini_set('xdebug.collect_params', 3); } // Enable the KO7 shutdown handler, which catches E_FATAL errors. register_shutdown_function(['KO7', 'shutdown_handler']); if (isset($settings['expose'])) { KO7::$expose = (bool) $settings['expose']; } // Determine if we are running in a Windows environment KO7::$is_windows = (DIRECTORY_SEPARATOR === '\\'); if (isset($settings['cache_dir'])) { if ( ! is_dir($settings['cache_dir'])) { try { // Create the cache directory mkdir($settings['cache_dir'], 0755, TRUE); // Set permissions (must be manually set to fix umask issues) chmod($settings['cache_dir'], 0755); } catch (Exception $e) { throw new KO7_Exception('Could not create cache directory :dir', [':dir' => Debug::path($settings['cache_dir'])]); } } // Set the cache directory path KO7::$cache_dir = realpath($settings['cache_dir']); } else { // Use the default cache directory KO7::$cache_dir = APPPATH.'cache'; } if ( ! is_writable(KO7::$cache_dir)) { throw new KO7_Exception('Directory :dir must be writable', [':dir' => Debug::path(KO7::$cache_dir)]); } if (isset($settings['cache_life'])) { // Set the default cache lifetime KO7::$cache_life = (int) $settings['cache_life']; } if (isset($settings['caching'])) { // Enable or disable internal caching KO7::$caching = (bool) $settings['caching']; } if (KO7::$caching === TRUE) { // Load the file path cache KO7::$_files = KO7::cache('KO7::find_file()'); } if (isset($settings['charset'])) { // Set the system character set KO7::$charset = strtolower($settings['charset']); } if (function_exists('mb_internal_encoding')) { // Set the MB extension encoding to the same character set mb_internal_encoding(KO7::$charset); } if (isset($settings['base_url'])) { // Set the base URL KO7::$base_url = rtrim($settings['base_url'], '/').'/'; } if (isset($settings['index_file'])) { // Set the index file KO7::$index_file = trim($settings['index_file'], '/'); } // Sanitize all request variables $_GET = KO7::sanitize($_GET); $_POST = KO7::sanitize($_POST); $_COOKIE = KO7::sanitize($_COOKIE); // Load the logger if one doesn't already exist if ( ! KO7::$log instanceof Log) { KO7::$log = Log::instance(); } // Load the config if one doesn't already exist if ( ! KO7::$config instanceof Config) { KO7::$config = new Config; } } /** * Cleans up the environment: * * - Restore the previous error and exception handlers * - Destroy the KO7::$log and KO7::$config objects * * @return void */ public static function deinit() { if (KO7::$_init) { // Removed the autoloader spl_autoload_unregister(['KO7', 'auto_load']); if (KO7::$errors) { // Go back to the previous error handler restore_error_handler(); // Go back to the previous exception handler restore_exception_handler(); } // Destroy objects created by init KO7::$log = KO7::$config = NULL; // Reset internal storage KO7::$_modules = KO7::$_files = []; KO7::$_paths = [APPPATH, SYSPATH]; // Reset file cache status KO7::$_files_changed = FALSE; // KO7 is no longer initialized KO7::$_init = FALSE; } } /** * Recursively sanitizes an input variable: * * - Normalizes all newlines to LF * * @param mixed $value any variable * @return mixed sanitized variable */ public static function sanitize($value) { if (is_array($value) OR is_object($value)) { foreach ($value as $key => $val) { // Recursively clean each value $value[$key] = KO7::sanitize($val); } } elseif (is_string($value)) { if (strpos($value, "\r") !== FALSE) { // Standardize newlines $value = str_replace(["\r\n", "\r"], "\n", $value); } } return $value; } /** * Provides auto-loading support of classes that follow KO7's [class * naming conventions](ko7/conventions#class-names-and-file-location). * See [Loading Classes](ko7/autoloading) for more information. * * // Loads classes/My/Class/Name.php * KO7::auto_load('My_Class_Name'); * * or with a custom directory: * * // Loads vendor/My/Class/Name.php * KO7::auto_load('My_Class_Name', 'vendor'); * * You should never have to call this function, as simply calling a class * will cause it to be called. * * This function must be enabled as an autoloader in the bootstrap: * * spl_autoload_register(array('KO7', 'auto_load')); * * @param string $class Class name * @param string $directory Directory to load from * @return boolean */ public static function auto_load($class, $directory = 'classes') { // Transform the class name according to PSR-0 $class = ltrim($class, '\\'); $file = ''; $namespace = ''; if ($last_namespace_position = strripos($class, '\\')) { $namespace = substr($class, 0, $last_namespace_position); $class = substr($class, $last_namespace_position + 1); $file = str_replace('\\', DIRECTORY_SEPARATOR, $namespace).DIRECTORY_SEPARATOR; } $file .= str_replace('_', DIRECTORY_SEPARATOR, $class); if ($path = KO7::find_file($directory, $file)) { // Load the class file require $path; // Class has been found return TRUE; } // Class is not in the filesystem return FALSE; } /** * Provides auto-loading support of classes that follow KO7's old class * naming conventions. * * This is included for compatibility purposes with older modules. * * @param string $class Class name * @param string $directory Directory to load from * @return boolean */ public static function auto_load_lowercase($class, $directory = 'classes') { // Transform the class name into a path $file = str_replace('_', DIRECTORY_SEPARATOR, strtolower($class)); if ($path = KO7::find_file($directory, $file)) { // Load the class file require $path; // Class has been found return TRUE; } // Class is not in the filesystem return FALSE; } /** * Changes the currently enabled modules. Module paths may be relative * or absolute, but must point to a directory: * * KO7::modules(array('modules/foo', MODPATH.'bar')); * * @param array $modules list of module paths * @return array enabled modules */ public static function modules(array $modules = NULL) { if ($modules === NULL) { // Not changing modules, just return the current set return KO7::$_modules; } // Start a new list of include paths, APPPATH first $paths = [APPPATH]; foreach ($modules as $name => $path) { if (is_dir($path)) { // Add the module to include paths $paths[] = $modules[$name] = realpath($path).DIRECTORY_SEPARATOR; } else { // This module is invalid, remove it throw new KO7_Exception('Attempted to load an invalid or missing module \':module\' at \':path\'', [ ':module' => $name, ':path' => Debug::path($path), ]); } } // Finish the include paths by adding SYSPATH $paths[] = SYSPATH; // Set the new include paths KO7::$_paths = $paths; // Set the current module list KO7::$_modules = $modules; foreach (KO7::$_modules as $path) { $init = $path.'init'.EXT; if (is_file($init)) { // Include the module initialization file once require_once $init; } } return KO7::$_modules; } /** * Returns the the currently active include paths, including the * application, system, and each module's path. * * @return array */ public static function include_paths() { return KO7::$_paths; } /** * Searches for a file in the [Cascading Filesystem](ko7/files), and * returns the path to the file that has the highest precedence, so that it * can be included. * * When searching the "config", "messages", or "i18n" directories, or when * the `$array` flag is set to true, an array of all the files that match * that path in the [Cascading Filesystem](ko7/files) will be returned. * These files will return arrays which must be merged together. * * If no extension is given, the default extension (`EXT` set in * `index.php`) will be used. * * // Returns an absolute path to views/template.php * KO7::find_file('views', 'template'); * * // Returns an absolute path to media/css/style.css * KO7::find_file('media', 'css/style', 'css'); * * // Returns an array of all the "mimes" configuration files * KO7::find_file('config', 'mimes'); * * @param string $dir directory name (views, i18n, classes, extensions, etc.) * @param string $file filename with subdirectory * @param string $ext extension to search for * @param boolean $array return an array of files? * @return array a list of files when $array is TRUE * @return string single file path */ public static function find_file($dir, $file, $ext = NULL, $array = FALSE) { if ($ext === NULL) { // Use the default extension $ext = EXT; } elseif ($ext) { // Prefix the extension with a period $ext = ".{$ext}"; } else { // Use no extension $ext = ''; } // Create a partial path of the filename $path = $dir.DIRECTORY_SEPARATOR.$file.$ext; if (KO7::$caching === TRUE AND isset(KO7::$_files[$path.($array ? '_array' : '_path')])) { // This path has been cached return KO7::$_files[$path.($array ? '_array' : '_path')]; } if (KO7::$profiling === TRUE AND class_exists('Profiler', FALSE)) { // Start a new benchmark $benchmark = Profiler::start('KO7', __FUNCTION__); } if ($array OR $dir === 'config' OR $dir === 'i18n' OR $dir === 'messages') { // Include paths must be searched in reverse $paths = array_reverse(KO7::$_paths); // Array of files that have been found $found = []; foreach ($paths as $dir) { if (is_file($dir.$path)) { // This path has a file, add it to the list $found[] = $dir.$path; } } } else { // The file has not been found yet $found = FALSE; /** * NOTE: This check is only needed if we are PRE-Initialization and in Compatibility mode * Only performing before Initialization makes sure that no `strpos, etc. operations get called * without being necessary */ if (!KO7::$_init && KO7::$compatibility && strpos($path, 'kohana') !== FALSE) { $found = MODPATH.'kohana'.DIRECTORY_SEPARATOR.$path; if (!is_file($found)) { $found = FALSE; } } // If still not found. Search through $_paths if (! $found) { foreach (KO7::$_paths as $dir) { if (is_file($dir.$path)) { // A path has been found $found = $dir.$path; // Stop searching break; } } } } if (KO7::$caching === TRUE) { // Add the path to the cache KO7::$_files[$path.($array ? '_array' : '_path')] = $found; // Files have been changed KO7::$_files_changed = TRUE; } if (isset($benchmark)) { // Stop the benchmark Profiler::stop($benchmark); } return $found; } /** * Recursively finds all of the files in the specified directory at any * location in the [Cascading Filesystem](ko7/files), and returns an * array of all the files found, sorted alphabetically. * * // Find all view files. * $views = KO7::list_files('views'); * * @param string $directory directory name * @param array $paths list of paths to search * @param string|array $ext only list files with this extension * @param bool $sort sort alphabetically * * @return array */ public static function list_files($directory = NULL, array $paths = NULL, $ext = NULL, $sort = NULL) { if ($directory !== NULL) { // Add the directory separator $directory .= DIRECTORY_SEPARATOR; } if ($paths === NULL) { // Use the default paths $paths = KO7::$_paths; } if (is_string($ext)) { // convert string extension to array $ext = [$ext]; } if ($sort === NULL) { // sort results by default $sort = TRUE; } // Create an array for the files $found = []; foreach ($paths as $path) { if (is_dir($path.$directory)) { // Create a new directory iterator $dir = new DirectoryIterator($path.$directory); foreach ($dir as $file) { // Get the file name $filename = $file->getFilename(); if ($filename[0] === '.' OR $filename[strlen($filename)-1] === '~') { // Skip all hidden files and UNIX backup files continue; } // Relative filename is the array key $key = $directory.$filename; if ($file->isDir()) { if ($sub_dir = KO7::list_files($key, $paths, $ext, $sort)) { if (isset($found[$key])) { // Append the sub-directory list $found[$key] += $sub_dir; } else { // Create a new sub-directory list $found[$key] = $sub_dir; } } } elseif ($ext === NULL || in_array('.'.$file->getExtension(), $ext, TRUE)) { if ( ! isset($found[$key])) { // Add new files to the list $found[$key] = realpath($file->getPathname()); } } } } } if ($sort) { // Sort the results alphabetically ksort($found); } return $found; } /** * Loads a file within a totally empty scope and returns the output: * * $foo = KO7::load('foo.php'); * * @param string $file * @return mixed */ public static function load($file) { return include $file; } /** * Cache variables using current cache module if enabled, if not uses KO7::file_cache * * // Set the "foo" cache * KO7::cache('foo', 'hello, world'); * * // Get the "foo" cache * $foo = KO7::cache('foo'); * * @throws KO7_Exception * @param string $name name of the cache * @param mixed $data data to cache * @param integer $lifetime number of seconds the cache is valid for * @return mixed for getting * @return boolean for setting */ public static function cache($name, $data = NULL, $lifetime = NULL) { //in case the KO7_Cache is not yet loaded we need to use the normal cache...sucks but happens onload if (class_exists('KO7_Cache')) { //deletes the cache if ($lifetime===0) return Cache::instance()->delete($name); //no data provided we read if ($data===NULL) return Cache::instance()->get($name); //saves data else return Cache::instance()->set($name,$data, $lifetime); } else return self::file_cache($name, $data, $lifetime); } /** * Provides simple file-based caching for strings and arrays: * * // Set the "foo" cache * KO7::file_cache('foo', 'hello, world'); * * // Get the "foo" cache * $foo = KO7::file_cache('foo'); * * All caches are stored as PHP code, generated with [var_export][ref-var]. * Caching objects may not work as expected. Storing references or an * object or array that has recursion will cause an E_FATAL. * * The cache directory and default cache lifetime is set by [KO7::init] * * [ref-var]: http://php.net/var_export * * @throws KO7_Exception * @param string $name name of the cache * @param mixed $data data to cache * @param integer $lifetime number of seconds the cache is valid for * @return mixed for getting * @return boolean for setting */ public static function file_cache($name, $data = NULL, $lifetime = NULL) { // Cache file is a hash of the name $file = sha1($name).'.txt'; // Cache directories are split by keys to prevent filesystem overload $dir = KO7::$cache_dir.DIRECTORY_SEPARATOR.$file[0].$file[1].DIRECTORY_SEPARATOR; if ($lifetime === NULL) { // Use the default lifetime $lifetime = KO7::$cache_life; } if ($data === NULL) { if (is_file($dir.$file)) { if ((time() - filemtime($dir.$file)) < $lifetime) { // Return the cache try { return unserialize(file_get_contents($dir.$file)); } catch (Exception $e) { // Cache is corrupt, let return happen normally. } } else { try { // Cache has expired unlink($dir.$file); } catch (Exception $e) { // Cache has mostly likely already been deleted, // let return happen normally. } } } // Cache not found return NULL; } if ( ! is_dir($dir)) { // Create the cache directory mkdir($dir, 0777, TRUE); // Set permissions (must be manually set to fix umask issues) chmod($dir, 0777); } // Force the data to be a string $data = serialize($data); try { // Write the cache return (bool) file_put_contents($dir.$file, $data, LOCK_EX); } catch (Exception $e) { // Failed to write cache return FALSE; } } /** * Get a message from a file. Messages are arbitrary strings that are stored * in the `messages/` directory and reference by a key. Translation is not * performed on the returned values. See [message files](ko7/files/messages) * for more information. * * // Get "username" from messages/text.php * $username = KO7::message('text', 'username'); * * @param string $file file name * @param string $path key path to get * @param mixed $default default value if the path does not exist * @return string message string for the given path * @return array complete message list, when no path is specified * @uses Arr::merge * @uses Arr::path */ public static function message($file, $path = NULL, $default = NULL) { static $messages; if ( ! isset($messages[$file])) { // Create a new message list $messages[$file] = []; if ($files = KO7::find_file('messages', $file)) { foreach ($files as $f) { // Combine all the messages recursively $messages[$file] = Arr::merge($messages[$file], KO7::load($f)); } } } if ($path === NULL) { // Return all of the messages return $messages[$file]; } else { // Get a message using the path return Arr::path($messages[$file], $path, $default); } } /** * PHP error handler, converts all errors into Error_Exceptions. This handler * respects error_reporting settings. * * @throws Error_Exception * @return TRUE */ public static function error_handler($code, $error, $file = NULL, $line = NULL) { if (error_reporting() & $code) { // This error is not suppressed by current error reporting settings // Convert the error into an Error_Exception throw new Error_Exception($error, NULL, $code, 0, $file, $line); } // Do not execute the PHP error handler return TRUE; } /** * Catches errors that are not caught by the error handler, such as E_PARSE. * * @uses KO7_Exception::handler * @return void */ public static function shutdown_handler() { if ( ! KO7::$_init) { // Do not execute when not active return; } try { if (KO7::$caching === TRUE AND KO7::$_files_changed === TRUE) { // Write the file path cache KO7::cache('KO7::find_file()', KO7::$_files); } } catch (Exception $e) { // Pass the exception to the handler KO7_Exception::handler($e); } if (KO7::$errors AND $error = error_get_last() AND in_array($error['type'], KO7::$shutdown_errors)) { // Clean the output buffer ob_get_level() AND ob_clean(); // Fake an exception for nice debugging KO7_Exception::handler(new Error_Exception($error['message'], NULL, $error['type'], 0, $error['file'], $error['line'])); // Shutdown now to avoid a "death loop" exit(1); } } /** * Generates a version string based on the variables defined above. * * @return string */ public static function version() { return 'Koseven '.KO7::VERSION.' ('.KO7::CODENAME.')'; } /** * Call this within your function to mark it deprecated. * * @param string $since Version since this function shall be marked deprecated. * @param string $replacement [optional] replacement function to use instead */ public static function deprecated(string $since, string $replacement = '') : void { // Get current debug backtrace $calling = debug_backtrace()[1]; // Extract calling class and calling function $class = $calling['class']; $function = $calling['function']; // Build message $msg = 'Function "' . $function . '" inside class "' . $class . '" is deprecated since version ' . $since . ' and will be removed within the next major release.'; // Check if replacement function is provided if ($replacement) { $msg .= ' Please consider replacing it with "' . $replacement . '".'; } // Log the deprecation $log = static::$log; $log->add(Log::WARNING, $msg); $log->write(); } }