PageRenderTime 82ms CodeModel.GetById 4ms app.highlight 64ms RepoModel.GetById 1ms app.codeStats 1ms

/index.php

https://bitbucket.org/fbertagnin/fbwork4
PHP | 2874 lines | 1506 code | 370 blank | 998 comment | 357 complexity | f3d42b366d37edb6d208f40583a74f9c MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2/**
   3 * Atomik Framework
   4 * Copyright (c) 2008-2009 Maxime Bouroumeau-Fuseau
   5 *
   6 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   7 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   8 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   9 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  10 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  11 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  12 * THE SOFTWARE.
  13 *
  14 * @package     Atomik
  15 * @author      Maxime Bouroumeau-Fuseau
  16 * @copyright   2008-2009 (c) Maxime Bouroumeau-Fuseau
  17 * @license     http://www.opensource.org/licenses/mit-license.php
  18 * @link        http://www.atomikframework.com
  19 */
  20
  21define('ATOMIK_VERSION', '2.2.2');
  22!defined('ATOMIK_APP_ROOT') && define('ATOMIK_APP_ROOT', './app');
  23
  24/* -------------------------------------------------------------------------------------------
  25 *  APPLICATION CONFIGURATION
  26 * ------------------------------------------------------------------------------------------ */
  27
  28Atomik::reset(array(
  29
  30    'app' => array(
  31    
  32        /* @var string */
  33        'default_action'        => 'index',
  34
  35        /* The name of the layout
  36         * Add multiple layouts using an array (will be rendered in reverse order)
  37         * @var array|bool|string */
  38        'layout'                => false,
  39    
  40        /* @var bool */
  41        'disable_layout'        => false,
  42        
  43        /* An array where keys are route names and their value is an associative
  44         * array of default values
  45         * @see Atomik::route()
  46         * @var array */
  47        'routes'                => array(),
  48    
  49        /* @var bool */
  50        'force_uri_extension'   => false,
  51            
  52        /* List of escaping profiles where keys are profile names and their
  53         * value an array of callbacks
  54         * @see Atomik::escape()
  55         * @var array */
  56        'escaping' => array(
  57            'default'           => array('htmlspecialchars', 'nl2br')
  58        ),
  59    
  60        /* @see Atomik::filter()
  61         * @var array */
  62        'filters' => array(
  63        
  64            /* @var array */
  65            'rules'             => array(),
  66            
  67            /* @var array */
  68            'callbacks'         => array(),
  69            
  70            /* @var string */
  71            'default_message'   => 'The %s field failed to validate',
  72            
  73            /* @var string */
  74            'required_message'  => 'The %s field must be filled'
  75        ),
  76    
  77        /* @see Atomik::render()
  78         * @var array */
  79        'views' => array(
  80        
  81            /* @var string */
  82            'file_extension'     => '.phtml',
  83            
  84            /* Alternative rendering engine
  85             * @see Atomik::_render()
  86             * @var callback */
  87            'engine'             => false,
  88            
  89            /* @var string */
  90            'default_context'    => 'html',
  91            
  92            /* The GET parameter to retrieve the current context
  93             * @var string */
  94            'context_param'      => 'format',
  95            
  96            /* List of contexts where keys are the context name.
  97             * Contexts can specify:
  98             *  - prefix (string): the view filename's extension prefix
  99             *  - layout (bool): whether the layout should be rendered
 100             *  - content_type (string): the HTTP response content type
 101             * @var array */
 102            'contexts' => array(
 103                'html' => array(
 104                    'prefix'         => '',
 105                    'layout'         => true,
 106                    'content_type'   => 'text/html'
 107                ),
 108                'ajax' => array(
 109                    'prefix'         => '',
 110                    'layout'         => false,
 111                    'content_type'   => 'text/html'
 112                ),
 113                'xml' => array(
 114                    'prefix'         => 'xml',
 115                    'layout'         => false,
 116                    'content_type'   => 'text/xml'
 117                ),
 118                'json' => array(
 119                    'prefix'         => 'json',
 120                    'layout'         => false,
 121                    'content_type'   => 'application/json'
 122                )
 123            )
 124        ),
 125        
 126        /* A parameter in the route that will allow to specify the http method 
 127         * (override the request's method). False to disable
 128         * @var string */
 129        'http_method_param'       => '_method',
 130        
 131        /* @var array */
 132        'allowed_http_methods'    => array('GET', 'POST', 'PUT', 'DELETE', 'TRACE', 'HEAD', 'OPTIONS', 'CONNECT')
 133        
 134     )       
 135));
 136
 137/* -------------------------------------------------------------------------------------------
 138 *  CORE CONFIGURATION
 139 * ------------------------------------------------------------------------------------------ */
 140
 141Atomik::set(array(
 142
 143    /* @var array */
 144    'plugins'                    => array(),
 145
 146    /* @var array */
 147    'atomik' => array(
 148
 149        /* Atomik's filename
 150         * @var string */
 151        'scriptname'			 => __FILE__,
 152    
 153        /* Base url, set to null for auto detection
 154         * @var string */
 155        'base_url'               => null,
 156        
 157        /* Whether url rewriting is activated on the server
 158         * @var bool */
 159        'url_rewriting'          => false,
 160    
 161        /* @var bool */
 162        'debug'                  => false,
 163    
 164        /* The GET parameter used to retreive the action
 165         * @var string */
 166        'trigger'                => 'action',
 167
 168        /* Whether to register the class autoloader
 169         * @var bool */
 170        'class_autoload'         => true,
 171        
 172        /* @var bool */
 173        'start_session'          => true,
 174        
 175        /* Plugin's assets path template. 
 176         * %s will be replaced by the plugin's name
 177         * @see Atomik::pluginAsset()
 178         * @var string */
 179        'plugin_assets_tpl'      => 'app/plugins/%s/assets/',
 180
 181        /* @var array */
 182        'log' => array(
 183        
 184            /* @var bool */
 185            'register_default'   => false,
 186            
 187            /* From which level to start logging messages
 188             * @var int */
 189            'level'              => LOG_WARNING,
 190            
 191            /* Message template for the default logger
 192             * @see Atomik::logToFile()
 193             * @var string */
 194            'message_template'   => '[%date%] [%level%] %message%'
 195        ),
 196    
 197        /* @var array */
 198        'dirs' => array(
 199            'app'                => ATOMIK_APP_ROOT,
 200            'plugins'            => array(ATOMIK_APP_ROOT . '/modules', ATOMIK_APP_ROOT . '/plugins/'),
 201            'actions'            => ATOMIK_APP_ROOT . '/actions/',
 202            'views'              => ATOMIK_APP_ROOT . '/views/',
 203            'layouts'            => array(ATOMIK_APP_ROOT . '/layouts', ATOMIK_APP_ROOT . '/views'),
 204            'helpers'            => ATOMIK_APP_ROOT . '/helpers/',
 205            'includes'           => array(ATOMIK_APP_ROOT . '/includes/', ATOMIK_APP_ROOT . '/libraries/'),
 206            'overrides'          => ATOMIK_APP_ROOT . '/overrides/'
 207        ),
 208    
 209        /* @var array */
 210        'files' => array(
 211            'index'              => 'index.php',
 212            'config'             => ATOMIK_APP_ROOT . '/config', // without extension
 213            'bootstrap'          => ATOMIK_APP_ROOT . '/bootstrap.php',
 214            'pre_dispatch'       => ATOMIK_APP_ROOT . '/pre_dispatch.php',
 215            'post_dispatch'      => ATOMIK_APP_ROOT . '/post_dispatch.php',
 216            '404'                => ATOMIK_APP_ROOT . '/404.php',
 217            'error'              => ATOMIK_APP_ROOT . '/error.php',
 218            'log'                => ATOMIK_APP_ROOT . '/log.txt'
 219        ),
 220        
 221        /* @var bool */
 222        'catch_errors'           => false,
 223        
 224        /* @var bool */
 225        'display_errors'         => true,
 226        
 227        /* @var array */
 228        'error_report_attrs'     => array(
 229            'atomik-error'               => 'style="padding: 10px"',
 230            'atomik-error-title'         => 'style="font-size: 1.3em; font-weight: bold; color: #FF0000"',
 231            'atomik-error-lines'         => 'style="width: 100%; margin-bottom: 20px; background-color: #fff;'
 232                                          . 'border: 1px solid #000; font-size: 0.8em"',
 233            'atomik-error-line'          => '',
 234            'atomik-error-line-error'    => 'style="background-color: #ffe8e7"',
 235            'atomik-error-line-number'   => 'style="background-color: #eeeeee"',
 236            'atomik-error-line-text'     => '',
 237            'atomik-error-stack'         => ''
 238        )
 239        
 240    ),
 241    
 242    /* @var int */
 243    'start_time' => time() + microtime()
 244));
 245
 246/* -------------------------------------------------------------------------------------------
 247 *  CORE
 248 * ------------------------------------------------------------------------------------------ */
 249
 250// creates the A function (shortcut to Atomik::get)
 251if (!function_exists('A')) {
 252    /**
 253     * Shortcut function to Atomik::get()
 254     * Useful when dealing with selectors
 255     *
 256     * @see Atomik::get()
 257     * @return mixed
 258     */
 259    function A()
 260    {
 261        $args = func_get_args();
 262        return call_user_func_array(array('Atomik', 'get'), $args);
 263    }
 264}
 265
 266// starts Atomik unless ATOMIK_AUTORUN is set to false
 267if (!defined('ATOMIK_AUTORUN') || ATOMIK_AUTORUN === true) {
 268    Atomik::run();
 269}
 270
 271/**
 272 * Exception class for Atomik
 273 * 
 274 * @package Atomik
 275 */
 276class Atomik_Exception extends Exception {}
 277
 278/**
 279 * Atomik Framework Main class
 280 *
 281 * @package Atomik
 282 */
 283final class Atomik
 284{
 285    /**
 286     * Global store
 287     * 
 288     * This property is used to stored all data accessed using get(), set()...
 289     *
 290     * @var array
 291     */
 292    public static $store = array();
 293    
 294    /**
 295     * Global store to reset to
 296     * 
 297     * @var array
 298     */
 299    private static $reset = array();
 300    
 301    /**
 302     * Loaded plugins
 303     * 
 304     * When a plugin is loaded, its name is saved in this array to 
 305     * avoid loading it twice.
 306     *
 307     * @var array
 308     */
 309    private static $plugins = array();
 310    
 311    /**
 312     * Registered events
 313     * 
 314     * The array keys are event names and their value is an array with 
 315     * the event callbacks
 316     *
 317     * @var array
 318     */
 319    private static $events = array();
 320    
 321    /**
 322     * Selectors namespaces
 323     * 
 324     * The array keys are the namespace name and the associated value is
 325     * the callback to call when the namespace is used
 326     *
 327     * @var array
 328     */
 329    private static $namespaces = array('flash' => array('Atomik', '_getFlashMessages'));
 330    
 331    /**
 332     * Execution contexts
 333     * 
 334     * Each call to Atomik::execute() creates a context.
 335     * 
 336     * @var array
 337     */
 338    private static $execContexts = array();
 339    
 340    /**
 341     * Pluggable applications
 342     * 
 343     * @var array
 344     */
 345    private static $pluggableApplications = array();
 346    
 347    /**
 348     * Registered methods
 349     * 
 350     * @var array
 351     */
 352    private static $methods = array();
 353    
 354    /**
 355     * Already loaded helpers
 356     * 
 357     * @var array
 358     */
 359    private static $loadedHelpers = array();
 360    
 361    /**
 362     * Starts Atomik
 363     * 
 364     * If dispatch is false, you will have to manually dispatch the request and exit.
 365     * 
 366     * @param string $uri
 367     * @param bool $dispatch Whether to dispatch
 368     */
 369    public static function run($uri = null, $dispatch = true)
 370    {
 371        // wrap the whole app inside a try/catch block to catch all errors
 372        try {
 373            @chdir(dirname(A('atomik/scriptname')));
 374             
 375            // loads the config file
 376            if (file_exists($filename = self::get('atomik/files/config') . '.php')) {
 377                // PHP
 378                if (is_array($config = include($filename))) {
 379                    self::set($config);
 380                }
 381                
 382            } else if (file_exists($filename = self::get('atomik/files/config') . '.ini')) {
 383                // INI
 384                if (($data = parse_ini_file($filename, true)) === false) {
 385                    throw new Atomik_Exception('INI configuration malformed');
 386                }
 387                self::set(self::_dimensionizeArray($data, '.'), null, false);
 388                
 389            } else if (file_exists($filename = self::get('atomik/files/config') . '.json')) {
 390                // JSON
 391                if (($config = json_decode(file_get_contents($filename), true)) === null) {
 392                    throw new Atomik_Exception('JSON configuration malformed');
 393                }
 394                self::set($config);
 395            }
 396            
 397            // adds includes dirs to php include path
 398            $includePaths = self::path(self::get('atomik/dirs/includes', array()), true);
 399            $includePaths[] = get_include_path();
 400            set_include_path(implode(PATH_SEPARATOR, $includePaths));
 401            
 402            // registers the error handler
 403            if (self::get('atomik/catch_errors', true) == true) {
 404                set_error_handler('Atomik::_errorHandler');
 405            }
 406            
 407            // sets the error reporting to all errors if debug mode is on
 408            if (self::get('atomik/debug', false) == true) {
 409                error_reporting(E_ALL | E_STRICT);
 410            }
 411            
 412            // default logger
 413            if (self::get('atomik/log/register_default', false) == true) {
 414                self::listenEvent('Atomik::Log', 'Atomik::logToFile');
 415            }
 416            
 417            // starts the session
 418            if (self::get('atomik/start_session', true) == true) {
 419                session_start();
 420                self::$store['session'] = &$_SESSION;
 421            }
 422            
 423            // registers the class autoload handler
 424            if (self::get('atomik/class_autoload', true) == true) {
 425                if (!function_exists('spl_autoload_register')) {
 426                    throw new Atomik_Exception('Missing spl_autoload_register function');
 427                }
 428                spl_autoload_register('Atomik::autoload');
 429            }
 430        
 431            // cleans the plugins array
 432            $plugins = array();
 433            foreach (self::get('plugins', array()) as $key => $value) {
 434                if (!is_string($key)) {
 435                    $key = $value;
 436                    $value = array();
 437                }
 438                $plugins[ucfirst($key)] = (array) $value;
 439            }
 440            self::set('plugins', $plugins, false);
 441            
 442            // loads plugins
 443            // this method allows plugins that are being loaded to modify the plugins array
 444            $disabledPlugins = array();
 445            while (count($pluginsToLoad = array_diff(array_keys(self::get('plugins')), self::getLoadedPlugins(), $disabledPlugins)) > 0) {
 446                foreach ($pluginsToLoad as $plugin) {
 447                    if (self::loadPlugin($plugin) === false) {
 448                        $disabledPlugins[] = $plugin;
 449                    }
 450                }
 451            }
 452             
 453            // loads bootstrap file
 454            if (file_exists($filename = self::get('atomik/files/bootstrap'))) {
 455                require($filename);
 456            }
 457        
 458            // core is starting
 459            self::fireEvent('Atomik::Start', array(&$cancel));
 460            if ($cancel) {
 461                self::end(true);
 462            }
 463            self::log('Starting', LOG_DEBUG);
 464        
 465            // checks if url rewriting is used
 466            if (!self::has('atomik/url_rewriting')) {
 467                self::set('atomik/url_rewriting', isset($_SERVER['REDIRECT_URL']) || isset($_SERVER['REDIRECT_URI']));
 468            }
 469            
 470            // dispatches
 471            if ($dispatch) {
 472                if (!self::dispatch($uri)) {
 473                    self::trigger404();
 474                }
 475                // end
 476                self::end(true);
 477            }
 478            
 479        } catch (Exception $e) {
 480            self::log('Exception caught: ' . $e->getMessage(), LOG_ERR);
 481            
 482            // checks if we really want to catch errors
 483            if (!self::get('atomik/catch_errors', true)) {
 484                throw $e;
 485            }
 486            
 487            self::fireEvent('Atomik::Error', array($e));
 488            header('Location: ', false, 500);
 489            self::renderException($e);
 490            self::end(false);
 491        }
 492    }
 493    
 494    /**
 495     * Dispatches the request
 496     * 
 497     * It takes an URI, applies routes, executes the action and renders the view.
 498     * If $uri is null, the value of the GET parameter specified as the trigger 
 499     * will be used.
 500     * 
 501     * @param string $uri
 502     * @param bool $allowPluggableApplication Whether to allow plugin application to be loaded
 503     */
 504    public static function dispatch($uri = null, $allowPluggableApplication = true)
 505    {
 506        self::fireEvent('Atomik::Dispatch::Start', array(&$uri, &$allowPluggableApplication, &$cancel));
 507        if ($cancel) {
 508            return true;
 509        }
 510        
 511        // checks if it's needed to auto discover the uri
 512        if ($uri === null) {
 513            
 514            // retreives the requested uri
 515            $trigger = self::get('atomik/trigger', 'action');
 516            if (isset($_GET[$trigger]) && !empty($_GET[$trigger])) {
 517                $uri = trim($_GET[$trigger], '/');
 518            }
 519    
 520            // retreives the base url
 521            if (self::get('atomik/base_url', null) === null) {
 522                if (self::get('atomik/url_rewriting') && (isset($_SERVER['REDIRECT_URL']) || isset($_SERVER['REDIRECT_URI']))) {
 523                    // finds the base url from the redirected url
 524                    $redirectUrl = isset($_SERVER['REDIRECT_URL']) ? $_SERVER['REDIRECT_URL'] : $_SERVER['REDIRECT_URI'];
 525                    self::set('atomik/base_url', substr($redirectUrl, 0, -strlen($_GET[$trigger])));
 526                } else {
 527                    // finds the base url from the script name
 528                    self::set('atomik/base_url', rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\') . '/');
 529                }
 530            }
 531            
 532        } else {
 533            // sets the user defined request
 534            // retreives the base url
 535            if (self::get('atomik/base_url', null) === null) {
 536                // finds the base url from the script name
 537                self::set('atomik/base_url', rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\') . '/');
 538            }
 539        }
 540        
 541        // default uri
 542        if (empty($uri)) {
 543            $uri = self::get('app/default_action', 'index');
 544        }
 545        
 546        // routes the request
 547        if (($request = self::route($uri, $_GET)) === false) {
 548            return false;
 549        }
 550            
 551        // checking if no dot are in the action name to avoid any hack attempt and if no 
 552        // underscore is use as first character in a segment
 553        if (strpos($request['action'], '..') !== false || substr($request['action'], 0, 1) == '_' 
 554            || strpos($request['action'], '/_') !== false) {
 555                return false;
 556        }
 557        
 558        self::set('request_uri', $uri);
 559        self::set('request', $request);
 560        if (!self::has('full_request_uri')) {
 561            self::set('full_request_uri', $uri);
 562        }
 563        
 564        self::fireEvent('Atomik::Dispatch::Uri', array(&$uri, &$request, &$cancel));
 565        if ($cancel) {
 566            return true;
 567        }
 568        
 569        // checks if the uri triggers a pluggable application
 570        if ($allowPluggableApplication) {
 571            foreach (self::$pluggableApplications as $plugin => $pluggAppConfig) {
 572                if (!self::uriMatch($pluggAppConfig['route'], $uri)) {
 573                    continue;
 574                }
 575                
 576                // rewrite uri
 577                $baseAction = trim($pluggAppConfig['route'], '/*');
 578                $uri = substr(trim($uri, '/'), strlen($baseAction));
 579                if ($uri == self::get('app/default_action')) {
 580                    $uri = '';
 581                }
 582                self::set('atomik/base_action', $baseAction);
 583                
 584                // dispatches the pluggable application
 585                return self::dispatchPluggableApplication($plugin, $uri, $pluggAppConfig['config']);
 586            }
 587        }
 588        
 589        // fetches the http method
 590        $httpMethod = $_SERVER['REQUEST_METHOD'];
 591        if (($param = self::get('app/http_method_param', false)) !== false) {
 592            // checks if the route parameter to override the method is defined
 593            $httpMethod = self::get($param, $httpMethod, $request);
 594        }
 595        if (!in_array($httpMethod, self::get('app/allowed_http_methods'))) {
 596            // specified method not allowed
 597            return false;
 598        }
 599        self::set('app/http_method', strtoupper($httpMethod));
 600        
 601        // fetches the view context
 602        $viewContext = self::get(self::get('app/views/context_param', 'format'), 
 603                            self::get('app/views/default_context', 'html'), $request);
 604        self::set('app/view_context', $viewContext);
 605        
 606        // retreives view context params and prepare the response
 607        if (($viewContextParams = self::get('app/views/contexts/' . $viewContext, false)) !== false) {
 608            if ($viewContextParams['layout'] !== true) {
 609                self::set('app/layout', $viewContextParams['layout']);
 610            }
 611            header('Content-type: ' . self::get('content-type', 'text/html', $viewContextParams));
 612        }
 613    
 614        // configuration is ok, ready to dispatch
 615        self::fireEvent('Atomik::Dispatch::Before', array(&$cancel));
 616        if ($cancel) {
 617            return true;
 618        }
 619        
 620        self::log('Dispatching action ' . $request['action'], LOG_DEBUG);
 621    
 622        // pre dispatch action
 623        if (file_exists($filename = self::get('atomik/files/pre_dispatch'))) {
 624            require($filename);
 625        }
 626    
 627        // executes the action
 628        ob_start();
 629        if (($content = self::execute(self::get('request/action'), $viewContext, false)) === false) {
 630            return false;
 631        }
 632        $content = ob_get_clean() . $content;
 633        
 634        // renders the layouts if enable
 635        if (($layouts = self::get('app/layout', false)) !== false) {
 636            if (!empty($layouts) && !self::get('app/disable_layout', false)) {
 637                foreach (array_reverse((array) $layouts) as $layout) {
 638                    $content = self::renderLayout($layout, $content);
 639                }
 640            }
 641        }
 642        
 643        // echoes the content
 644        self::fireEvent('Atomik::Output::Before', array(&$content));
 645        echo $content;
 646        self::fireEvent('Atomik::Output::After', array($content));
 647    
 648        // dispatch done
 649        self::fireEvent('Atomik::Dispatch::After');
 650    
 651        // post dispatch action
 652        if (file_exists($filename = self::get('atomik/files/post_dispatch'))) {
 653            require($filename);
 654        }
 655        
 656        return true;
 657    }
 658    
 659    /**
 660     * Checks if an uri matches the pattern. The pattern can contain the * wildcard at the
 661     * end to specify that it matches the target and all its child segments.
 662     * 
 663     * @param string $pattern
 664     * @param string $uri Default is the current request uri
 665     * @return bool
 666     */
 667    public static function uriMatch($pattern, $uri = null)
 668    {
 669        if ($uri === null) {
 670            $uri = self::get('request_uri');
 671        }
 672        $uri = trim($uri, '/');
 673        $pattern = trim($pattern, '/');
 674        
 675        if (substr($pattern, -1) == '*') {
 676            $pattern = rtrim($pattern, '/*');
 677            return strlen($uri) >= strlen($pattern) && substr($uri, 0, strlen($pattern)) == $pattern;
 678        } else {
 679            return $uri == $pattern;
 680        }
 681    }
 682    
 683    /**
 684     * Parses an uri to extract parameters
 685     * 
 686     * Routes defines how to extract parameters from an uri. They can
 687     * have additional default parameters.
 688     * There are two kind of routes:
 689     *
 690     *  - segments: 
 691     *    the uri is divided into path segments. Each segment can be
 692     *    either static or a parameter (indicated by :).
 693     *    eg: /archives/:year/:month
 694     *
 695     *  - regexp:
 696     *    uses a regexp against the uri. Must be enclosed using # instead of
 697     *    slashes parameters must be specified as named subpattern.
 698     *    eg: #^archives/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})$#
 699     *
 700     * If no route matches, the default route (ie :action) will automatically be used.
 701     * Unless you're using regexps, any additional segments will be added as parameters
 702     * eg: /archives/2009/01/id/1, will also have the id=1 parameter
 703     * 
 704     * You can also name your routes using the @name parameter (which won't be included
 705     * in the returned params). Named route can then be use with Atomik::url()
 706     *
 707     * @param string $uri
 708     * @param array $params    Additional parameters which are not in the uri
 709     * @param array $routes    Uses app/routes if null
 710     * @return array|boolean   Route parameters or false if it fails
 711     */
 712    public static function route($uri, $params = array(), $routes = null)
 713    {
 714        if ($routes === null) {
 715            $routes = self::get('app/routes');
 716        }
 717        
 718        self::fireEvent('Atomik::Router::Start', array(&$uri, &$routes, &$params));
 719        
 720        // extracts uri information
 721        $components = parse_url($uri);
 722        $uri = trim($components['path'], '/');
 723        $uriSegments = explode('/', $uri);
 724        $uriExtension = false;
 725        if (isset($components['query'])) {
 726            parse_str($components['query'], $query);
 727            $params = array_merge($query, $params);
 728        }
 729        
 730        // extract the file extension from the uri
 731        $lastSegment = array_pop($uriSegments);
 732        if (($dot = strrpos($lastSegment, '.')) !== false) {
 733            $uriExtension = substr($lastSegment, $dot + 1);
 734            $lastSegment = substr($lastSegment, 0, $dot);
 735        }
 736        $uriSegments[] = $lastSegment;
 737        
 738        // checks if the extension must be present
 739        if (self::get('app/force_uri_extension', false) && $uriExtension === false) {
 740            return false;
 741        }
 742        
 743        // searches for a route matching the uri
 744        $found = false;
 745        $request = array();
 746        foreach ($routes as $route => $default) {
 747            if (!is_string($route)) {
 748                $route = $default;
 749                $default = array();
 750            }
 751            
 752            // removes the route name from the default params
 753            if (isset($default['@name'])) {
 754            	unset($default['@name']);
 755            }
 756            
 757            // regexp
 758            if ($route{0} == '#') {
 759                if (!preg_match($route, $uri, $matches)) {
 760                    continue;
 761                }
 762                unset($matches[0]);
 763                $found = true;
 764                $request = array_merge($default, $matches);
 765                break;
 766            }
 767            
 768            $valid = true;
 769            $segments = explode('/', trim($route, '/'));
 770            $request = $default;
 771            $extension = false;
 772            
 773            // extract the file extension from the route
 774            $lastSegment = array_pop($segments);
 775            if (($dot = strrpos($lastSegment, '.')) !== false) {
 776                $extension = substr($lastSegment, $dot + 1);
 777                $lastSegment = substr($lastSegment, 0, $dot);
 778            }
 779            $segments[] = $lastSegment;
 780            
 781            // checks the extension
 782            if ($extension !== false) {
 783                if ($extension{0} == ':') {
 784                    // extension is a parameter
 785                    if ($uriExtension !== false) {
 786                        $request[substr($extension, 1)] = $uriExtension;
 787                    } else if (!isset($request[substr($extension, 1)])) {
 788                        // no uri extension and no default value
 789                        continue;
 790                    }
 791                } else if ($extension != $uriExtension) {
 792                    continue;
 793                }
 794            }
 795            
 796            for ($i = 0, $count = count($segments); $i < $count; $i++) {
 797                if (substr($segments[$i], 0, 1) == ':') {
 798                    // segment is a parameter
 799                    if (isset($uriSegments[$i])) {
 800                        // this segment is defined in the uri
 801                        $request[substr($segments[$i], 1)] = $uriSegments[$i];
 802                        $segments[$i] = $uriSegments[$i];
 803                    } else if (!array_key_exists(substr($segments[$i], 1), $default)) {
 804                        // not defined in the uri and no default value
 805                        $valid = false;
 806                        break;
 807                    }
 808                } else {
 809                    // fixed segment
 810                    if (!isset($uriSegments[$i]) || $uriSegments[$i] != $segments[$i]) {
 811                        $valid = false;
 812                        break;
 813                    }
 814                }
 815            }
 816            
 817            // checks if route is valid and if the action param is set
 818            if ($valid && isset($request['action'])) {
 819                $found = true;
 820                // if there's remaining segments in the uri, adding them as params
 821                if (($count = count($uriSegments)) > ($start = count($segments))) {
 822                    for ($i = $start; $i < $count; $i += 2) {
 823                        if (isset($uriSegments[$i + 1])) {
 824                            $request[$uriSegments[$i]] = $uriSegments[$i + 1];
 825                        }
 826                    }
 827                }
 828                break;
 829            }
 830        }
 831        
 832        if (!$found) {
 833            // route not found, creating default route
 834            $request = array(
 835                'action' => implode('/', $uriSegments), 
 836                self::get('app/views/context_param', 'format') => $uriExtension === false ? 
 837                    self::get('app/views/default_context', 'html') : $uriExtension
 838            );
 839        }
 840        
 841        $request = array_merge($params, $request);
 842        self::fireEvent('Atomik::Router::End', array($uri, &$request));
 843        
 844        return $request;
 845    }
 846    
 847    /**
 848     * Executes an action
 849     * 
 850     * Searches for a file called after the action (with the php extension) inside
 851     * directories set under atomik/dirs/actions. If no file is found, it will search
 852     * for a view and render it. If neither of them are found, it will throw an exception.
 853     *
 854     * @see Atomik::render()
 855     * @param string $action            The action name. The HTTP method can be prefixed after a dot
 856     * @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
 857     * @param bool $triggerError        Whether to throw an exception if an error occurs
 858     * @return mixed                    The output of the view or an array of variables or false if an error occured
 859     */
 860    public static function execute($action, $viewContext = true, $triggerError = true)
 861    {
 862        $view = $action;
 863        $vars = array();
 864        $render = $viewContext !== false;
 865        
 866        if (is_bool($viewContext)) {
 867            // using the request's context
 868            $viewContext = self::get('app/view_context');
 869        }
 870        // appends the context's prefix to the view name
 871        $prefix = self::get('app/views/contexts/' . $viewContext . '/prefix', $viewContext);
 872        if (!empty($prefix)) {
 873            $view .= '.' . $prefix;
 874        }
 875        
 876        // creates the execution context
 877        $context = array('action' => &$action, 'view' => &$view, 'render' => &$render);
 878        self::$execContexts[] =& $context;
 879    
 880        self::fireEvent('Atomik::Execute::Start', array(&$action, &$context, &$triggerError));
 881        if ($action === false) {
 882            return false;
 883        }
 884        
 885        // checks if the method is specified in $action
 886        if (($dot = strrpos($action, '.')) !== false) {
 887            // it is, extract it
 888            $methodAction = $action;
 889            $method = substr($action, $dot + 1);
 890            $action = substr($action, 0, $dot);
 891        } else {
 892            // use the current request's http method
 893            $method = strtolower(self::get('app/http_method'));
 894            $methodAction = $action . '.' . $method;
 895        }
 896        
 897        // filenames
 898        $actionFilename = self::actionFilename($action);
 899        $methodActionFilename = self::actionFilename($methodAction);
 900        $viewFilename = self::viewFilename($view);
 901        
 902        // checks if at least one of the action files or the view file is defined
 903        if ($actionFilename === false && $methodActionFilename === false && $viewFilename === false) {
 904            if ($triggerError) {
 905                throw new Atomik_Exception('Action ' . $action . ' does not exist');
 906            }
 907            return false;
 908        }
 909        
 910        if ($viewFilename === false) {
 911            // no view files, disabling view
 912            $view = false;
 913        }
 914    
 915        self::fireEvent('Atomik::Execute::Before', array(&$action, &$context, &$actionFilename, &$methodActionFilename, &$triggerError));
 916    
 917        // class name if using a class
 918        $className = str_replace(' ', '_', ucwords(str_replace('/', ' ', $action)));
 919            
 920        // executes the global action
 921        if ($actionFilename !== false) {
 922            // executes the action in its own scope and fetches defined variables
 923            $vars = self::executeFile($actionFilename, array(), $className . 'Action');
 924        }
 925        
 926        // executes the method specific action
 927        if ($methodActionFilename !== false) {
 928            // executes the action in its own scope and fetches defined variables
 929            $vars = self::executeFile($methodActionFilename, $vars, $className . ucfirst($method) . 'Action');
 930        }
 931    
 932        self::fireEvent('Atomik::Execute::After', array($action, &$context, &$vars, &$triggerError));
 933        
 934        // deletes the execution context
 935        array_pop(self::$execContexts);
 936        
 937        // returns $vars if the view should not be rendered
 938        if ($render === false) {
 939            return $vars;
 940        }
 941        
 942        // no view
 943        if ($view === false) {
 944            return '';
 945        }
 946        
 947        // renders the view associated to the action
 948        return self::render($view, $vars, $triggerError);
 949    }
 950    
 951    /**
 952     * Executes the action file inside a clean scope and returns defined variables
 953     *
 954     * @see Atomik::_execute()
 955     * @param string $actionFilename
 956     * @param array $vars              Variables that will be available in the scope
 957     * @param string $className        If a class name is specified, it will try to execute its execute() static method
 958     * @return array
 959     */
 960    public static function executeFile($actionFilename, $vars = array(), $className = null)
 961    {
 962        self::fireEvent('Atomik::Executefile', array(&$actionFilename, &$vars));
 963        
 964        $atomik = new self();
 965        $vars = $atomik->_execute($actionFilename, $vars, $className);
 966        $atomik = null;
 967        
 968        return $vars;
 969    }
 970    
 971    /**
 972     * Executes a file inside a clean scope and returns defined variables
 973     * 
 974     * @internal
 975     * @param string $__filename  Filename
 976     * @param array $__vars       An array containing key/value pairs that will be transformed to variables accessible inside the file
 977     * @return string             View output
 978     */
 979    private function _execute($__filename, $__vars = array(), $__className = null)
 980    {
 981        extract($__vars);
 982        require($__filename);
 983        $vars = array();
 984        
 985        // checks if a class is used
 986        if ($__className !== null && class_exists($__className, false) && method_exists($__className, 'execute')) {
 987            // call the class execute() static method
 988            if (($vars = call_user_func(array($__className, 'execute'))) === null) {
 989                $vars = array();
 990            }
 991            $vars = array_merge(get_class_vars($__className), $vars);
 992        }
 993        
 994        // retreives "public" variables (not prefixed with an underscore)
 995        $definedVars = get_defined_vars();
 996        foreach ($definedVars as $name => $value) {
 997            if (substr($name, 0, 1) != '_') {
 998                $vars[$name] = $value;
 999            }
1000        }
1001        
1002        return $vars;
1003    }
1004    
1005    /**
1006     * Prevents the view of the actionfrom which it's called to be rendered
1007     */
1008    public static function noRender()
1009    {
1010        if (count(self::$execContexts)) {
1011            self::$execContexts[count(self::$execContexts) - 1]['view'] = false;
1012        }
1013    }
1014    
1015    /**
1016     * Modifies the view associted to the action from which it's called
1017     * 
1018     * @param string $view View name
1019     */
1020    public static function setView($view)
1021    {
1022        if (count(self::$execContexts)) {
1023            self::$execContexts[count(self::$execContexts) - 1]['view'] = $view;
1024        }
1025    }
1026    
1027    /**
1028     * Renders a view
1029     * 
1030     * Searches for a file called after the view inside
1031     * directories configured in atomik/dirs/views. If no file is found, an 
1032     * exception is thrown unless $triggerError is false.
1033     *
1034     * @param string $view         The view name
1035     * @param array $vars          An array containing key/value pairs that will be transformed to variables accessible inside the view
1036     * @param bool $triggerError   Whether to throw an exception if an error occurs
1037     * @param array $dirs          Directories where view files are stored
1038     * @return string|bool
1039     */
1040    public static function render($view, $vars = array(), $triggerError = true, $dirs = null)
1041    {
1042        if ($dirs === null) {
1043            $dirs = self::get('atomik/dirs/views');
1044        }
1045        
1046        self::fireEvent('Atomik::Render::Start', array(&$view, &$vars, &$dirs, &$triggerError));
1047        
1048        // view filename
1049        if (($filename = self::viewFilename($view, $dirs)) === false) {
1050            if ($triggerError) {
1051                throw new Atomik_Exception('View ' . $view . ' not found');
1052            }
1053            return false;
1054        }
1055        
1056        self::fireEvent('Atomik::Render::Before', array(&$view, &$vars, &$filename, $triggerError));
1057        
1058        $output = self::renderFile($filename, $vars);
1059        
1060        self::fireEvent('Atomik::Render::After', array($view, &$output, $vars, $filename, $triggerError));
1061        
1062        return $output;
1063    }
1064    
1065    /**
1066     * Renders a file using a filename which will not be resolved.
1067     *
1068     * @param string $filename   Filename
1069     * @param array $vars        An array containing key/value pairs that will be transformed to variables accessible inside the file
1070     * @return string            The output of the rendered file
1071     */
1072    public static function renderFile($filename, $vars = array())
1073    {
1074        self::fireEvent('Atomik::Renderfile::Before', array(&$filename, &$vars));
1075        
1076        if (($callback = self::get('app/views/engine', false)) !== false) {
1077            if (!is_callable($callback)) {
1078                throw new Atomik_Exception('The specified rendering engine callback cannot be called');
1079            }
1080            $output = $callback($filename, $vars);
1081            
1082        } else {
1083            $atomik = new self();
1084            $output = $atomik->_render($filename, $vars);
1085            $atomik = null;
1086        }
1087        
1088        self::fireEvent('Atomik::Renderfile::After', array($filename, &$output, $vars));
1089        
1090        return $output;
1091    }
1092    
1093    /**
1094     * Renders a layout
1095     * 
1096     * @param string $layout       Layout name
1097     * @param string $content      The content that will be available in the layout in the $contentForLayout variable
1098     * @param array $vars          An array containing key/value pairs that will be transformed to variables accessible inside the layout
1099     * @param bool $triggerError   Whether to throw an exception if an error occurs
1100     * @param array $dirs          Directories where to search for layouts
1101     * @return string
1102     */
1103    public static function renderLayout($layout, $content, $vars = array(), $triggerError = true, $dirs = null)
1104    {
1105        if ($dirs === null) {
1106            $dirs = self::get('atomik/dirs/layouts');
1107        }
1108        
1109        self::fireEvent('Atomik::Renderlayout', array(&$layout, &$content, &$vars, &$triggerError, &$dirs));
1110        $vars['contentForLayout'] = $content;
1111        
1112        return self::render($layout, $vars, $triggerError, $dirs);
1113    }
1114    
1115    /**
1116     * Renders a file (internal/default rendering engine)
1117     * 
1118     * @internal
1119     * @param string $__filename   Filename
1120     * @param array $__vars        An array containing key/value pairs that will be transformed to variables accessible inside the file
1121     * @return string              View output
1122     */
1123    private function _render($__filename, $__vars = array())
1124    {
1125        extract($__vars);
1126		ob_start();
1127        include($__filename);
1128        return ob_get_clean();
1129    }
1130    
1131    /**
1132     * Loads an helper file
1133     * 
1134     * @param string $helperName
1135     * @param array $dirs          Directories where to search for helpers
1136     */
1137    public static function loadHelper($helperName, $dirs = null)
1138    {
1139        if (isset(self::$loadedHelpers[$helperName])) {
1140            return;
1141        }
1142        
1143        if ($dirs === null) {
1144            $dirs = self::get('atomik/dirs/helpers');
1145        }
1146        
1147        self::fireEvent('Atomik::Loadhelper::Before', array(&$helperName, &$dirs));
1148        
1149        if (($filename = self::path($helperName . '.php', $dirs)) === false) {
1150            throw new Atomik_Exception('Helper ' . $helperName . ' not found');
1151        }
1152        
1153        include $filename;
1154    
1155        if (!function_exists($helperName)) {
1156            // searching for an helper defined as a class
1157            $camelizedHelperName = str_replace(' ', '', ucwords(str_replace('_', ' ', $helperName)));
1158            $className = $camelizedHelperName . 'Helper';
1159            
1160            if (!class_exists($className, false)) {
1161                // neither a function nor a class has been found
1162                throw new Exception('Helper ' . $helperName . ' file found but no function or class matching the helper name');
1163            }
1164            // helper defined as a class
1165            self::$loadedHelpers[$helperName] = array(new $className(), $camelizedHelperName);
1166            
1167        } else {
1168            // helper defined as a function
1169            self::$loadedHelpers[$helperName] = $helperName;
1170        }
1171        
1172        self::fireEvent('Atomik::Loadhelper::After', array($helperName, $dirs));
1173    }
1174    
1175    /**
1176     * Executes an helper
1177     * 
1178     * @param string $helperName
1179     * @param array $args          Arguments for the helper
1180     * @param array $dirs          Directories where to search for helpers
1181     * @return mixed
1182     */
1183    public static function helper($helperName, $args = array(), $dirs = null)
1184    {
1185        self::loadHelper($helperName, $dirs);
1186        return call_user_func_array(self::$loadedHelpers[$helperName], $args);
1187    }
1188    
1189    /**
1190     * PHP magic method to handle calls to helper in views
1191     * 
1192     * @param string $helperName
1193     * @param array $args
1194     * @return mixed
1195     */
1196    public function __call($helperName, $args)
1197    {
1198        if (method_exists('Atomik', $helperName)) {
1199            return call_user_func_array(array('Atomik', $helperName), $args);
1200        }
1201        if (isset(self::$methods[$helperName])) {
1202            return call_user_func_array(self::$methods[$helperName], $args);
1203        }
1204        return self::helper($helperName, $args);
1205    }
1206    
1207    /**
1208     * Disables the layout
1209     * 
1210     * @param bool $disable Whether to disable the layout
1211     */
1212    public static function disableLayout($disable = true)
1213    {
1214        self::set('app/disable_layout', $disable);
1215    }
1216
1217    /**
1218     * Fires the Atomik::End event and exits the application
1219     *
1220     * @param bool $success         Whether the application exit on success or because an error occured
1221     * @param bool $writeSession    Whether to call session_write_close() before exiting
1222     */
1223    public static function end($success = false, $writeSession = true)
1224    {
1225        self::fireEvent('Atomik::End', array($success, &$writeSession));
1226        
1227        if ($writeSession) {
1228            session_write_close();
1229        }
1230        
1231        self::log('Ending', LOG_DEBUG);
1232        exit;
1233    }
1234    
1235    
1236    /* -------------------------------------------------------------------------------------------
1237     *  Accessors
1238     * ------------------------------------------------------------------------------------------ */
1239    
1240    /**
1241     * Sets a key/value pair in the store
1242     * 
1243     * If the first argument is an array, values are merged recursively.
1244     * The array is first dimensionized
1245     * You can set values from sub arrays by using a path-like key.
1246     * For example, to set the value inside the array $array[key1][key2]
1247     * use the key 'key1/key2'
1248     * Can be used on any array by specifying the third argument
1249     *
1250     * @see Atomik::_dimensionizeArray()
1251     * @param array|string $key     Can be an array to set many key/value
1252     * @param mixed $value
1253     * @param bool $dimensionize    Whether to use Atomik::_dimensionizeArray() on $key
1254     * @param array $array          The array on which the operation is applied
1255     * @param array $add            Whether to add values or replace them
1256     */
1257    public static function set($key, $value = null, $dimensionize = true, &$array = null, $add = false)
1258    {
1259        // if $data is null, uses the global store
1260        if ($array === null) {
1261            $array = &self::$store;
1262        }
1263        
1264        // setting a key directly
1265        if (is_string($key)) {
1266            $parentArrayKey = strpos($key, '/') !== false ? dirname($key) : null;
1267            $key = basename($key);
1268            
1269            $parentArray = &self::getRef($parentArrayKey, $array);
1270            if ($parentArray === null) {
1271                $dimensionizedParentArray = self::_dimensionizeArray(array($parentArrayKey => null));
1272                $array = self::_mergeRecursive($array, $dimensionizedParentArray);
1273                $parentArray = &self::getRef($parentArrayKey, $array);
1274            }
1275            
1276            if ($add !== false) {
1277                if (!isset($parentArray[$key]) || $parentArray[$key] === null) {
1278                    if (!is_array($value)) {
1279                        $parentArray[$key] = $value;
1280                        return;
1281                    }
1282                    $parentArray[$key] = array();
1283                } else if (!is_array($parentArray[$key])) {
1284                    $parentArray[$key] = array($parentArray[$key]);
1285                }
1286                
1287                $value = is_array($value) ? $value : array($value);
1288                if ($add == 'prepend') {
1289                    $parentArray[$key] = array_merge_recursive($value, $parentArray[$key]);
1290                } else {
1291                    $parentArray[$key] = array_merge_recursive($parentArray[$key], $value);
1292                }
1293            } else {
1294                $parentArray[$key] = $value;
1295            }
1296            
1297            return;
1298        }
1299        
1300        if (!is_array($key)) {
1301            throw new Atomik_Exception('The first parameter of Atomik::set() must be a string or an array, ' . gettype($key) . ' given');
1302        }
1303        
1304        if ($dimensionize) {
1305            $key = self::_dimensionizeArray($key);
1306        }
1307    
1308        // merges the store and the array
1309        if ($add) {
1310            $array = array_merge_recursive($array, $key);
1311        } else {
1312            $array = self::_mergeRecursive($array, $key);
1313        }
1314    }
1315    
1316    /**
1317     * Adds a value to the array pointed by the key
1318     * 
1319     * If the first argument is an arr…

Large files files are truncated, but you can click here to view the full file