/webroot/vork
[return to app]1
<?php error_reporting(E_ALL | E_STRICT);
2
3 define('DEBUG_MODE', false); //this is the only line that needs to be adjusted in this file
4
5 /**
6 * Vork Framework www.Vork.us
7 * @version 3.00
8 * @author Eric David Benari, Vork Chief Architect
9 */
10 class configInit {
11 /**
12 * Location of the packages folder (relative to webroot)
13 */
14 public static function packagesPath() {
15 return configDefaults::basepath() . 'packages' . self::DS;
16 }
17
18 /**
19 * Directory separator, in both Linux and Windows you can just leave this as-is
20 */
21 const DS = '/';
22
23 /**
24 * Creates the ability to access constants within inheriting classes just like properties
25 * If there is a naming conflict properties take precedence, but this should never be
26 * an issue when following standard Zend Framework/PEAR naming conventions where properties use
27 * camel-caps and constants are all-capitals delimited by undersores.
28 *
29 * get::$config->myProperty
30 * get::$config->MY_CONSTANT
31 *
32 * @param string $key
33 * @return mixed
34 */
35 public function __get($key) {
36 $constantName = get_class($this) . '::' . $key;
37 if (defined($constantName)) {
38 return constant($constantName);
39 } else {
40 trigger_error('Undefined property: ' . $constantName, E_USER_NOTICE);
41 }
42 }
43 }
44
45 if (DEBUG_MODE) {
46 require configDefaults::basepath() . '.debug';
47 }
48
49 /**
50 * Default configuration - these settings will only be used if they are not defined in the config file
51 */
52 class configDefaults extends configInit {
53 /**
54 * Path to the config file, change only if not using the default file name/location
55 * Note: if no DB connections or other configuration settings are needed the .config file can
56 * be deleted and this setting will be ignored.
57 */
58 const CONFIG_PATH = '.config';
59
60 /**
61 * Optional, only need to set these if you wish to execute a file that is global to the
62 * application before and/or after the page loads. Filename must match the class name and be in the MVC root.
63 */
64 const APP_CONSTRUCT = null, APP_DESTRUCT = null;
65
66 /**
67 * Optional, only need to set these in your config class if you move the folders from the default locations
68 *
69 * @var string
70 */
71 public $pathToMvc, $modelsFolder, $viewsFolder, $controllersFolder,
72 $layoutsFolder, $elementsFolder, $componentsFolder, $helpersFolder;
73
74 /**
75 * Optional, only need to set this in your config class if the webroot folder is not installed in the root of
76 * your web site directory
77 *
78 * @var string
79 */
80 public $basepath = '/';
81
82 /**
83 * Name of controller that is used to handle errors
84 *
85 * @var string
86 */
87 public $errorHandler = 'error';
88
89 /**
90 * By default no extension is used for MVC files, if you need to use a file extension then
91 * it needs to be set it in your config class in order for the files to be found
92 * and you need to include the prepended dot
93 *
94 * @var string
95 */
96 public $fileExtension = '';
97
98 /**
99 * These are the default helperes that will be loaded into every view (and available to every element)
100 *
101 * Default is 'html' and 'form'; you may wish to add more by overriding this property this
102 * property within your config and adding the name of the additional helpers to the parent array.
103 *
104 * Note: only do this if most of your application needs the helper, otherwise you can load
105 * helpers in the __construct of a controller or within any method (action) of a controller
106 *
107 * @var array
108 */
109 public $helpers = array('html', 'form');
110
111 /**
112 * This sets the objects that will be available to all your models, the default is db and
113 * that should suffice for most applications. To add more create the object (class) in your config
114 * and then override this property adding the name of your object to the parent array and add a static
115 * property with the same name as the object to the config class (this will contain the object later.)
116 */
117 public static $db, $modelObjects = array('db');
118
119 /**
120 * Internal cache of basepath
121 * @var string
122 */
123 protected static $_basepath;
124
125 /**
126 * Set the base working path
127 * @return string
128 */
129 public static function basepath() {
130 if (self::$_basepath === null) {
131 if (isset($_SERVER['DOCUMENT_ROOT']) && $_SERVER['DOCUMENT_ROOT']) {
132 $_SERVER['DOCUMENT_ROOT'] = rtrim($_SERVER['DOCUMENT_ROOT'], self::DS);
133 self::$_basepath = (substr($_SERVER['DOCUMENT_ROOT'], -7) == 'webroot'
134 ? substr($_SERVER['DOCUMENT_ROOT'], 0, -8) : $_SERVER['DOCUMENT_ROOT']) .
self::DS;
135 } else { //cli
136 self::$_basepath = substr($_SERVER['SCRIPT_FILENAME'], 0, -12); //remove webroot/vork
137 }
138 }
139 return self::$_basepath;
140 }
141
142 /**
143 * Establishes MVC folder locations automatically and sets default title/meta-data (if defaults exist)
144 */
145 public function __construct() {
146 if (!get::$title) {
147 get::$title = (isset($this->title) && $this->title ? $this->title : $this->SITE_NAME);
148 }
149 if (!get::$meta && isset($this->meta) && is_array($this->meta)) {
150 get::$meta = $this->meta;
151 }
152
153 if ($this->pathToMvc === null) {
154 $this->pathToMvc = configDefaults::basepath() . 'mvc' . self::DS;
155 if (isset($_SERVER['SCRIPT_NAME']) && $_SERVER['SCRIPT_NAME'] == '/webroot/vork') {
156 $this->pathToMvc = $_SERVER['DOCUMENT_ROOT'] . self::DS . 'mvc' . self::DS;
157 }
158 } else {
159 if (substr($this->pathToMvc, -1) != self::DS) { //append trailing slash if needed
160 $this->pathToMvc .= self::DS;
161 }
162 }
163 $mvcFolders = array('models', 'views', 'controllers',
164 'layouts', 'elements', 'components', 'helpers');
165 foreach ($mvcFolders as $mvcFolder) {
166 if ($this->{$mvcFolder . 'Folder'} === null) {
167 $this->{$mvcFolder . 'Folder'} = $mvcFolder;
168 }
169 }
170 }
171 }
172
173 /**
174 * The internal MVC logic - essentially the brain of the framework
175 */
176 class mvc {
177 /**
178 * Override these within your application to change controller, action, layout or view
179 * You can also modify the params array as needed.
180 * mvc::$controller = 'cart';
181 * mvc::$action = 'logout';
182 * mvc::$layout = 'mobile';
183 * mvc::$view = 'confirmation';
184 *
185 * Changes to the above will only take affect after the current action returns, to make the affect
186 * immediate (to redirect before completing the current action) simply add a return; statement right after.
187 *
188 * These properties have defaults set:
189 * If no controller is defined in the URL then the index controller is used
190 * If no action is defined in the URL then the index action is used
191 * If no layout is defined in the controller then the default layout is used
192 *
193 * Views will always default to a file with the same name as the action and will first look within a
194 * folder in the views directory that has the same name as the controller. If using the index action or
195 * in the case that a controller is not found (usually for a page of semi-static content) and the folder
196 * or the file within it is not found it will then look for a file in the root of the views
197 * directory with the name of the controller.
198 *
199 * Acceptable locations are:
200 * /mvc/views/controllerName/actionName
201 * /mvc/views/controllerName
202 *
203 * To use a view from a different controller you can simply override the view setting within
204 * your application and include the path:
205 * mvc::$view = 'someOtherController' . get::$config->DS . 'newView';
206 */
207 public static $controller = 'index', $action = 'index', $layout = 'default', $view, $params = array();
208
209 /**
210 * This is a safety to prevent redirecting in infinite loops (eg. a redirects to b, then b redirects
211 * back to a.) The default maximum redirects is 10 within one request, if you need more you could
212 * increase this but keep in mind that any app that needs to redirect over 10 times per request
213 * likely has some serious flaw in the architecture.
214 */
215 protected $_maxActionRedirects = 10, $_redirectHistory = array();
216
217 /**
218 * Buffer used to contain view params
219 * @var array
220 */
221 protected static $_viewParams = array();
222
223 /**
224 * Indicator as to the current processing-state of Vork internals
225 */
226 protected static $_state = '__construct';
227
228 /**
229 * Set the $_state property
230 */
231 public static function setState($state) {
232 if (DEBUG_MODE) {
233 debug::log('Vork internal state: ' . $state);
234 }
235 self::$_state = $state;
236 }
237
238 /**
239 * Read-only public access to the $_state property
240 * @return string
241 */
242 public static function state() {
243 return self::$_state;
244 }
245
246 /**
247 * Replaces characters that are not valid within the name of a PHP object
248 *
249 * @param string $objName
250 * @return string
251 */
252 public static function getValidObjectName($objName) {
253 $objName = preg_replace('/\W/', '_', $objName);
254 if (is_numeric($objName[0])) { //cannot start with a number
255 $objName = '_' . $objName;
256 }
257 return $objName;
258 }
259
260 /**
261 * Load config, open a DB connection & cache (if they are defined,) parse the URL to determine
controller/action
262 * and then load the page
263 */
264 public function __construct() {
265 if (is_file(configDefaults::basepath() . configDefaults::CONFIG_PATH)) {
266 require configDefaults::basepath() . configDefaults::CONFIG_PATH;
267 get::$config = new config;
268 } else {
269 get::$config = new configDefaults;
270 }
271 if (DEBUG_MODE) {
272 debug::log('Config loaded');
273 }
274
275 if (get::$config->CACHE_CONNECT) {
276 get::dbConnection('cache');
277 if (DEBUG_MODE) {
278 debug::log('Cache loaded');
279 }
280 }
281
282 $this->_parseUrl();
283
284 if (get::$config->APP_CONSTRUCT && is_file(get::$config->pathToMvc . get::$config->APP_CONSTRUCT)) {
285 require get::$config->pathToMvc . get::$config->APP_CONSTRUCT;
286 get::$config->{get::$config->APP_CONSTRUCT} = new get::$config->APP_CONSTRUCT;
287 if (DEBUG_MODE) {
288 debug::log('APP_CONSTRUCT loaded');
289 }
290 }
291
292 $this->_loadAction();
293
294 if (get::$config->APP_DESTRUCT && is_file(get::$config->pathToMvc . get::$config->APP_DESTRUCT)) {
295 require get::$config->pathToMvc . get::$config->APP_DESTRUCT;
296 get::$config->{get::$config->APP_DESTRUCT} = new get::$config->APP_DESTRUCT;
297 if (DEBUG_MODE) {
298 debug::log('APP_DESTRUCT loaded');
299 }
300 }
301 }
302
303 /**
304 * Loads an element. Elements can access the view params plus the params sent to them as an argument as if
305 * they are local variables. If there is a naming conflict the variable in the argument takes precedence.
306 *
307 * @param string $outputType
308 * @param string $elementName
309 * @param array $elementParams
310 * @return mixed
311 */
312 protected static function _loadElement($outputType, $elementName, $elementParams) {
313 $elementFile = get::mvcFilePath('elements', $elementName);
314 if (is_file($elementFile)) {
315 if (is_array(self::$_viewParams)) {
316 extract(self::$_viewParams);
317 }
318 if (is_array($elementParams)) {
319 extract($elementParams);
320 }
321 if (DEBUG_MODE) {
322 $logMsg = 'element ' . $elementName . ' loaded';
323 if ($elementParams) {
324 $logMsg .= ' with ' . json_encode($elementParams);
325 }
326 }
327 if ($outputType == 'return') {
328 ob_start();
329 require $elementFile;
330 $element = ob_get_contents();
331 ob_end_clean();
332 if (DEBUG_MODE) {
333 debug::log($logMsg);
334 }
335 return $element;
336 } else {
337 $return = require $elementFile;
338 if (DEBUG_MODE) {
339 debug::log($logMsg);
340 }
341 return $return;
342 }
343 } else {
344 trigger_error('File for element ' . $elementName . ' could not be found', E_USER_WARNING);
345 }
346 }
347
348 /**
349 * Returns the output of an element as a string
350 *
351 * @param string $elementName
352 * @param array $elementParams
353 * @return string
354 */
355 public static function getElement($elementName, $elementParams = array()) {
356 return self::_loadElement('return', $elementName, $elementParams);
357 }
358
359 /**
360 * Loads an element, any output goes directly to stdout (echos to the screen)
361 *
362 * @param string $elementName
363 * @param array $elementParams
364 */
365 public static function loadElement($elementName, $elementParams = array()) {
366 return self::_loadElement('echo', $elementName, $elementParams);
367 }
368
369 /**
370 * Loads the default helpers defined in the config at every page instance
371 */
372 protected function _loadDefaultHelpers() {
373 foreach (get::$config->helpers as $helper) {
374 if (!isset(self::$_viewParams[$helper])) {
375 $helperFile = get::mvcFilePath('helpers', $helper);
376 if (is_file($helperFile)) {
377 if (!class_exists($helper . 'Helper')) {
378 require $helperFile;
379 }
380 $helperClassName = $helper . 'Helper';
381 get::$loadedObjects['helper'][$helper][null] = self::$_viewParams[$helper] = new
$helperClassName;
382 } else {
383 trigger_error('File for ' . $helperFile . ' helper could not be found', E_USER_WARNING);
384 }
385 }
386 }
387 if (DEBUG_MODE) {
388 debug::log('Default helpers loaded: ' . implode(', ', get::$config->helpers));
389 }
390 }
391
392 /**
393 * Loads a view, if a layout exists then the view will be loaded into the layout
394 *
395 * @param string $controllerName
396 * @param string $action
397 * @return mixed
398 */
399 protected function _loadView($controllerName, $action) {
400 if (!load::$loadView || self::$view === false) {
401 return;
402 }
403 self::setState('view');
404 if (self::$view == null) { //try: /mvc/views/controllerName/actionName
405 $viewFile = get::mvcFilePath('views', $controllerName . get::$config->DS . $action);
406 if (!is_file($viewFile) && $action == 'index') { //try: /mvc/views/controllerName
407 $viewFile = get::mvcFilePath('views', $controllerName);
408 }
409 } else {
410 $viewFile = get::mvcFilePath('views', $controllerName . get::$config->DS . self::$view);
411 if (!is_file($viewFile)) { //local path failed, try absolute path
412 $viewFile = get::mvcFilePath('views', self::$view);
413 }
414 }
415
416 if (!is_file($viewFile) && $controllerName != get::$config->errorHandler) {
417 return $this->_loadAction(get::$config->errorHandler, 'missingView');
418 }
419
420 if (is_array(self::$_viewParams)) {
421 extract(self::$_viewParams);
422 }
423
424 if (is::ajax() && get::$config->ajaxEnabled
425 && (!is_array(get::$config->ajaxEnabled) //ajax=true
426 || (isset(get::$config->ajaxEnabled[$controllerName])
427 && (get::$config->ajaxEnabled[$controllerName] === true //ajax[controllerName]=true
428 || get::$config->ajaxEnabled[$controllerName] == $action //ajax[controllerName]=action
429 || (is_array(get::$config->ajaxEnabled[$controllerName])
//ajax[controllerName]=array(action)
430 && in_array($action, get::$config->ajaxEnabled[$controllerName])))))) {
431 self::$layout = false; //AJAX request
432 }
433
434 $layoutFile = get::mvcFilePath('layouts', self::$layout);
435 if (self::$layout && is_file($layoutFile)) {
436 ob_start();
437 require $viewFile;
438 $view = ob_get_contents();
439 ob_end_clean();
440 if (DEBUG_MODE) {
441 debug::log('View loaded');
442 }
443 self::setState('layout');
444 require $layoutFile;
445 if (DEBUG_MODE) {
446 debug::log('Layout loaded');
447 }
448 } else {
449 require $viewFile;
450 if (DEBUG_MODE) {
451 debug::log('View loaded');
452 }
453 }
454 }
455
456 /**
457 * Redirects controllers and/or action
458 *
459 * @param string $originalController
460 * @param string $originalAction
461 */
462 protected function _redirectAction($originalController, $originalAction) {
463 if (count($this->_redirectHistory) < $this->_maxActionRedirects) {
464 $this->_redirectHistory[] = array('oldController' => $originalController,
465 'newController' => self::$controller,
466 'oldAction' => $originalAction,
467 'newAction' => self::$action);
468 $this->_loadAction();
469 } else {
470 self::$params['redirectHistory'] = $this->_redirectHistory;
471 $this->_loadAction(get::$config->errorHandler, 'redirectEndlessLooping');
472 }
473 }
474
475 /**
476 * Loads an action
477 *
478 * @param string $controllerName Optional, if omitted then self::$controller is used
479 * @param string $action Optional, if omitted then self::$action is used
480 * @return null
481 */
482 protected function _loadAction($controllerName = null, $action = null) {
483 if (is_null($controllerName)) {
484 $controllerName = self::$controller;
485 }
486 if (is_null($action)) {
487 $action = self::getValidObjectName(self::$action);
488 }
489 if (strpos($controllerName, '..') !== false || strpos($controllerName, ':') !== false) {
490 return $this->_loadAction(get::$config->errorHandler, 'invalidControllerName');
491 }
492 if (!is_file(get::mvcFilePath('controllers', $controllerName))) { //no controller, attempt to load view
directly
493 $this->_loadDefaultHelpers();
494 return $this->_loadView($controllerName, $action);
495 }
496 $originalController = $controllerName;
497 $controllerClassName = $controllerName . 'Controller';
498 if (!class_exists($controllerClassName)) {
499 require get::mvcFilePath('controllers', $controllerName);
500 }
501 if (!class_exists($controllerClassName) && $controllerName != get::$config->errorHandler) {
502 return $this->_loadAction(get::$config->errorHandler, 'controllerNotDefined');
503 }
504
505 $originalAction = self::$action;
506 $controller = new $controllerClassName;
507 if ($controllerName != self::$controller && $controllerName != get::$config->errorHandler) {
508 return $this->_redirectAction($originalController, $action); //controller was changed in
__construct()
509 }
510 if ($originalAction != self::$action) { //action was changed in __construct()
511 $action = self::$action;
512 }
513 $optionalAction = (defined($controllerName . 'Controller::optionalAction')
514 && constant($controllerName . 'Controller::optionalAction'));
515 if (!$optionalAction) { //updates self::$action in instances where _loadAction() was called with $action
arg
516 self::$action = $action;
517 }
518 if (!method_exists($controller, $action) && $controllerName != get::$config->errorHandler) {
519 if ($optionalAction && self::$action != 'index') {
520 array_unshift(self::$params, self::$action);
521 self::$action = 'index';
522 return $this->_loadAction($controllerName, 'index');
523 } else {
524 return $this->_loadAction(get::$config->errorHandler, 'missingAction');
525 }
526 }
527
528 $this->_loadDefaultHelpers();
529 self::setState('controller::action');
530 $viewParams = $controller->$action();
531 if (is_array($viewParams)) {
532 self::$_viewParams = array_merge(self::$_viewParams, $viewParams);
533 }
534 if (DEBUG_MODE) {
535 debug::log('Controller:action loaded');
536 }
537
538 if ($controllerName != get::$config->errorHandler) {
539 if (self::$controller != $originalController || self::$action != $action) {
540 return $this->_redirectAction($originalController, $action);
541 }
542 }
543 $this->_loadView($controllerName, $action);
544 }
545
546 /**
547 * Parse URL string to extract controller name, action name and params
548 */
549 protected function _parseUrl() {
550 $basepathLen = strlen(get::$config->basepath);
551 if (isset($_SERVER['REDIRECT_URL'])) {
552 $_SERVER['REQUEST_URI'] = $_SERVER['REDIRECT_URL'];
553 }
554 if (isset($_SERVER['REQUEST_URI'])) {
555 $path = substr($_SERVER['REQUEST_URI'], $basepathLen);
556 $positionOfGet = strpos($path, '?');
557 if ($positionOfGet !== false) {
558 $path = substr($path, 0, $positionOfGet);
559 }
560 $pathParts = explode('/', $path);
561 $fileName = (strrpos($_SERVER['SCRIPT_FILENAME'], get::$config->DS) + 1);
562 if (isset($pathParts[0]) && $pathParts[0] == substr($_SERVER['SCRIPT_FILENAME'], $fileName)) {
563 $firstSegment = array_shift($pathParts); //remove the current filename from the path parts;
564 if ($firstSegment == 'vork' && is::superuser()) {
565 require configDefaults::basepath() . '.installer';
566 }
567 }
568 foreach ($pathParts as $pathSegment) {
569 $pathSegment = trim($pathSegment);
570 if ($pathSegment != '') {
571 $pathSegments[] = $pathSegment;
572 }
573 }
574 } else if (isset($_SERVER['argv'])) { //cli-mode
575 $pathSegments = $_SERVER['argv'];
576 array_shift($pathSegments);
577 }
578
579 if (isset($pathSegments) && $pathSegments) {
580 self::$controller = self::getValidObjectName(array_shift($pathSegments));
581
582 if (isset($pathSegments[0])) {
583 self::$action = array_shift($pathSegments);
584 if (!empty($pathSegments)) {
585 self::$params = $pathSegments;
586 }
587 }
588 }
589 if (DEBUG_MODE) {
590 debug::log('URL parsed');
591 }
592 }
593 }
594
595 /**
596 * Blackhole object used when catching a database exception to avoid fatal errors being thrown before reaching
597 * the error-handler controller & view.
598 * Note: static database methods will still fatal-error in pre-5.3 versions of PHP as __callStatic was not yet
supported
599 */
600 class blackhole implements Iterator, ArrayAccess {
601 public static function __callStatic($method, array $args) {
602 return new blackhole;
603 }
604 public function __call($method, array $args) {
605 return $this;
606 }
607 public function __get($name) {
608 return $this;
609 }
610 public function __set($name, $val) {}
611 public function __isset($name) {
612 return false;
613 }
614 public function __unset($name) {}
615 public function __toString() {
616 return '';
617 }
618
619 public function rewind() {}
620 public function current() {}
621 public function key() {}
622 public function next() {}
623 public function valid() {
624 return false;
625 }
626
627 public function offsetExists($offset) {
628 return false;
629 }
630 public function offsetGet($offset) {}
631 public function offsetSet($offset, $value) {}
632 public function offsetUnset($offset) {}
633 }
634
635 /**
636 * Public interface to determine Boolean properties
637 */
638 class is {
639 /**
640 * If instance is requested via AJAX
641 * @return Boolean
642 */
643 public static function ajax() {
644 return (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] ==
'XMLHttpRequest');
645 }
646
647 /**
648 * If instance is requested via HTTPS
649 * @return Boolean
650 */
651 public static function ssl() {
652 return (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on');
653 }
654
655 /**
656 * Internal cache for the is::bot() and is::mobile() methods
657 * @var Boolean or null
658 */
659 protected static $_bot, $_mobile;
660
661 /**
662 * Identifies a spider/bot/crawler
663 * @return Boolean
664 */
665 public static function bot() {
666 if (is_null(self::$_bot)) {
667 self::$_bot = false;
668 if (isset($_SERVER['HTTP_USER_AGENT'])) {
669 $spiders = array('search', 'spider', 'crawler', 'archiver', 'indexer', 'webbot', 'robot',
670 'nagios', 'Googlebot', 'MSNbot', 'Jeeves', 'Slurp', 'Scooter', 'InfoSeek',
'Lycos',
671 'seoprofiler', 'Feedfetcher', 'facebookexternalhit');
672 self::$_bot = (preg_match('/' . implode('|', $spiders) . '/i', $_SERVER['HTTP_USER_AGENT']));
673 }
674 }
675 return self::$_bot;
676 }
677
678 /**
679 * Identifies a mobile user
680 * @return Boolean
681 */
682 public static function mobile() {
683 if (is_null(self::$_mobile)) {
684 self::$_mobile = false;
685 if (isset($_SERVER['HTTP_USER_AGENT'])) {
686 $mobile = array('Android', 'AvantGo', 'BlackBerry', 'DoCoMo', 'Fennec', 'iPod', 'iPhone', 'J2ME',
687 'MIDP', 'NetFront', 'Nokia', 'Opera Mini', 'PalmOS', 'PalmSource', 'portalmmm',
688 'Plucker', 'ReqwirelessWeb', 'SonyEricsson', 'Symbian', 'UP\.Browser', 'webOS',
689 'Windows CE', 'Xiino');
690 self::$_mobile = (preg_match('/' . implode('|', $mobile) . '/i', $_SERVER['HTTP_USER_AGENT']));
691 }
692 }
693 return self::$_mobile;
694 }
695
696 /**
697 * If instance is requested from Flash
698 * @return Boolean
699 */
700 public static function flash() {
701 return (isset($_SERVER['HTTP_USER_AGENT'])
702 && preg_match('/^(Shockwave|Adobe) Flash/i', $_SERVER['HTTP_USER_AGENT']));
703 }
704
705 /**
706 * Wrapper for get::$config->isSuperuser() to unify syntax
707 * @return Boolean
708 */
709 public static function superuser() {
710 return get::$config->isSuperuser();
711 }
712 }
713
714 /**
715 * Public interface to retrieve rendered elements and other objects
716 */
717 class get {
718 /**
719 * Opens up public access to config constants and variables and the cache object
720 * @var object
721 */
722 public static $config, $cache;
723
724 /**
725 * Used to store the page title and meta data for layout usage
726 *
727 * You can set properties for "title" and "meta" in the .config to act as default values (setting in the view
or
728 * controller will always override the defaults)
729 * public $title = 'My Default Title';
730 * public $meta = array('keyowords' => 'apples, bananas, kiwis', 'description' => 'My site is about...');
731 *
732 * @var string $title
733 * @var array $meta valid keys are description and keywords
734 */
735 public static $title, $meta = array();
736
737 /**
738 * Index of objects loaded, used to maintain uniqueness of singletons
739 * @var array
740 */
741 public static $loadedObjects = array();
742
743 /**
744 * Index of names of database-objects that have a connection open
745 * @var array Keys are DB names, vals are always Bool true
746 */
747 protected static $_dbConnected = array();
748
749 /**
750 * Returns fully qualified path to an MVC file
751 *
752 * @param string $type
753 * @param string $mvcName
754 * @return string
755 */
756 public static function mvcFilePath($type, $mvcName) {
757 return self::$config->pathToMvc . self::$config->{$type . 'Folder'}
758 . self::$config->DS . $mvcName . self::$config->fileExtension;
759 }
760
761 /**
762 * Gets the current URL
763 *
764 * @param array Optional, keys:
765 * get - Boolean Default: false - include GET URL if it exists
766 * abs - Boolean Default: false - true=absolute URL (aka. FQDN), false=just the path for relative
links
767 * ssl - Boolean Default: null - true=https, false=http, unset/null=auto-selects the current
protocol
768 * a true or false value implies abs=true
769 * @return string
770 */
771 public static function url(array $args = array()) {
772 $ssl = null;
773 $get = false;
774 $abs = false;
775 extract($args);
776
777 if (!isset($_SERVER['HTTP_HOST']) && PHP_SAPI == 'cli') {
778 $_SERVER['HTTP_HOST'] = trim(`hostname`);
779 $argv = $_SERVER['argv'];
780 array_shift($argv);
781 $_SERVER['REDIRECT_URL'] = '/' . implode('/', $argv);
782 $get = false; // command-line has no GET
783 }
784
785 $url = (isset($_SERVER['REDIRECT_URL']) ? $_SERVER['REDIRECT_URL'] : $_SERVER['SCRIPT_NAME']);
786 if (substr($url, -1) == '/') { //strip trailing slash for URL consistency
787 $url = substr($url, 0, -1);
788 }
789
790 if (is_null($ssl) && $abs == true) {
791 $ssl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on');
792 }
793 if ($abs || !is_null($ssl)) {
794 $url = (!$ssl ? 'http://' : 'https://') . $_SERVER['HTTP_HOST'] . $url;
795 }
796
797 if ($get && isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING']) {
798 $url .= '?' . $_SERVER['QUERY_STRING'];
799 }
800 return ($url ? $url : '/');
801 }
802
803 /**
804 * Initializes a database connection
805 * @param string $db
806 */
807 public static function dbConnection($db) {
808 if (!isset(self::$_dbConnected[$db]) && method_exists(self::$config, 'dbConnect')) {
809 try {
810 self::$config->dbConnect($db);
811 self::$_dbConnected[$db] = true;
812 } catch (Exception $e) {
813 mvc::$params = $e;
814 mvc::$controller = get::$config->errorHandler;
815 mvc::$action = 'DatasourceError';
816 config::$$db = new blackhole;
817 }
818 }
819 }
820
821 /**
822 * Loads an object as a singleton
823 *
824 * @param string $objectType
825 * @param string $objectName
826 * @param mixed $args Optional, default is null - this is sent as an argument to the constructor and used as
part
827 * of the criteria to determine a unique singleton object - differing args return new
objects
828 * @return object
829 */
830 protected static function _loadObject($objectType, $objectName, $args = null) {
831 if ($objectName == null) {
832 $objectName = mvc::$controller;
833 }
834 $argsSerialized = serialize($args);
835 if (isset(self::$loadedObjects[$objectType][$objectName][$argsSerialized])) {
836 return self::$loadedObjects[$objectType][$objectName][$argsSerialized];
837 }
838 $objectFile = self::mvcFilePath($objectType . 's', $objectName);
839 if (is_file($objectFile)) {
840 if (strpos($objectName, '/')) { //objects within folders
841 $segments = explode('/', $objectName);
842 $objectName = array_shift($segments);
843 foreach ($segments as $segment) {
844 $objectName .= ucfirst($segment);
845 }
846 }
847 $objectClassName = mvc::getValidObjectName($objectName) . ucfirst($objectType);
848 if (!class_exists($objectClassName)) {
849 require $objectFile;
850 }
851 if (class_exists($objectClassName)) {
852 try {
853 $objectObject = new $objectClassName($args);
854 } catch (Exception $e) {
855 trigger_error($e->getMessage(), E_USER_WARNING);
856 return new blackhole;
857 }
858 if ($objectType == 'model' && config::MODEL_AUTOCONNECT) {
859 foreach (config::$modelObjects as $modelObject) {
860 self::dbConnection($modelObject);
861 $objectObject->$modelObject = config::$$modelObject;
862 }
863 }
864 self::$loadedObjects[$objectType][$objectName][$argsSerialized] = $objectObject;
865 if (DEBUG_MODE) {
866 debug::log($objectType . ' ' . $objectName . ' loaded');
867 }
868 return $objectObject;
869 } else {
870 $errorMsg = 'Class for ' . $objectType . ' ' . $objectName . ' could not be found';
871 }
872 } else {
873 $errorMsg = 'File for ' . $objectType . ' ' . $objectName . ' could not be found';
874 }
875 trigger_error($errorMsg, E_USER_WARNING);
876 }
877
878 /**
879 * Returns a model object
880 *
881 * @param string $model
882 * @param mixed $args Optional - arguments to be passed to the model-object constructor
883 * @return object
884 */
885 public static function model($model = null, $args = null) {
886 return self::_loadObject('model', $model, $args);
887 }
888
889 /**
890 * Returns a component object
891 *
892 * @param string $component
893 * @param mixed $args Optional - arguments to be passed to the component-object constructor
894 * @return object
895 */
896 public static function component($component = null, $args = null) {
897 return self::_loadObject('component', $component, $args);
898 }
899
900 /**
901 * Returns a helper object
902 *
903 * @param string $helper
904 * @param mixed $args Optional - arguments to be passed to the helper-object constructor
905 * @return object
906 */
907 public static function helper($helper, $args = null) {
908 if (is_array($helper)) {
909 array_walk($helper, array('self', __METHOD__));
910 return;
911 }
912 if (!in_array($helper, self::$config->helpers)) {
913 self::$config->helpers[] = $helper;
914 }
915 return self::_loadObject('helper', $helper, $args);
916 }
917
918 /**
919 * Renders an element and returns the results as a string
920 *
921 * @param string $element
922 * @param array $params
923 */
924 public static function element($element, $params = array()) {
925 $elementId = $element . $paramsString = serialize($params);
926 if (!isset(self::$loadedObjects['element'][$elementId])) {
927 self::$loadedObjects['element'][$elementId] = mvc::getElement($element, $params);
928 }
929 return self::$loadedObjects['element'][$elementId];
930 }
931
932 /**
933 * Overloads the php function htmlentities and changes the default charset to UTF-8 and the default value for
the
934 * fourth parameter $doubleEncode to false. Also adds ability to pass a null value to get the default
$quoteStyle
935 * and $charset (removes need to repeatedly define ENT_COMPAT, 'UTF-8', just to access the $doubleEncode
argument)
936 *
937 * If you are using a PHP version prior to 5.2.3 the $doubleEncode parameter is not available so you will
need
938 * to comment out the last parameter in the return clause (including the preceding comma)
939 *
940 * @param string $string
941 * @param int $quoteStyle Uses ENT_COMPAT if null or omitted
942 * @param string $charset Uses UTF-8 if null or omitted
943 * @param boolean $doubleEncode
944 * @return string
945 */
946 public static function htmlentities($string, $quoteStyle = ENT_COMPAT, $charset = 'UTF-8', $doubleEncode =
false) {
947 return htmlentities($string, (!is_null($quoteStyle) ? $quoteStyle : ENT_COMPAT),
948 (!is_null($charset) ? $charset : 'UTF-8'), $doubleEncode);
949 }
950
951 /**
952 * Initialize the character maps needed for the xhtmlentities() method and verifies the argument values
953 * passed to it are valid.
954 *
955 * @param int $quoteStyle
956 * @param string $charset Only valid options are UTF-8 and ISO-8859-1 (Latin-1)
957 * @param boolean $doubleEncode
958 */
959 protected static function initXhtmlentities($quoteStyle, $charset, $doubleEncode) {
960 $chars = get_html_translation_table(HTML_ENTITIES, $quoteStyle);
961 if (isset($chars)) {
962 unset($chars['<'], $chars['>'], $chars['&']);
963 $charMaps[$quoteStyle]['ISO-8859-1'][true] = $chars;
964 $charMaps[$quoteStyle]['ISO-8859-1'][false] = array_combine(array_values($chars), $chars);
965 $charMaps[$quoteStyle]['UTF-8'][true] = array_combine(array_map('utf8_encode', array_keys($chars)),
$chars);
966 $charMaps[$quoteStyle]['UTF-8'][false] = array_merge($charMaps[$quoteStyle]['ISO-8859-1'][false],
967 $charMaps[$quoteStyle]['UTF-8'][true]);
968 self::$loadedObjects['xhtmlEntities'] = $charMaps;
969 }
970 if (!isset($charMaps[$quoteStyle][$charset][$doubleEncode])) {
971 if (!isset($chars)) {
972 $invalidArgument = 'quoteStyle = ' . $quoteStyle;
973 } else if (!isset($charMaps[$quoteStyle][$charset])) {
974 $invalidArgument = 'charset = ' . $charset;
975 } else {
976 $invalidArgument = 'doubleEncode = ' . (string) $doubleEncode;
977 }
978 trigger_error('Undefined argument sent to xhtmlentities() method: ' . $invalidArgument,
E_USER_NOTICE);
979 }
980 }
981
982 /**
983 * Converts special characters in a string to XHTML-valid ASCII encoding the same as htmlentities except
984 * this method allows the use of HTML tags and ASCII-code within your string. Signature is the same as
htmlentities
985 * except that the only character sets available (third argument) are UTF-8 (default) and ISO-8859-1
(Latin-1).
986 *
987 * @param string $string
988 * @param int $quoteStyle Constants available are ENT_NOQUOTES (default), ENT_QUOTES, ENT_COMPAT
989 * @param string $charset Only valid options are UTF-8 (default) and ISO-8859-1 (Latin-1)
990 * @param boolean $doubleEncode Default is false
991 * @return string
992 */
993 public static function xhtmlentities($string, $quoteStyle = ENT_NOQUOTES, $charset = 'UTF-8',
994 $doubleEncode = false) {
995 $quoteStyles = array(ENT_NOQUOTES, ENT_QUOTES, ENT_COMPAT);
996 $quoteStyle = (!in_array($quoteStyle, $quoteStyles) ? current($quoteStyles) : $quoteStyle);
997 $charset = ($charset != 'ISO-8859-1' ? 'UTF-8' : $charset);
998 $doubleEncode = (Boolean) $doubleEncode;
999 if (!isset(self::$loadedObjects['xhtmlEntities'][$quoteStyle][$charset][$doubleEncode])) {
1000 self::initXhtmlentities($quoteStyle, $charset, $doubleEncode);
1001 }
1002 $string = strtr($string, self::$loadedObjects['xhtmlEntities'][$quoteStyle][$charset][$doubleEncode]);
1003 return preg_replace('/\&(?!([a-zA-Z]{3}[a-zA-Z0-9]{0,3}|#\d{1,8}|#x[a-zA-Z0-9]{2,8});)/', '&',
$string);
1004 }
1005 }
1006
1007 /**
1008 * Abstract class to extend model functionality (usage is optional, model functionality is not dependent on this)
1009 */
1010 abstract class model {
1011 /**
1012 * Enables true lazy-loading of database connections when config::MODEL_AUTOCONNECT is set to false and each
1013 * model extends this class
1014 *
1015 * @param string $var
1016 * @return object
1017 */
1018 public function __GET($var) {
1019 get::dbConnection($var);
1020 return $this->$var = config::$$var;
1021 }
1022 }
1023
1024 class cache {
1025 /**
1026 * Public access to the cache object
1027 * @return obj
1028 */
1029 public function __construct() {
1030 return get::$cache;
1031 }
1032
1033 /**
1034 * Static wrapper for the caching methods
1035 *
1036 * @param str $method
1037 * @param array $args
1038 * @return mixed
1039 */
1040 public static function __callStatic($method, array $args) {
1041 return call_user_func_array(array(get::$cache, $method), $args);
1042 }
1043
1044 /**
1045 * Automates caching of a function or method result
1046 *
1047 * @param str $id
1048 * @param mixed $method
1049 * @param array $args
1050 * @param int $expires
1051 * @return mixed
1052 */
1053 public static function method($id, $method, array $args = array(), $flag = null, $expires = null) {
1054 $return = get::$cache->get($id);
1055 if ($return === false) {
1056 $return = call_user_func_array($method, $args);
1057 get::$cache->set($id, $return, $flag, $expires);
1058 }
1059 return $return;
1060 }
1061
1062 /**
1063 * The next bunch of methods are workarounds for lack of __callStatic(), they can be safely removed in PHP
5.3+
1064 * @deprecated
1065 */
1066 public static function get() {
1067 $args = func_get_args();
1068 return call_user_func_array(array(get::$cache, __FUNCTION__), $args);
1069 }
1070 public static function set() {
1071 $args = func_get_args();
1072 return call_user_func_array(array(get::$cache, __FUNCTION__), $args);
1073 }
1074 public static function delete() {
1075 $args = func_get_args();
1076 return call_user_func_array(array(get::$cache, __FUNCTION__), $args);
1077 }
1078 public static function flush() {
1079 $args = func_get_args();
1080 return call_user_func_array(array(get::$cache, __FUNCTION__), $args);
1081 }
1082 }
1083
1084 /**
1085 * Public interface to load elements and cause redirects
1086 */
1087 class load {
1088 /**
1089 * Flag set to false to supress loading a view; used when the user is redirected
1090 * @var boolean
1091 */
1092 public static $loadView = true;
1093
1094 /**
1095 * Sends a redirects header and disables view rendering
1096 * This redirects via a browser command, this is not the same as changing controllers which is handled within
MVC
1097 *
1098 * @param string $url Optional, if undefined this will refresh the page (mostly useful for dumping post
values)
1099 * @param boolean $exit Optional, defaults to true - if false then the instance will be allowed to continue
after
1100 */
1101 public static function redirect($url = null, $exit = true) {
1102 self::$loadView = false;
1103 header('Location: ' . ($url ? $url : get::url(array('get' => true))));
1104 if ($exit) {
1105 exit(0);
1106 }
1107 }
1108
1109 /**
1110 * Loads the 404 Not Found error page
1111 *
1112 * During controller execution this takes effect only upon completion of the current controller-action
1113 * During view/layout execution this uses a browser-redirect
1114 *
1115 * For immediate redirection without further code-execution, "return" immediately via: return
load::error404();
1116 */
1117 public static function error404() {
1118 mvc::$controller = get::$config->errorHandler;
1119 mvc::$action = 'notFound';
1120 $state = mvc::state();
1121 if ($state == 'layout' || $state == 'view') {
1122 self::redirect('/' . mvc::$controller . '/' . mvc::$action);
1123 }
1124 }
1125
1126 /**
1127 * Loads an element to STDOUT
1128 *
1129 * @param string $element
1130 * @param array $params
1131 */
1132 public static function element($element, $params = array()) {
1133 return mvc::loadElement($element, $params);
1134 }
1135
1136 /**
1137 * Loads JavaScript library; any output goes directly to stdout (echos to the screen)
1138 * Simplified-access wrapper to htmlHelper::jsLoad()
1139 *
1140 * @param mixed $args Same args as htmlHelper::jsLoad()
1141 */
1142 public static function js($args) {
1143 echo get::helper('html')->jsLoad($args);
1144 }
1145 }
1146
1147 if (!defined('DONT_AUTOSTART_VORK')) {
1148 new mvc;
1149 }