PageRenderTime 40ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/application/zula/zula.php

http://github.com/tcm-project/tangocms
PHP | 472 lines | 234 code | 32 blank | 206 comment | 43 complexity | 07f87384ce9526a689feb38af09ba69a MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. /**
  3. * Zula Framework Core
  4. *
  5. * @patches submit all patches to patches@tangocms.org
  6. *
  7. * @author Alex Cartwright
  8. * @author Robert Clipsham
  9. * @copyright Copyright (C) 2007, 2008, 2009, 2010 Alex Cartwright
  10. * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU/LGPL 2.1
  11. * @package Zula
  12. */
  13. /** Exceptions */
  14. class Zula_Exception extends Exception {}
  15. class Zula_ModelNoExist extends Zula_Exception {}
  16. class Zula_DetailNoExist extends Zula_Exception {}
  17. final class Zula {
  18. /**
  19. * Current version of Zula being used
  20. */
  21. const _VERSION = '0.8.0';
  22. /**
  23. * Holds the singleton instance of this class
  24. * @var object
  25. */
  26. static private $_instance = null;
  27. /**
  28. * The exit code that Zula will use upon clean shutdown
  29. * @var int
  30. */
  31. private $exitCode = 0;
  32. /**
  33. * The default libraries the the framework will load upon startup
  34. * @var array
  35. */
  36. private $defaultLibs = array('date', 'log', 'i18n', 'cache', 'error', 'input');
  37. /**
  38. * Current state that Zula is running in, mostly either 'production',
  39. * 'development' or 'setup';
  40. * @var string
  41. */
  42. private $state = 'production';
  43. /**
  44. * Current mode the request to Zula is, either 'normal', 'standalone' or 'cli'
  45. * @var string
  46. */
  47. private $mode = 'normal';
  48. /**
  49. * Name of the configuration profile loaded
  50. * @var string
  51. */
  52. private $configProfile = null;
  53. /**
  54. * Path to the main configuration file that got loaded
  55. * @var string
  56. */
  57. private $configPath = null;
  58. /**
  59. * Holds the current working directory
  60. * @var string
  61. */
  62. private $cwd = '';
  63. /**
  64. * Array with directory paths to use throughout scripts.
  65. * Allows you to easily change the directories used without having
  66. * to edit all of the code in many different places
  67. * @var array
  68. */
  69. private $directories = array(
  70. '3rd_party' => 'application/libraries/3rd_party',
  71. 'assets' => './assets',
  72. 'config' => 'config',
  73. 'fonts' => 'application/fonts',
  74. 'setup' => 'setup',
  75. 'js' => './assets/js',
  76. 'libs' => 'application/libraries',
  77. 'locale' => 'application/locale',
  78. 'logs' => 'application/logs',
  79. 'modules' => 'application/modules',
  80. 'themes' => './assets/themes',
  81. 'tmp' => './tmp',
  82. 'uploads' => './assets/uploads',
  83. 'views' => 'application/views',
  84. 'zula' => 'application/zula',
  85. );
  86. /**
  87. * Updates the current directories we have with the correct path and
  88. * also does some other needed startup things such as setting the autoloader
  89. * storing CWD, getting temp dir etc etc
  90. *
  91. * @param string $rootDir
  92. * @param string $state
  93. * @return object
  94. */
  95. private function __construct( $rootDir, $state='production' ) {
  96. $this->cwd = getcwd();
  97. $this->state = (string) $state;
  98. if (
  99. zula_http_header_get('X-Requested-With') == 'XMLHttpRequest'
  100. ||
  101. zula_http_header_get('X-Zula-Mode') == 'standalone'
  102. ) {
  103. $this->mode = 'standalone';
  104. } else {
  105. $this->mode = (PHP_SAPI == 'cli') ? 'cli' : 'normal';
  106. }
  107. set_exception_handler( array($this, 'exceptionHandler') );
  108. spl_autoload_register( array(__CLASS__, 'autoloadClass') );
  109. /**
  110. * Get the base directory (path from the URL) and update directories
  111. * with the root directory prefix.
  112. */
  113. // Configure the base directory
  114. $base = trim( dirname($_SERVER['SCRIPT_NAME']), './\ ' );
  115. define( '_BASE_DIR', empty($base) ? '/' : '/'.$base.'/' );
  116. foreach( $this->directories as $name=>$path ) {
  117. if ( substr( $path, 0, 2 ) !== './' ) {
  118. $path = $rootDir.'/'.$path;
  119. }
  120. $this->updateDir( $name, $path );
  121. }
  122. }
  123. /**
  124. * Get the instance of the Zula class
  125. *
  126. * @param string $rootDir
  127. * @param string $state
  128. * @return object
  129. */
  130. static public function getInstance( $rootDir, $state='production' ) {
  131. if ( !is_object( self::$_instance ) ) {
  132. self::$_instance = new self( $rootDir, $state );
  133. }
  134. return self::$_instance;
  135. }
  136. /**
  137. * Autoloader function that will attempt to load the correct class library
  138. * based on the name given.
  139. *
  140. * It will also automagcailly include an 'Exceptions.php' file if it is found.
  141. *
  142. * @param string $class
  143. */
  144. static public function autoloadClass( $class ) {
  145. if ( stripos( $class, '_controller_' ) !== false ) {
  146. $modDir = realpath( self::$_instance->getDir( 'modules' ) );
  147. $classSplit = explode( '_', strtolower( $class ) );
  148. $cntrlrIndex = array_search( 'controller', $classSplit );
  149. // Store cntrlr file and exception file paths
  150. $modDir = $modDir.'/'.implode( '_', array_slice($classSplit, 0, $cntrlrIndex) );
  151. $classFile = $modDir.'/controllers/'.implode( '_', array_slice($classSplit, $cntrlrIndex+1) ).'.php';
  152. $exceptions = $modDir.'/Exceptions.php';
  153. } else {
  154. // Load internal Zula lib
  155. $libDir = realpath( self::$_instance->getDir( 'libs' ) );
  156. $classSplit = array_map( 'ucfirst', explode('_', strtolower($class)) );
  157. $classFile = $libDir.'/'.implode( '/', $classSplit ).'.php';
  158. $exceptions = $libDir.'/'.$classSplit[0].'/Exceptions.php';
  159. }
  160. if ( isset( $exceptions ) && is_readable( $exceptions ) ) {
  161. include_once $exceptions;
  162. }
  163. if ( is_readable( $classFile ) ) {
  164. include $classFile;
  165. }
  166. }
  167. /**
  168. * Zula Exception Handler for all uncaught exceptions
  169. *
  170. * @param object $exception
  171. * @return bool
  172. */
  173. public function exceptionHandler( Exception $e ) {
  174. $formatCode = sprintf( 'ZULA-%03d', $e->getCode() );
  175. if ( !Registry::has( 'error' ) ) {
  176. try {
  177. $this->loadLib( 'error' );
  178. } catch ( Exception $e ) {}
  179. }
  180. /**
  181. * Create the correct title for the exception
  182. */
  183. if ( $e instanceof Zula_Exception ) {
  184. $title = 'Uncaught internal (Zula) exception';
  185. } else if ( $e instanceof PDOException ) {
  186. $title = 'Uncaught SQL (PDO) exception';
  187. } else {
  188. $title = 'Uncaught application exception';
  189. }
  190. // Attempt to write a dump log file
  191. $logDir = $this->getDir( 'logs' );
  192. if ( zula_is_writable( $logDir ) ) {
  193. $i = 1;
  194. do {
  195. $file = $logDir.'/zula-dump.'.$i.'.log';
  196. $i++;
  197. } while ( file_exists( $file ) );
  198. $body = 'Uncaught exception in "'.$e->getFile().'" on line '.$e->getLine().' with code "'.$formatCode.'"';
  199. $body .= "\n\nProject Version: ".(defined('_PROJECT_VERSION') ? _PROJECT_VERSION : 'unknown');
  200. $body .= "\nTime & Date: ".date( 'c' );
  201. $body .= "\nRequest Method: ".(PHP_SAPI == 'cli' ? 'cli' : $_SERVER['REQUEST_METHOD']);
  202. if ( Registry::has( 'router' ) ) {
  203. $body .= "\nRequest Path: ".Registry::get( 'router' )->getRequestPath();
  204. $body .= "\nRaw Request Path: ".Registry::get( 'router' )->getRawRequestPath();
  205. }
  206. $body .= "\nException Thrown: ".get_class( $e );
  207. $body .= "\nMessage: ".$e->getMessage();
  208. $body .= "\n\nStack Trace:\n";
  209. $body .= $e->getTraceAsString();
  210. file_put_contents( $file, $body );
  211. }
  212. if ( Registry::has( 'error' ) ) {
  213. return Registry::get( 'error' )->report( $e->getMessage(), E_USER_ERROR, $e->getFile(), $e->getLine(), $title );
  214. } else {
  215. trigger_error( $e->getMessage(), E_USER_ERROR );
  216. }
  217. }
  218. /**
  219. * Gets the state that Zula is currently running in
  220. *
  221. * @return string
  222. */
  223. public function getState() {
  224. return $this->state;
  225. }
  226. /**
  227. * Gets the mode the Zula request is
  228. *
  229. * @return string
  230. */
  231. public function getMode() {
  232. return $this->mode;
  233. }
  234. /**
  235. * Gets the name of the configuration profile being used
  236. *
  237. * @return string
  238. */
  239. public function getConfigProfile() {
  240. return $this->configProfile;
  241. }
  242. /**
  243. * Gets the path to the main configuration file
  244. *
  245. * @return string
  246. */
  247. public function getConfigPath() {
  248. return $this->configPath;
  249. }
  250. /**
  251. * Returns the exit code that will be used, mainly (if not exclusively)
  252. * for CLI only. By default the following are used:
  253. *
  254. * 0 - Everything is ok
  255. * 1 - Reservered for zula.sh
  256. * 2 - Internal error
  257. * 3 - Application error
  258. * 4 - Non-existant request path (HTTP 404)
  259. * 5 - Permission denied (HTTP 403)
  260. *
  261. * @return int
  262. */
  263. public function getExitCode() {
  264. return $this->exitCode;
  265. }
  266. /**
  267. * Sets the exit code to use when Zula cleanly shutdowns
  268. *
  269. * @var int $code
  270. * @return object
  271. */
  272. public function setExitCode( $code ) {
  273. $this->exitCode = (int) $code;
  274. return $this;
  275. }
  276. /**
  277. * Loads the default most commonly used libraries for the framework
  278. *
  279. * @return object
  280. */
  281. public function loadDefaultLibs() {
  282. $config = Registry::get( 'config' );
  283. foreach( $this->defaultLibs as $library ) {
  284. if ( $library == 'cache' ) {
  285. try {
  286. $cache = Cache::factory( $config->get( 'cache/type' ) );
  287. } catch ( Config_KeyNoExist $e ) {
  288. // Revert to file based caching.
  289. $cache = Cache::factory( 'file' );
  290. }
  291. try {
  292. $cache->ttl( $config->get( 'cache/ttl' ) );
  293. } catch ( Exception $e ) {}
  294. } else if ( $library == 'i18n' ) {
  295. try {
  296. I18n::factory( $config->get( 'locale/engine' ) );
  297. } catch ( Config_KeyNoExist $e ) {
  298. I18n::factory( 'failsafe' );
  299. }
  300. } else {
  301. $this->loadLib( $library );
  302. }
  303. }
  304. return $this;
  305. }
  306. /**
  307. * Loads a library by doing the following:
  308. * 1) Load the class file
  309. * 2) Create instance of class
  310. * 3) Call it's _onLoad() method if it exists
  311. * 4) Adding it to the registry
  312. *
  313. * If the library is already loaded then it wont be loaded again
  314. *
  315. * @param string $library
  316. * @param string $regName Custom name to be used when storing in the registry
  317. * @return object
  318. */
  319. public function loadLib( $library, $regName=null ) {
  320. $className = strtolower( $library );
  321. $regName = trim($regName) ? $regName : $className;
  322. if ( Registry::has( $regName ) ) {
  323. return Registry::get( $regName );
  324. } else if ( class_exists( $className ) === false ) {
  325. throw new Zula_Exception( 'Zula library "'.$className.'" does not exist' );
  326. }
  327. $tmpLib = new $className;
  328. if ( $tmpLib instanceof Zula_LibraryBase ) {
  329. if ( method_exists( $tmpLib, '_onLoad' ) ) {
  330. $tmpLib->_onLoad( $regName );
  331. }
  332. Registry::register( $regName, $tmpLib );
  333. return $tmpLib;
  334. } else {
  335. throw new Zula_Exception( 'Zula library "'.$className.'" must extend "Zula_LibraryBase"', 2 );
  336. }
  337. }
  338. /**
  339. * Loads the main configuration ini file and adds it to the registry.
  340. * The 'config' directory will be updated to have the provided profile
  341. * appended to it, e.g. './config' becomes './config/default'
  342. *
  343. * @param string $profile
  344. * @return object
  345. */
  346. public function loadConfig( $profile ) {
  347. if ( Registry::has( 'config' ) && Registry::has( 'config_ini' ) ) {
  348. return Registry::get( 'config' );
  349. }
  350. $configDir = $this->getDir( 'config' ).'/'.$profile;
  351. try {
  352. $configIni = new Config_ini;
  353. $configIni->load( $configDir.'/config.ini.php' );
  354. Registry::register( 'config_ini', $configIni );
  355. // Merge the ini configuration in to the main config library
  356. $config = new Config;
  357. $config->load( $configIni );
  358. Registry::register( 'config', $config );
  359. // Store the profile name and update the 'config' dir value
  360. $this->setDir( 'config', $configDir );
  361. $this->configProfile = $profile;
  362. $this->configPath = $configDir.'/config.ini.php';
  363. return $config;
  364. } catch ( Config_Ini_FileNoExist $e ) {
  365. throw new Zula_Exception( 'Zula configuration file "'.$configDir.'/config.ini.php" does not exist or is not readable', 8);
  366. }
  367. }
  368. /**
  369. * Updates the path to a named directory that can be used. If the path
  370. * does not exist, it shall attemp to create the directory (only if the
  371. * 3rd argument is set to bool true)
  372. *
  373. * @param string $name
  374. * @param string $dir
  375. * @param bool $createDir
  376. * @return object
  377. */
  378. public function setDir( $name, $dir, $createDir=false ) {
  379. if ( $createDir && zula_make_dir( $dir ) === false ) {
  380. throw new Zula_Exception( 'failed to create directory "'.$dir.'"' );
  381. }
  382. $this->directories[ $name ] = $dir;
  383. unset( $this->htmlDirs[ $name ] );
  384. return $this;
  385. }
  386. /**
  387. * Alias to Zula::setDir()
  388. *
  389. * @param string $name
  390. * @param string $dir
  391. * @param bool $createDir
  392. * @deprecated deprecated since 0.7.70
  393. * @return object
  394. */
  395. public function updateDir( $name, $dir, $createDir=false ) {
  396. return $this->setDir( $name, $dir, $createDir );
  397. }
  398. /**
  399. * Returns the correct dir specified. It set it will remove
  400. * all dots and slashes from the right. If set to return it
  401. * for use in HTML (or other places I guess) then it will return
  402. * a string that contains the correct URL for use in HTML.
  403. *
  404. * For example, if installed to example.com/tangocms/ then it will return
  405. * a string such as '/tangocms/application/libraries' instead of just
  406. * './application/libraries'
  407. *
  408. * This method does *NOT* return a string with a forward slash as its suffex:
  409. * IE: it will return './html/images' *NOT* './html/images/'
  410. *
  411. * @param string $name
  412. * @param bool $forHtml
  413. * @return string
  414. */
  415. public function getDir( $name, $forHtml=false ) {
  416. static $htmlPaths = array();
  417. if ( isset( $this->directories[ $name ] ) ) {
  418. if ( $forHtml === true ) {
  419. if ( !isset( $htmlPaths[ $name ] ) ) {
  420. $trim = (substr( $this->directories[ $name ], 0, 2 ) == '..') ? '/\ ' : './\ ';
  421. $htmlPaths[ $name ] = _BASE_DIR . trim( $this->directories[ $name ], $trim );
  422. }
  423. return $htmlPaths[ $name ];
  424. }
  425. return rtrim( $this->directories[ $name ], '/' );
  426. }
  427. throw new Zula_Exception( 'Zula named directory "'.$name.'" does not exist' );
  428. }
  429. /**
  430. * A handy function to reset the current working directory to what it
  431. * was at the very beggining of the script. This is because with Apache
  432. * the CWD is reset to / within a class Destructor.
  433. *
  434. * @return bool
  435. */
  436. public function resetCwd() {
  437. return chdir( $this->cwd );
  438. }
  439. }
  440. ?>