/Atomik.php
PHP | 1804 lines | 903 code | 245 blank | 656 comment | 211 complexity | bf47181b5e0ae0d0daac558c9d0114ee MD5 | raw file
Possible License(s): LGPL-2.1, MIT, CC-BY-3.0
- <?php
- /**
- * Atomik Framework
- * Copyright (c) 2008-2009 Maxime Bouroumeau-Fuseau
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * @package Atomik
- * @author Maxime Bouroumeau-Fuseau
- * @copyright 2008-2010 (c) Maxime Bouroumeau-Fuseau
- * @license http://www.opensource.org/licenses/mit-license.php
- * @link http://www.atomikframework.com
- */
- define('ATOMIK_VERSION', '2.3');
- !defined('ATOMIK_APP_ROOT') && define('ATOMIK_APP_ROOT', './app');
- /* -------------------------------------------------------------------------------------------
- * APPLICATION CONFIGURATION
- * ------------------------------------------------------------------------------------------ */
- Atomik::reset(array(
- 'app' => array(
-
- /* @var string */
- 'default_action' => 'index',
- /* The name of the layout
- * Add multiple layouts using an array (will be rendered in reverse order)
- * @var array|bool|string */
- 'layout' => false,
-
- /* @var bool */
- 'disable_layout' => false,
-
- /* Whether to propagate view vars to the layout
- * @var bool */
- 'vars_to_layout' => true,
-
- /* An array where keys are route names and their value is an associative
- * array of default values
- * @see Atomik::route()
- * @var array */
- 'routes' => array(),
-
- /* @var bool */
- 'force_uri_extension' => false,
-
- /* List of escaping profiles where keys are profile names and their
- * value an array of callbacks
- * @see Atomik::escape()
- * @var array */
- 'escaping' => array(
- 'default' => array('htmlspecialchars', 'nl2br')
- ),
-
- /* @see Atomik::filter()
- * @var array */
- 'filters' => array(
-
- /* @var array */
- 'rules' => array(),
-
- /* @var array */
- 'callbacks' => array(),
-
- /* @var string */
- 'default_message' => 'The %s field failed to validate',
-
- /* @var string */
- 'required_message' => 'The %s field must be filled'
- ),
-
- /**
- * The callback used to execute actions
- * @var callback */
- 'executor' => array('Atomik', 'executeFile'),
-
- /* @see Atomik::render()
- * @var array */
- 'views' => array(
-
- /* @var string */
- 'file_extension' => '.phtml',
-
- /* Alternative rendering engine
- * @see Atomik::renderFile()
- * @var callback */
- 'engine' => false,
-
- /* @var string */
- 'default_context' => 'html',
-
- /* The GET parameter to retrieve the current context
- * @var string */
- 'context_param' => 'format',
-
- /* List of contexts where keys are the context name.
- * Contexts can specify:
- * - prefix (string): the view filename's extension prefix
- * - layout (bool): whether the layout should be rendered
- * - content_type (string): the HTTP response content type
- * @var array */
- 'contexts' => array(
- 'html' => array(
- 'prefix' => '',
- 'layout' => true,
- 'content_type' => 'text/html'
- ),
- 'ajax' => array(
- 'prefix' => '',
- 'layout' => false,
- 'content_type' => 'text/html'
- ),
- 'xml' => array(
- 'prefix' => 'xml',
- 'layout' => false,
- 'content_type' => 'text/xml'
- ),
- 'json' => array(
- 'prefix' => 'json',
- 'layout' => false,
- 'content_type' => 'application/json'
- ),
- 'js' => array(
- 'prefix' => 'js',
- 'layout' => false,
- 'content_type' => 'text/javascript'
- ),
- 'css' => array(
- 'prefix' => 'css',
- 'layout' => false,
- 'content_type' => 'text/css'
- )
- )
- ),
-
- /* A parameter in the route that will allow to specify the http method
- * (override the request's method). False to disable
- * @var string */
- 'http_method_param' => '_method',
-
- /* @var array */
- 'allowed_http_methods' => array('GET', 'POST', 'PUT', 'DELETE', 'TRACE', 'HEAD', 'OPTIONS', 'CONNECT')
-
- )
- ));
- /* -------------------------------------------------------------------------------------------
- * CORE CONFIGURATION
- * ------------------------------------------------------------------------------------------ */
- Atomik::set(array(
- /* @var array */
- 'plugins' => array(),
- /* @var array */
- 'atomik' => array(
- /* Atomik's filename
- * @var string */
- 'scriptname' => __FILE__,
-
- /* Base url, set to null for auto detection
- * @var string */
- 'base_url' => null,
-
- /* Whether url rewriting is activated on the server
- * @var bool */
- 'url_rewriting' => false,
- /* Keep compatibility with 2.2
- * @var bool */
- 'urimatch_compat' => false,
- /* Whether to automatically allow additional params at the end of routed uris
- * @var bool */
- 'auto_uri_wildcard' => false,
-
- /* @var bool */
- 'debug' => false,
-
- /* The GET parameter used to retreive the action
- * @var string */
- 'trigger' => 'action',
- /* Whether to register the class autoloader
- * @var bool */
- 'class_autoload' => true,
-
- /* @var bool */
- 'start_session' => true,
- /* @var string */
- 'session_namespace' => false,
-
- /* Plugin's assets path template.
- * %s will be replaced by the plugin's name
- * @see Atomik::pluginAsset()
- * @var string */
- 'plugin_assets_tpl' => 'app/plugins/%s/assets/',
- /* @var array */
- 'log' => array(
-
- /* @var bool */
- 'register_default' => false,
-
- /* From which level to start logging messages
- * @var int */
- 'level' => LOG_WARNING,
-
- /* Message template for the default logger
- * @see Atomik::logToFile()
- * @var string */
- 'message_template' => '[%date%] [%level%] %message%'
- ),
-
- /* @var array */
- 'dirs' => array(
- 'app' => ATOMIK_APP_ROOT,
- 'plugins' => ATOMIK_APP_ROOT . '/plugins',
- 'actions' => ATOMIK_APP_ROOT . '/actions',
- 'views' => ATOMIK_APP_ROOT . '/views',
- 'layouts' => array(ATOMIK_APP_ROOT . '/views', ATOMIK_APP_ROOT . '/layouts'),
- 'helpers' => ATOMIK_APP_ROOT . '/helpers',
- 'includes' => array(ATOMIK_APP_ROOT . '/includes', ATOMIK_APP_ROOT . '/libraries', ATOMIK_APP_ROOT . '/libs'),
- 'namespaces' => array(),
- 'overrides' => ATOMIK_APP_ROOT . '/overrides'
- ),
-
- /* @var array */
- 'files' => array(
- 'index' => 'index.php',
- 'config' => ATOMIK_APP_ROOT . '/config', // without extension
- 'bootstrap' => ATOMIK_APP_ROOT . '/bootstrap.php',
- 'pre_dispatch' => ATOMIK_APP_ROOT . '/pre_dispatch.php',
- 'post_dispatch' => ATOMIK_APP_ROOT . '/post_dispatch.php',
- '404' => ATOMIK_APP_ROOT . '/404.php',
- 'error' => ATOMIK_APP_ROOT . '/error.php',
- 'log' => ATOMIK_APP_ROOT . '/log.txt'
- ),
-
- /* @var bool */
- 'catch_errors' => false,
-
- /* @var bool */
- 'throw_errors' => false,
-
- /* @var array */
- 'error_report_attrs' => array(
- 'atomik-error' => 'style="padding: 10px"',
- 'atomik-error-title' => 'style="font-size: 1.3em; font-weight: bold; color: #FF0000"',
- 'atomik-error-lines' => 'style="width: 100%; margin-bottom: 20px; background-color: #fff;'
- . 'border: 1px solid #000; font-size: 0.8em"',
- 'atomik-error-line' => '',
- 'atomik-error-line-error' => 'style="background-color: #ffe8e7"',
- 'atomik-error-line-number' => 'style="background-color: #eeeeee"',
- 'atomik-error-line-text' => '',
- 'atomik-error-stack' => ''
- )
-
- ),
-
- /* @var int */
- 'start_time' => time() + microtime()
- ));
- /* -------------------------------------------------------------------------------------------
- * CORE
- * ------------------------------------------------------------------------------------------ */
- // creates the A function (shortcut to Atomik::get)
- if (!function_exists('A')) {
- /**
- * Shortcut function to Atomik::get()
- * Useful when dealing with selectors
- *
- * @see Atomik::get()
- * @return mixed
- */
- function A()
- {
- $args = func_get_args();
- return call_user_func_array(array('Atomik', 'get'), $args);
- }
- }
- // starts Atomik unless ATOMIK_AUTORUN is set to false
- if (!defined('ATOMIK_AUTORUN') || ATOMIK_AUTORUN === true) {
- Atomik::run();
- }
- /**
- * Exception class for Atomik
- *
- * @package Atomik
- */
- class Atomik_Exception extends Exception {}
- /**
- * HTTP Exception class for Atomik
- *
- * The code must be an HTTP response code
- *
- * @package Atomik
- */
- class Atomik_HttpException extends Atomik_Exception {}
- /**
- * Atomik Framework Main class
- *
- * @package Atomik
- */
- final class Atomik
- {
- /**
- * Global store
- *
- * This property is used to stored all data accessed using get(), set()...
- *
- * @var array
- */
- public static $store = array();
-
- /**
- * Atomik singleton
- *
- * @var Atomik
- */
- private static $instance;
-
- /**
- * Global store to reset to
- *
- * @var array
- */
- private static $reset = array();
-
- /**
- * Loaded plugins
- *
- * When a plugin is loaded, its name is saved in this array to
- * avoid loading it twice.
- *
- * @var array
- */
- private static $plugins = array();
-
- /**
- * Registered events
- *
- * The array keys are event names and their value is an array with
- * the event callbacks
- *
- * @var array
- */
- private static $events = array();
-
- /**
- * Selectors namespaces
- *
- * The array keys are the namespace name and the associated value is
- * the callback to call when the namespace is used
- *
- * @var array
- */
- private static $namespaces = array('flash' => array('Atomik', '_getFlashMessages'));
-
- /**
- * Execution contexts
- *
- * Each call to Atomik::execute() creates a context.
- *
- * @var array
- */
- private static $execContexts = array();
-
- /**
- * Pluggable applications
- *
- * @var array
- */
- private static $pluggableApplications = array();
-
- /**
- * Already loaded helpers
- *
- * @var array
- */
- private static $loadedHelpers = array();
-
- /**
- * Returns a singleton instance
- *
- * @return Atomik
- */
- public static function instance()
- {
- if (self::$instance === null) {
- self::$instance = new Atomik();
- }
- return self::$instance;
- }
-
- /**
- * Starts Atomik
- *
- * If dispatch is false, you will have to manually dispatch the request and exit.
- *
- * @param string $env A configuration key which will be merged at the root of the store
- * @param string $uri
- * @param bool $dispatch Whether to dispatch
- */
- public static function run($env = null, $uri = null, $dispatch = true)
- {
- // wrap the whole app inside a try/catch block to catch all errors
- try {
- @chdir(dirname(self::get('atomik/scriptname')));
-
- // config & environment
- self::loadConfig(self::get('atomik/files/config'), false);
- if ($env !== null && self::has($env)) {
- self::set(self::get($env));
- }
-
- self::fireEvent('Atomik::Config');
-
- // adds includes dirs to php include path
- $includePaths = array_merge(
- (array) self::get('atomik/dirs/includes', array()),
- array(get_include_path())
- );
- set_include_path(implode(PATH_SEPARATOR, $includePaths));
-
- // registers the error handler
- if (self::get('atomik/catch_errors', false) ||
- !self::get('atomik/throw_errors', false)) {
- set_error_handler('Atomik::_errorHandler');
- }
-
- // sets the error reporting to all errors if debug mode is on
- if (self::get('atomik/debug', false) == true) {
- error_reporting(E_ALL | E_STRICT);
- }
-
- // default logger
- if (self::get('atomik/log/register_default', false) == true) {
- self::listenEvent('Atomik::Log', 'Atomik::logToFile');
- }
-
- // registers the class autoload handler
- if (self::get('atomik/class_autoload', true) == true) {
- if (!function_exists('spl_autoload_register')) {
- throw new Atomik_Exception('Missing spl_autoload_register function');
- }
- spl_autoload_register('Atomik::autoload');
- }
-
- // cleans the plugins array
- $plugins = array();
- foreach (self::get('plugins', array()) as $key => $value) {
- if (!is_string($key)) {
- $key = $value;
- $value = array();
- }
- $plugins[ucfirst($key)] = (array) $value;
- }
- self::set('plugins', $plugins, false);
-
- // loads plugins
- // this method allows plugins that are being loaded to modify the plugins array
- $disabledPlugins = array();
- while (count($pluginsToLoad = array_diff(array_keys(self::get('plugins')),
- self::getLoadedPlugins(), $disabledPlugins)) > 0) {
- foreach ($pluginsToLoad as $plugin) {
- if (self::loadPlugin($plugin) === false) {
- $disabledPlugins[] = $plugin;
- }
- }
- }
-
- self::fireEvent('Atomik::Bootstrap');
-
- // loads bootstrap file
- if (file_exists($filename = self::get('atomik/files/bootstrap'))) {
- require($filename);
- }
-
- // starts the session
- if (self::get('atomik/start_session', true) == true) {
- session_start();
- if (($ns = self::get('atomik/session_namespace', false)) !== false) {
- if (!isset($_SESSION[$ns])) {
- $_SESSION[$ns] = array();
- }
- self::$store['session'] = &$_SESSION[$ns];
- } else {
- self::$store['session'] = &$_SESSION;
- }
- }
-
- // core is starting
- self::fireEvent('Atomik::Start', array(&$cancel));
- if ($cancel) {
- self::end(true);
- }
- self::log('Starting', LOG_DEBUG);
-
- // checks if url rewriting is used
- if (!self::has('atomik/url_rewriting')) {
- self::set('atomik/url_rewriting',
- isset($_SERVER['REDIRECT_URL']) || isset($_SERVER['REDIRECT_URI']));
- }
-
- // dispatches
- if ($dispatch) {
- self::dispatch($uri);
- self::end(true);
- }
-
- } catch (Exception $e) {
- self::log('[EXCEPTION: ' . $e->getCode() . '] ' . $e->getMessage(), LOG_ERR);
- self::fireEvent('Atomik::Error', array($e));
-
- // checks if we really want to catch errors
- if (self::get('atomik/catch_errors', false)) {
- self::renderException($e);
- } else if (self::get('atomik/throw_errors', false)) {
- throw $e;
- }
-
- header('Location: ', false, 500); // set the http response code
- self::end(false);
- }
- }
-
- /**
- * Loads a configuration file
- *
- * Supported format are php, ini and json
- * If the file's extension is not specified, the method will
- * search for a file with one of the supported extensions.
- *
- * @param string $filename
- * @param bool $triggerError Whether to throw an exception if the file does not exist
- */
- public static function loadConfig($filename, $triggerError = true)
- {
- self::fireEvent('Atomik::Loadconfig::Before', array(&$filename, &$triggerError));
-
- // config file format
- if (!preg_match('/.+\.(php|ini|json)$/', $filename)) {
- $found = false;
- foreach (array('php', 'ini', 'json') as $format) {
- if (file_exists($filename . '.' . $format)) {
- $found = true;
- break;
- }
- }
- if (!$found) {
- if ($triggerError) {
- throw new Atomik_Exception("Configuration file $filename not found");
- }
- return;
- }
- $filename .= '.' . $format;
- } else {
- $format = substr($filename, strrpos($filename, '.') + 1);
- }
-
- // loads the config file
- if ($format === 'php') {
- if (is_array($config = include($filename))) {
- self::set($config);
- }
- } else if ($format === 'ini') {
- if (($data = parse_ini_file($filename, true)) === false) {
- throw new Atomik_Exception('INI configuration malformed');
- }
- self::set(self::_dimensionizeArray($data, '.'), null, false);
- } else if ($format === 'json') {
- if (($config = json_decode(file_get_contents($filename), true)) === null) {
- throw new Atomik_Exception('JSON configuration malformed');
- }
- self::set($config);
- }
-
- self::fireEvent('Atomik::Loadconfig::After', array($filename, $triggerError));
- }
-
- /**
- * Dispatches the request
- *
- * It takes an URI, applies routes, executes the action and renders the view.
- * If $uri is null, the value of the GET parameter specified as the trigger
- * will be used.
- *
- * @param string $uri
- * @param bool $allowPluggableApplication Whether to allow plugin application to be loaded
- */
- public static function dispatch($uri = null, $allowPluggableApplication = true)
- {
- try {
- self::fireEvent('Atomik::Dispatch::Start', array(&$uri, &$allowPluggableApplication, &$cancel));
- if ($cancel) {
- return;
- }
-
- // checks if it's needed to auto discover the uri
- if ($uri === null) {
-
- // retreives the requested uri
- $trigger = self::get('atomik/trigger', 'action');
- if (isset($_GET[$trigger]) && !empty($_GET[$trigger])) {
- $uri = trim($_GET[$trigger], '/');
- }
-
- // retreives the base url
- if (self::get('atomik/base_url', null) === null) {
- if (self::get('atomik/url_rewriting') && (isset($_SERVER['REDIRECT_URL']) || isset($_SERVER['REDIRECT_URI']))) {
- // finds the base url from the redirected url
- $redirectUrl = isset($_SERVER['REDIRECT_URL']) ? $_SERVER['REDIRECT_URL'] : $_SERVER['REDIRECT_URI'];
- if (isset($_GET[$trigger])) {
- self::set('atomik/base_url', substr($redirectUrl, 0, -strlen($_GET[$trigger])));
- } else {
- self::set('atomik/base_url', $redirectUrl);
- }
- } else {
- // finds the base url from the script name
- self::set('atomik/base_url', rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\') . '/');
- }
- }
-
- } else {
- // sets the user defined request
- // retreives the base url
- if (self::get('atomik/base_url', null) === null) {
- // finds the base url from the script name
- self::set('atomik/base_url', rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\') . '/');
- }
- }
-
- // default uri
- if (empty($uri)) {
- $uri = self::get('app/default_action', 'index');
- }
-
- // routes the request
- $request = self::route($uri, $_GET);
- if (isset($request['@redirect'])) {
- self::redirect($request['@redirect']);
- }
-
- // checking if no dot are in the action name to avoid any hack attempt and if no
- // underscore is use as first character in a segment
- if (strpos($request['action'], '..') !== false || substr($request['action'], 0, 1) == '_'
- || strpos($request['action'], '/_') !== false) {
- throw new Atomik_Exception('Action outside of bound');
- }
-
- self::set('request_uri', $uri);
- self::set('request', $request);
- if (!self::has('full_request_uri')) {
- self::set('full_request_uri', $uri);
- }
-
- self::fireEvent('Atomik::Dispatch::Uri', array(&$uri, &$request, &$cancel));
- if ($cancel) {
- return;
- }
-
- // checks if the uri triggers a pluggable application
- if ($allowPluggableApplication) {
- foreach (self::$pluggableApplications as $plugin => $pluggAppConfig) {
- if (!self::uriMatch($pluggAppConfig['route'], $request['action'])) {
- continue;
- }
-
- // rewrite uri
- $baseAction = trim($pluggAppConfig['route'], '/*');
- $uri = substr(trim($request['action'], '/'), strlen($baseAction));
- if ($baseAction == '' && $uri == self::get('app/default_action')) {
- $uri = '';
- }
- self::set('atomik/base_action', $baseAction);
-
- // dispatches the pluggable application
- return self::dispatchPluggableApplication($plugin, $uri, $pluggAppConfig);
- }
- }
-
- // fetches the http method
- $httpMethod = $_SERVER['REQUEST_METHOD'];
- if (($param = self::get('app/http_method_param', false)) !== false) {
- // checks if the route parameter to override the method is defined
- $httpMethod = strtoupper(self::get($param, $httpMethod, $request));
- }
- if (!in_array($httpMethod, self::get('app/allowed_http_methods'))) {
- // specified method not allowed
- throw new Atomik_Exception('HTTP method not allowed');
- }
- self::set('app/http_method', strtoupper($httpMethod));
-
- // sets the view context
- self::setViewContext();
-
- // configuration is ok, ready to dispatch
- self::fireEvent('Atomik::Dispatch::Before', array(&$cancel));
- if ($cancel) {
- return;
- }
-
- self::log('Dispatching action ' . $request['action'], LOG_DEBUG);
- $vars = array();
-
- // pre dispatch action
- if (file_exists($filename = self::get('atomik/files/pre_dispatch'))) {
- list($content, $vars) = self::instance()->scoped($filename);
- }
-
- // executes the action
- ob_start();
- list($content, $vars) = self::execute(self::get('request/action'), true, $vars, true);
- $content = ob_get_clean() . $content;
-
- // whether to propagate vars to the layout or not
- if (!self::get('app/vars_to_layout', true)) {
- $vars = array();
- }
-
- // renders the layouts if enable
- if (($layout = self::get('app/layout', false)) !== false &&
- !self::get('app/disable_layout', false)) {
- $content = self::renderLayout($layout, $content, $vars);
- }
-
- // echoes the content
- self::fireEvent('Atomik::Output::Before', array(&$content));
- echo $content;
- self::fireEvent('Atomik::Output::After', array($content));
-
- // dispatch done
- self::fireEvent('Atomik::Dispatch::After');
-
- // post dispatch action
- if (file_exists($filename = self::get('atomik/files/post_dispatch'))) {
- require($filename);
- }
-
- } catch (Atomik_HttpException $e) {
- if ($e->getCode() == 404) {
- self::log('[404 NOT FOUND] ' . $e->getMessage(), LOG_ERR);
- self::fireEvent('Atomik::404', array($e));
-
- header('HTTP/1.0 404 Not Found');
- header('Content-type: text/html');
-
- if (file_exists($filename = self::get('atomik/files/404'))) {
- // includes the 404 error file
- include($filename);
- } else if (self::get('atomik/debug', false)) {
- echo '<h1>' . $e->getMessage() . '</h1>';
- } else {
- echo '<h1>Page not found</h1>';
- }
-
- self::end(false);
- }
- }
- }
- /**
- * Fires the Atomik::End event and exits the application
- *
- * @param bool $success Whether the application exit on success or because an error occured
- * @param bool $writeSession Whether to call session_write_close() before exiting
- */
- public static function end($success = false, $writeSession = true)
- {
- self::fireEvent('Atomik::End', array($success, &$writeSession));
-
- if ($writeSession) {
- session_write_close();
- }
-
- self::log('Ending', LOG_DEBUG);
- exit;
- }
-
- /**
- * Sets the view context
- *
- * View contexts are defined in app/views/contexts.
- * They can specify:
- * - an extension prefix (prefix)
- * - a layout (layout) (false disables the layout)
- * - an HTTP Content-Type (content_type)
- *
- * @param string $context
- */
- public static function setViewContext($context = null)
- {
- if ($context === null) {
- // fetches the view context
- $context = self::get(self::get('app/views/context_param', 'format'),
- self::get('app/views/default_context', 'html'),
- self::get('request'));
- }
-
- self::set('app/view_context', $context);
-
- // retreives view context params and prepare the response
- if (($viewContextParams = self::get('app/views/contexts/' . $context, false)) !== false) {
- if ($viewContextParams['layout'] !== true) {
- self::set('app/layout', $viewContextParams['layout']);
- }
- header('Content-type: ' .
- self::get('content_type', 'text/html', $viewContextParams));
- }
- }
-
- /**
- * Checks if an uri matches the pattern.
- *
- * The pattern can contain the * wildcard in any segment.
- * For example "users/*" will match all child actions of users.
- * If you want to match users and its children use "users*".
- *
- * Pattern is considered a regular expression if enclosed
- * between # (example: "#users/(.*)#")
- *
- * @param string $pattern
- * @param string $uri Default is the current request uri
- * @return bool
- */
- public static function uriMatch($pattern, $uri = null)
- {
- if ($uri === null) {
- $uri = self::get('request_uri');
- }
- $uri = trim($uri, '/');
- $pattern = trim($pattern, '/');
-
- if (self::get('atomik/urimatch_compat', false)) {
- // compatibility with 2.2
- if (substr($pattern, -2) == '/*') {
- $pattern = substr($pattern, 0, -2) . '*';
- }
- }
-
- $regexp = $pattern;
- if ($pattern{0} != '#') {
- $regexp = '#^' . str_replace('*', '(.*)', $pattern) . '$#';
- }
-
- return preg_match($regexp, $uri);
- }
-
- /**
- * Parses an uri to extract parameters
- *
- * Routes defines how to extract parameters from an uri. They can
- * have additional default parameters.
- * There are two kind of routes:
- *
- * - segments:
- * the uri is divided into path segments. Each segment can be
- * either static or a parameter (indicated by :).
- * eg: /archives/:year/:month
- *
- * - regexp:
- * uses a regexp against the uri. Must be enclosed using # instead of
- * slashes parameters must be specified as named subpattern.
- * eg: #^archives/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})$#
- *
- * If no route matches, the default route (ie :action) will automatically be used.
- * If the route ends with *, any additional segments will be added as parameters
- * eg: /archives/:year/* + /archives/2009/id/1 => year=2009 id=1
- *
- * You can also name your routes using the @name parameter (which won't be included
- * in the returned params). Named route can then be used with Atomik::url()
- *
- * @param string $uri
- * @param array $params Additional parameters which are not in the uri
- * @param array $routes Uses app/routes if null
- * @return array Route parameters
- */
- public static function route($uri, $params = array(), $routes = null)
- {
- if ($routes === null) {
- $routes = self::get('app/routes');
- }
-
- self::fireEvent('Atomik::Router::Start', array(&$uri, &$routes, &$params));
-
- // extracts uri information
- $components = parse_url($uri);
- $uri = trim($components['path'], '/');
- $uriSegments = explode('/', $uri);
- $uriExtension = false;
- if (isset($components['query'])) {
- parse_str($components['query'], $query);
- $params = array_merge($query, $params);
- }
-
- // extract the file extension from the uri
- $lastSegment = array_pop($uriSegments);
- if (($dot = strrpos($lastSegment, '.')) !== false) {
- $uriExtension = substr($lastSegment, $dot + 1);
- $lastSegment = substr($lastSegment, 0, $dot);
- }
- $uriSegments[] = $lastSegment;
-
- // checks if the extension must be present
- if (self::get('app/force_uri_extension', false) && $uriExtension === false) {
- throw new Atomik_Exception('Missing file extension');
- }
-
- // searches for a route matching the uri
- $found = false;
- $request = array();
- foreach (array_reverse($routes) as $route => $default) {
- if (!is_array($default)) {
- $default = array('action' => $default);
- }
-
- // removes the route name from the default params
- if (isset($default['@name'])) {
- unset($default['@name']);
- }
-
- // regexp
- if ($route{0} == '#') {
- if (!preg_match($route, $uri, $matches)) {
- continue;
- }
- unset($matches[0]);
- $found = true;
- $request = array_merge($default, $matches);
- break;
- }
-
- $segments = explode('/', trim($route, '/'));
- $request = $default;
- $extension = false;
-
- // extract the file extension from the route
- $lastSegment = array_pop($segments);
- if (($dot = strrpos($lastSegment, '.')) !== false) {
- $extension = substr($lastSegment, $dot + 1);
- $lastSegment = substr($lastSegment, 0, $dot);
- }
- // checks if additional params are allowed
- if (!($wildcard = $lastSegment == '*')) {
- $segments[] = $lastSegment;
- }
-
- // checks the extension
- if ($extension !== false) {
- if ($extension{0} == ':') {
- // extension is a parameter
- if ($uriExtension !== false) {
- $request[substr($extension, 1)] = $uriExtension;
- } else if (!isset($request[substr($extension, 1)])) {
- // no uri extension and no default value
- continue;
- }
- } else if ($extension != $uriExtension) {
- continue;
- }
- }
-
- for ($i = 0, $count = count($segments); $i < $count; $i++) {
- if (substr($segments[$i], 0, 1) == ':') {
- // segment is a parameter
- if (isset($uriSegments[$i])) {
- // this segment is defined in the uri
- $request[substr($segments[$i], 1)] = $uriSegments[$i];
- $segments[$i] = $uriSegments[$i];
- } else if (!array_key_exists(substr($segments[$i], 1), $default)) {
- // not defined in the uri and no default value
- continue 2;
- }
- } else {
- // fixed segment
- if (!isset($uriSegments[$i]) || $uriSegments[$i] != $segments[$i]) {
- continue 2;
- }
- }
- }
-
- // the "action" param must be set
- if (!isset($request['action']) && !isset($request['@redirect'])) {
- continue;
- }
-
- // if there's remaining segments in the uri
- if (($count = count($uriSegments)) > ($start = count($segments))) {
- if (!$wildcard || !self::get('atomik/auto_uri_wildcard', false)) {
- continue;
- }
- // adds them as params
- for ($i = $start; $i < $count; $i += 2) {
- if (isset($uriSegments[$i + 1])) {
- $request[$uriSegments[$i]] = $uriSegments[$i + 1];
- }
- }
- }
-
- $found = true;
- break;
- }
-
- if (!$found) {
- // route not found, creating default route
- $request = array(
- 'action' => implode('/', $uriSegments),
- self::get('app/views/context_param', 'format') => $uriExtension === false ?
- self::get('app/views/default_context', 'html') : $uriExtension
- );
- }
-
- $request = array_merge($params, $request);
- self::fireEvent('Atomik::Router::End', array($uri, &$request));
-
- return $request;
- }
-
- /**
- * Includes a file in the method scope and returns
- * public variables and the output buffer
- *
- * @internal
- * @param string $__filename Filename
- * @param array $__vars An array containing key/value pairs that will be transformed to variables accessible inside the file
- * @return array A tuple with the output buffer and the public variables
- */
- private function scoped($__filename, $__vars = array())
- {
- extract((array)$__vars);
- ob_start();
- include($__filename);
- $content = ob_get_clean();
-
- // retreives "public" variables (not prefixed with an underscore)
- $vars = array();
- foreach (get_defined_vars() as $name => $value) {
- if (substr($name, 0, 1) != '_') {
- $vars[$name] = $value;
- }
- }
-
- return array($content, $vars);
- }
-
-
- /* -------------------------------------------------------------------------------------------
- * Actions
- * ------------------------------------------------------------------------------------------ */
-
- /**
- * Executes an action using the executor specified in app/executor
-
- * Tries to execute the action. If this fail, it tries to render the view.
- * If neither of them are found, it will throw an exception.
- *
- * @see Atomik::render()
- * @param string $action The action name. The HTTP method can be suffixed after a dot
- * @param bool|string $viewContext The view context. Set to false to not render the view and return the variables or to true for the request's context
- * @return mixed The output of the view or an array of variables or false if an error occured
- */
- public static function execute($action, $viewContext = true, $vars = array(), $returnBoth = false)
- {
- $view = $action;
- $render = $viewContext !== false;
- $executor = self::get('app/executor', 'Atomik::executeFile');
-
- if (is_bool($viewContext)) {
- // using the request's context
- $viewContext = self::get('app/view_context');
- }
- // appends the context's prefix to the view name
- $prefix = self::get('app/views/contexts/' . $viewContext . '/prefix', $viewContext);
- if (!empty($prefix)) {
- $view .= '.' . $prefix;
- }
-
- // creates the execution context
- $context = array('action' => &$action, 'view' => &$view, 'vars' => &$vars,
- 'render' => &$render, 'executor' => &$executor);
- self::$execContexts[] =& $context;
-
- self::fireEvent('Atomik::Execute::Start', array(&$action, &$context, &$vars));
- if ($action === false) {
- self::trigger404('No action specified');
- }
-
- // checks if the method is specified in $action
- if (($dot = strrpos($action, '.')) !== false) {
- // it is, extract it
- $method = strtolower(substr($action, $dot + 1));
- $action = substr($action, 0, $dot);
- } else {
- // use the current request's http method
- $method = strtolower(self::get('app/http_method'));
- }
- $context['method'] = $method;
-
- self::fireEvent('Atomik::Execute::Before', array(&$action, &$context, &$vars));
-
- $vars = call_user_func($executor, $action, $method, $vars, $context);
-
- $viewFilename = self::viewFilename($view);
- if ($viewFilename === false) {
- if ($vars === false) {
- self::trigger404('No files found associated to the specified action');
- }
- // no view file, disabling view
- $view = false;
- }
-
- if ($vars === false) {
- $vars = array();
- }
-
- self::fireEvent('Atomik::Execute::After', array($action, &$context, &$vars));
-
- // deletes the execution context
- array_pop(self::$execContexts);
-
- // returns $vars if the view should not be rendered
- if ($render === false) {
- return $returnBoth ? array('', $vars) : $vars;
- }
- // no view
- if ($view === false) {
- return $returnBoth ? array('', $vars) : '';
- }
-
- // renders the view associated to the action
- $content = self::render($view, $vars);
- return $returnBoth ? array($content, $vars) : $content;
- }
-
- /**
- * Executor which uses files to define actions
- *
- * Searches for a file called after the action (with the php extension) inside
- * directories set under atomik/dirs/actions
- *
- * The content of this file can be anything.
- *
- * You can create an action file per http method by suffixing the action
- * name by the http method in lower case with a dot separating them.
- * (eg: submit action for POST => submit.post.php)
- * The non-http-method specific file (ie without any suffix) will always
- * be executed before the http-method specific file and variables will
- * be forwarded from one to another.
- *
- * @param string $action
- * @param string $method
- * @param array $context
- * @return array
- */
- public static function executeFile($action, $method, $vars, $context)
- {
- // filenames
- $methodAction = $action . '.' . $method;
- $actionFilename = self::actionFilename($action);
- $methodActionFilename = self::actionFilename($methodAction);
-
- self::fireEvent('Atomik::Executefile', array(&$actionFilename, &$methodActionFilename, &$context));
-
- // checks if at least one of the action files or the view file is defined
- if ($actionFilename === false && $methodActionFilename === false) {
- return false;
- }
-
- $atomik = self::instance();
-
- // executes the global action
- if ($actionFilename !== false) {
- // executes the action in its own scope and fetches defined variables
- list($content, $vars) = $atomik->scoped($actionFilename, $vars);
- echo $content;
- }
-
- // executes the method specific action
- if ($methodActionFilename !== false) {
- // executes the action in its own scope and fetches defined variables
- list($content, $vars) = $atomik->scoped($methodActionFilename, $vars);
- echo $content;
- }
-
- return $vars;
- }
-
- /**
- * Prevents the view of the actionfrom which it's called to be rendered
- */
- public static function noRender()
- {
- if (count(self::$execContexts)) {
- self::$execContexts[count(self::$execContexts) - 1]['view'] = false;
- }
- }
-
- /**
- * Modifies the view associted to the action from which it's called
- *
- * @param string $view View name
- */
- public static function setView($view)
- {
- if (count(self::$execContexts)) {
- self::$execContexts[count(self::$execContexts) - 1]['view'] = $view;
- }
- }
-
- /**
- * Disables the layout
- *
- * @param bool $disable Whether to disable the layout
- */
- public static function disableLayout($disable = true)
- {
- self::set('app/disable_layout', $disable);
- }
-
-
- /* -------------------------------------------------------------------------------------------
- * Views
- * ------------------------------------------------------------------------------------------ */
-
- /**
- * Renders a view
- *
- * Searches for a file called after the view inside
- * directories configured in atomik/dirs/views. If no file is found, an
- * exception is thrown unless $triggerError is false.
- *
- * @param string $view The view name
- * @param array $vars An array containing key/value pairs that will be transformed to variables accessible inside the view
- * @param array $dirs Directories where view files are stored
- * @return string|bool
- */
- public static function render($view, $vars = array(), $dirs = null)
- {
- if ($dirs === null) {
- $dirs = self::get('atomik/dirs/views');
- }
-
- self::fireEvent('Atomik::Render::Start', array(&$view, &$vars, &$dirs, &$triggerError));
-
- // view filename
- if (($filename = self::viewFilename($view, $dirs)) === false) {
- self::trigger404('View ' . $view . ' not found');
- }
-
- self::fireEvent('Atomik::Render::Before', array(&$view, &$vars, &$filename, $triggerError));
-
- $output = self::renderFile($filename, $vars);
-
- self::fireEvent('Atomik::Render::After', array($view, &$output, $vars, $filename, $triggerError));
-
- return $output;
- }
-
- /**
- * Renders a file using a filename which will not be resolved.
- *
- * @param string $filename Filename
- * @param array $vars An array containing key/value pairs that will be transformed to variables accessible inside the file
- * @return string The output of the rendered file
- */
- public static function renderFile($filename, $vars = array())
- {
- self::fireEvent('Atomik::Renderfile::Before', array(&$filename, &$vars));
-
- if (($callback = self::get('app/views/engine', false)) !== false) {
- if (!is_callable($callback)) {
- throw new Atomik_Exception('The specified rendering engine callback cannot be called');
- }
- $output = $callback($filename, $vars);
-
- } else {
- list($output, $vars) = self::instance()->scoped($filename, $vars);
- }
-
- self::fireEvent('Atomik::Renderfile::After', array($filename, &$output, $vars));
-
- return $output;
- }
-
- /**
- * Renders a layout
- *
- * @param string $layout Layout name
- * @param string $content The content that will be available in the layout in the $contentForLayout variable
- * @param array $vars An array containing key/value pairs that will be transformed to variables accessible inside the layout
- * @param array $dirs Directories where to search for layouts
- * @return string
- */
- public static function renderLayout($layout, $content, $vars = array(), $dirs = null)
- {
- if ($dirs === null) {
- $dirs = self::get('atomik/dirs/layouts');
- }
-
- if (is_array($layout)) {
- foreach (array_reverse($layout) as $lay) {
- $content = self::renderLayout($lay, $content, $vars, $dirs);
- }
- return $content;
- }
-
- $appLayout = self::delete('app/layout');
- self::set('app/layout', array($layout));
-
- do {
- $layout = array_shift(self::getRef('app/layout'));
- self::fireEvent('Atomik::Renderlayout', array(&$layout, &$content, &$vars, &$dirs));
- $vars['contentForLayout'] = $content;
- $content = self::render($layout, $vars, $dirs);
- } while (count(self::get('app/layout')));
-
- self::set('app/layout', $appLayout);
- return $content;
- }
-
-
- /* -------------------------------------------------------------------------------------------
- * Helpers
- * ------------------------------------------------------------------------------------------ */
-
- /**
- * Loads an helper file
- *
- * @param string $helperName
- * @param array $dirs Directories where to search for helpers
- */
- public static function loadHelper($helperName, $dirs = null)
- {
- if (isset(self::$loadedHelpers[$helperName])) {
- return;
- }
-
- if ($dirs === null) {
- $dirs = self::get('atomik/dirs/helpers');
- }
-
- self::fireEvent('Atomik::Loadhelper::Before', array(&$helperName, &$dirs));
-
- if (($filename = self::path($helperName . '.php', $dirs)) === false) {
- throw new Atomik_Exception('Helper ' . $helperName . ' not found');
- }
-
- include $filename;
-
- if (!function_exists($helperName)) {
- // searching for an helper defined as a class
- $camelizedHelperName = str_replace(' ', '', ucwords(str_replace('_', ' ', $helperName)));
- $className = $camelizedHelperName . 'Helper';
-
- if (!class_exists($className, false)) {
- // neither a function nor a class has been found
- throw new Exception('Helper ' . $helperName . ' file found but no function or class matching the helper name');
- }
- // helper defined as a class
- self::$loadedHelpers[$helperName] = array(new $className(), $camelizedHelperName);
-
- } else {
- // helper defined as a function
- self::$loadedHelpers[$helperName] = $helperName;
- }
-
- self::fireEvent('Atomik::Loadhelper::After', array($helperName, $dirs));
- }
-
- /**
- * Registers an helper
- *
- * @param string $helperName
- * @param callback $callback
- */
- public static function registerHelper($helperName, $callback)
- {
- self::$loadedHelpers[$helperName] = $callback;
- }
-
- /**
- * Executes an helper
- *
- * @param string $helperName
- * @param array $args Arguments for the helper
- * @param array $dirs Directories where to search for helpers
- * @return mixed
- */
- public static function helper($helperName, $args = array(), $dirs = null)
- {
- self::loadHelper($helperName, $dirs);
- return call_user_func_array(self::$loadedHelpers[$helperName], $args);
- }
-
- /**
- * PHP magic method to handle calls to helper in views
- *
- * @param string $helperName
- * @param array $args
- * @return mixed
- */
- public function __call($helperName, $args)
- {
- if (method_exists('Atomik', $helperName)) {
- return call_user_func_array(array('Atomik', $helperName), $args);
- }
- return self::helper($helperName, $args);
- }
-
- /**
- * PHP > 5.3 magic method to handle calls to undefined method
- * Redirect calls to {@see Atomik::helper()}
- *
- * @param string $helperName
- * @param array $args
- * @return mixed
- */
- public static function __callStatic($helperName, $args)
- {
- return call_user_func(array(self::instance(), 'helper'), $helperName, $args);
- }
-
-
- /* -------------------------------------------------------------------------------------------
- * Accessors
- * ------------------------------------------------------------------------------------------ */
-
- /**
- * Sets a key/value pair in the store
- *
- * If the first argument is an array, values are merged recursively.
- * The array is first dimensionized
- * You can set values from sub arrays by using a path-like key.
- * For example, to set the value inside the array $array[key1][key2]
- * use the key 'key1/key2'
- * Can be used on any array by specifying the third argument
- *
- * @see Atomik::_dimensionizeArray()
- * @param array|string $key Can be an array to set many key/value
- * @param mixed $value
- * @param bool $dimensionize Whether to use Atomik::_dimensionizeArray() on $key
- * @param array $array The array on which the operation is applied
- * @param array $add Whether to add values or replace them
- */
- public static function set($key, $value = null, $dimensionize = true, &$array = null, $add = false)
- {
- // if $data is null, uses the global store
- if ($array === null) {
- $array = &self::$store;
- }
-
- // setting a key directly
- if (is_string($key)) {
- $parentArrayKey = strpos($key, '/') !== false ? dirname($key) : null;
- $key = basename($key);
-
- $parentArray = &self::getRef($parentArrayKey, $array);
- if ($parentArray === null) {
- $dimensionizedParentArray = self::_dimensionizeArray(array($parentArrayKey => null));
- $array = self::_mergeRecursive($array, $dimensionizedParentArray);
- $parentArray = &self::getRef($parentArrayKey, $array);
- }
-
- if ($add !== false) {
- if (!isset($parentArray[$key]) || $parentArray[$key] === null) {
- if (!is_array($value)) {
- $parentArray[$key] = $value;
- return;
- }
- $parentArray[$key] = array();
- } else if (!is_array($parentArray[$key])) {
- $parentArray[$key] = array($parentArray[$key]);
- }
-
- $value = is_array($value) ? $value : array($value);
- if ($add == 'prepend') {
- $parentArray[$key] = array_merge_recursive($value, $parentArray[$key]);
- } else {
- $parentArray[$key] = array_merge_recursive($parentArray[$key], $value);
- }
- } else {
- $parentArray[$key] = $value;
- }
-
- return;
- }
-
- if (!is_array($key)) {
- throw new Atomik_Exception('The first parameter of Atomik::set() must be a string or an array, ' . gettype($key) . ' given');
- }
-
- if ($dimensionize) {
- $key = self::_dimensionizeArray($key);
- }
-
- // merges the store and the array
- if ($add) {
- $array = array_merge_recursive($array, $key);
- } else {
- $array = self::_mergeRecursive($array, $key);
- }
- }
-
- /**
- * Adds a value to the array pointed by the key
- *
- * If the first argument is an array, values are merged recursively.
- * The array is first dimensionized
- * You can add values to sub arrays by using a path-like key.
- * For example, to add a value to the array $array[key1][key2]
- * use the key 'key1/key2'
- * If the value pointed by the key is not an array, it will be
- * transformed to one.
- * Can be used on any array by specifying the third argument
- *
- * @see Atomik::_dimensionizeArray()
- * @param array|string $key Can be an array to add many key/value
- * @param mixed $value
- * @param bool $dimensionize Whether to use Atomik::_dimensionizeArray()
- * @param array $array The array on which the operation is applied
- */
- public static function add($key, $value = null, $dimensionize = true, &$array = null)
- {
- return self::set($key, $value, $dimensionize, $array, 'append');
- }
-
- /**
- * Prependes a value to the array pointed by the key
- *
- * Works the same as add()
- *
- * @see Atomik::add()
- * @param array|string $key Can be an array to add many key/value
- * @param mixed $value
- * @param bool $dimensionize Whether to use Atomik::_dimensionizeArray()
- * @param array $array The array on which the operation is applied
- */
- public static function prepend($key, $value = null, $dimensionize = true, &$array = null)
- {
- return self::set($key, $value, $dimensionize, $array, 'prepend');
- }
-
- /**
- * Like array_merge() but recursively
- *
- * @internal
- * @see array_merge()
- * @param array $array1
- * @param array $array2
- * @return array
- */
- public static function _mergeRecursive($array1, $array2)
- {
- $array = $array1;
- foreach ($array2 as $key => $value) {
- if (is_array($value) && array_key_exists($key, $array1) && is_array($array1[$key])) {
- $array[$key] = self::_mergeRecursive($array1[$key], $value);
- continue;
- }
- $array[$key] = $value;
- }
- return $array;
- }
-
- /**
- * Recursively checks array for path-like keys (ie. keys containing slashes)
- * and transform them into multi dimensions array
- *
- * @internal
- * @param array $array
- * @param string $separator
- * @return array
- */
- public static function _dimensionizeArray($array, $separator = '/')
- {
- $dimArray = array();
-
- foreach ($array as $key => $value) {
- // checks if the key is a path
- if (strpos($key, $separator) !== false) {
- $parts = explode($separator, $key);
- $firstPart = array_shift($parts);
- // recursively dimensionize the key
- $value = self::_dimensionizeArray(array(implode($separator, $parts) => $value), $separator);
-
- if (isset($dimArray[$firstPart])) {
- if (!is_array($dimArray[$firstPart])) {
- // if $firstPart exists but is not an array, drops the value and use an array
- $dimArray[$firstPart] = array();
- }
- // merge recursively both arrays
- $dimArray[$firstPart] = self::_mergeRecursive($dimArray[$firstPart], $value);
- } else {
- $dimArray[$firstPart] = $value;
- }
-
- } else if (is_array($value)) {
- // dimensionize sub arrays
- $value = self::_dimensionizeArray($value, $separator);
- if (isset($dimArray[$key])) {
- $dimArray[$key] = self::_mergeRecursive($dimArray[$key], $value);
- } else {
- $dimArray[$key] = $value;
- }
- } else {
- $dimArray[$key] = $value;
- }
- }
-
- return $dimArray;
- }
-
- /**
- * Gets a value using its associatied key from the store
- *
- * You can fetch value from sub arrays by using a path-like
- * key. Separate each key with a slash. For example if you want
- * to fetch the value from an $store[key1][key2][key3] you can use
- * key1/key2/key3
- * Can be used on any array by specifying the third argument
- *
- * @param string|array $key The configuration key which value should be returned. If null, fetches all values
- * @param mixed $default Default value if the key is not found
- * @param array $array The array on which the operation is applied
- * @return mixed
- */
- public static function get($key = null, $default = null, $array = null)
- {
- // checks if a namespace is used
- if (is_string($key) && preg_match('/^([a-z]+):(.*)/', $key, $match)) {
- // checks if the namespace exists */
- if (isset(self::$namespaces[$match[1]])) {
- // calls the namespace callback and returns
- $args = func_get_args();
- $args[0] = $match[2];
- return call_user_func_array(self::$namespaces[$match[1]], $args);
- }
- }
-
- if (($value = self::getRef($key, $array)) !== null) {
- return $value;
- }
-
- // key not found, returns default
- return $default;
- }
-
- /**
- * Checks if a key is defined in the store
- *
- * Can check through sub array using a path-like key
- * Can be used on any array by specifying the second argument
- *
- * @see Atomik::get()
- * @param string $key The key which should be deleted
- * @param array $array The array on which the operation is applied
- * @return bool
- */
- public static function has($key, $array = null)
- {
- return self::getRef($key, $array) !== null;
- }
-
- /**
- * Deletes a key from the store
- *
- * Can delete through sub array using a path-like key
- * Can be used on any array by specifying the second argument
- *
- * @see Atomik::get()
- * @param string $key
- * @param array $array The array on which the operation is applied
- * @return mixed The deleted value
- */
- public static function delete($key, &$array = null)
- {
- $parentArrayKey = strpos($key, '/') !== false ? dirname($key) : null;
- $key = basename($key);
- $parentArray = &self::getRef($parentArrayKey, $array);
-
- if ($parentArray === null || !array_key_exists($key, $parentArray)) {
- throw new Atomik_Exception('Key "' . $key . '" does not exists');
- }
-
- $value = $parentArray[$key];
- unset($parentArray[$key]);
- return $value;
- }
-
- /**
- * Gets a reference to a value from the store using its associatied key
- *
- * You can fetch value from sub arrays by using a path-like
- * key. Separate each key with a slash. For example if you want
- * to fetch the value from an $store[key1][key2][key3] you can use
- * key1/key2/key3
- * Can be used on any array by specifying the second argument
- *
- * @param string|array $key The configuration key which value should be returned. If null, fetches all values
- * @param array $array The array on which the operation is applied
- * @return mixed Null if the key does not match
- */
- public static function &getRef($key = null, &$array = null)
- {
- $null = null;
-
- // returns the store
- if ($array === null) {
- $array = &self::$store;
- }
-
- // return the whole arrat
- if ($key === null) {
- return $array;
- }
-
- // checks if the $key is an array
- if (!is_array($key)) {
- // checks if it has slashes
- if (!strpos($key, '/')) {
- if (array_key_exists($key, $array)) {
- $value =& $array[$key];
- return $value;
- }
- return $null;
- }
- // creates an array by spliting using slashes
- $key = explode('/', $key);
- }
-
- // checks if the key exists
- $firstKey = array_shift($key);
- if (array_key_exists($firstKey, $array)) {
- if (count($key) > 0) {
- // there's still keys so it goes deeper
- return self::getRef($key, $array[$firstKey]);
- } else {
- // the key has been found
- $value =& $array[$firstKey];
- return $value;
- }
- }
-
- return $null;
- }
-
- /**
- * Resets the global store
- *
- * If no argument are specified the store is resetted, otherwise value are set normally and the
- * state is saved.
- *
- * @internal
- * @see Atomik::set()
- * @param array|string $key Can be an array to set many key/value
- * @param mixed $value
- * @param bool $dimensionize Whether to use Atomik::_dimensionizeArray() on $key
- */
- public static function reset($key = null, $value = null, $dimensionize = true)
- {
- if ($key !== null) {
- self::set($key, $value, $dimensionize, self::$reset);
- self::set($key, $value, $dimensionize);
- return;
- }
-
- // reset
- self::$store = self::_mergeRecursive(self::$store, self::$reset);
- }
-
- /**
- * Registers a new selector namespace
- *
- * A namespace preceed a key. When used, $callback will be
- * called instead of the normal logic. Applies only on get() calls.
- *
- * @param string $namespace
- * @param callback $callback
- */
- public static function registerSelector($namespace, $callback)
- {
- self::$namespaces[$namespace] = $callback;