PageRenderTime 33ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

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

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