PageRenderTime 47ms CodeModel.GetById 19ms app.highlight 20ms RepoModel.GetById 1ms app.codeStats 1ms

/web/concrete/core/libraries/view.php

https://github.com/glockops/concrete5
PHP | 995 lines | 550 code | 135 blank | 310 comment | 171 complexity | d18ab6d4db487b6cb7db49480de94212 MD5 | raw file
  1<?
  2
  3defined('C5_EXECUTE') or die("Access Denied.");
  4
  5/**
  6 * @package Core
  7 * @category Concrete
  8 * @author Andrew Embler <andrew@concrete5.org>
  9 * @copyright  Copyright (c) 2003-2008 Concrete5. (http://www.concrete5.org)
 10 * @license    http://www.concrete5.org/license/     MIT License
 11 *
 12 */
 13
 14/**
 15 * A generic object that every front-end template (view) or page extends.
 16 * @package Core
 17 * @author Andrew Embler <andrew@concrete5.org>
 18 * @category Concrete
 19 * @copyright  Copyright (c) 2003-2008 Concrete5. (http://www.concrete5.org)
 20 * @license    http://www.concrete5.org/license/     MIT License
 21 *
 22 */
 23	class Concrete5_Library_View extends Object {
 24			
 25		/**
 26		 * @var string
 27		 */ 
 28		private $viewPath;
 29		
 30		/**
 31		 * @var string
 32		 */
 33		protected $pkgHandle;
 34		
 35		/**
 36		 * @var bool
 37		 */
 38		protected $disableContentInclude = false;
 39		
 40		/**
 41		 * controller used by this particular view
 42		 * @access public
 43	     * @var object
 44		*/
 45		public $controller;
 46		
 47		
 48		/** 
 49		 * An array of items that get loaded into a page's header
 50		 * @var array
 51		 */
 52		private $headerItems = array();
 53
 54		/** 
 55		 * An array of items that get loaded into just before body close
 56		 * @var array
 57		 */
 58		private $footerItems = array();
 59
 60		/**
 61		 * themePaths holds the various hard coded paths to themes
 62		 * @access private
 63	     * @var array
 64		*/
 65		private $themePaths = array();	
 66	
 67		/**
 68		 * @var bool
 69		 */
 70		private $areLinksDisabled = false;
 71		
 72		/**
 73		 * editing mode is enabled or not
 74		 * @access private
 75	     * @var boolean
 76		*/	
 77		private $isEditingEnabled = true;
 78		
 79		/**
 80		 * getInstance() grabs one instance of the view w/the singleton pattern
 81		 * @return View
 82		*/
 83		public function getInstance() {
 84			static $instance;
 85			if (!isset($instance)) {
 86				$instance = new View();
 87			}
 88			return $instance;
 89		}		
 90		
 91		
 92		/**
 93		 * This grabs the theme for a particular path, if one exists in the themePaths array 
 94		 * @access private
 95	     * @param string $path
 96		 * @return string $theme
 97		*/
 98		private function getThemeFromPath($path) {
 99			// there's probably a more efficient way to do this
100			$theme = false;
101			$txt = Loader::helper('text');
102			foreach($this->themePaths as $lp => $layout) {
103				if ($txt->fnmatch($lp, $path)) {
104					$theme = $layout;
105					break;
106				}
107			}
108			return $theme;
109		}
110		
111		/** 
112		 * Returns a stylesheet found in a themes directory - but FIRST passes it through the tools CSS handler
113		 * in order to make certain style attributes found inside editable
114		 * @param string $stylesheet
115		 */
116		public function getStyleSheet($stylesheet) {
117			if ($this->isPreview()) {
118				return REL_DIR_FILES_TOOLS . '/css/' . DIRNAME_THEMES . '/' . $this->getThemeHandle() . '/' . $stylesheet . '?mode=preview&time=' . time();
119			}
120			$pt = PageTheme::getByHandle($this->getThemeHandle());
121			$file = $this->getThemePath() . '/' . $stylesheet;
122			$cacheFile = DIR_FILES_CACHE . '/' . DIRNAME_CSS . '/' . $this->getThemeHandle() . '/' . $stylesheet;
123			$env = Environment::get();
124			$themeRec = $env->getUncachedRecord(DIRNAME_THEMES . '/' . $this->getThemeHandle() . '/' . $stylesheet, $pt->getPackageHandle());
125			if (file_exists($cacheFile) && $themeRec->exists()) {
126				if (filemtime($cacheFile) > filemtime($themeRec->file)) {
127					return REL_DIR_FILES_CACHE . '/' . DIRNAME_CSS . '/' . $this->getThemeHandle() . '/' . $stylesheet;
128				}
129			}
130			if ($themeRec->exists()) {
131				$themeFile = $themeRec->file;
132				if (!file_exists(DIR_FILES_CACHE . '/' . DIRNAME_CSS)) {
133					@mkdir(DIR_FILES_CACHE . '/' . DIRNAME_CSS);
134				}
135				if (!file_exists(DIR_FILES_CACHE . '/' . DIRNAME_CSS . '/' . $this->getThemeHandle())) {
136					@mkdir(DIR_FILES_CACHE . '/' . DIRNAME_CSS . '/' . $this->getThemeHandle());
137				}
138				$fh = Loader::helper('file');
139				$stat = filemtime($themeFile);
140				if (!file_exists(dirname($cacheFile))) {
141					@mkdir(dirname($cacheFile), DIRECTORY_PERMISSIONS_MODE, true);
142				}
143				$style = $pt->parseStyleSheet($stylesheet);
144				$r = @file_put_contents($cacheFile, $style);
145				if ($r) {
146					return REL_DIR_FILES_CACHE . '/' . DIRNAME_CSS . '/' . $this->getThemeHandle() . '/' . $stylesheet;
147				} else {
148					return $this->getThemePath() . '/' . $stylesheet;
149				}
150			}
151		}
152
153		/** 
154		 * Function responsible for adding header items within the context of a view.
155		 * @access private
156		 */
157
158		public function addHeaderItem($item, $namespace = 'VIEW') {
159			if ($this->resolveItemConflicts($item)) {
160				$this->headerItems[$namespace][] = $item;
161			}
162		}
163		
164		/** 
165		 * Function responsible for adding footer items within the context of a view.
166		 * @access private
167		 */
168		public function addFooterItem($item, $namespace = 'VIEW') {
169			if ($this->resolveItemConflicts($item)) {
170				$this->footerItems[$namespace][] = $item;
171			}
172		}
173		
174		/**
175		 * Internal helper function for addHeaderItem() and addFooterItem().
176		 * Looks through header and footer items for anything of the same type
177		 * and having the same "unique handle" as the given item.
178		 *
179		 * HOW TO USE THIS FUNCTION:
180		 * When calling this function, just pass the first $item argument
181		 *  (the second optional argument is only for our own recursive use).
182		 * If we return FALSE, that means the given item should NOT be added to headerItems/footerItems.
183		 * If we return TRUE, then go ahead and add the item to headerItems/footerItems.
184		 *
185		 * NOTE: THIS FUNCTION HAS POTENTIAL SIDE-EFFECTS (IN ADDITION TO RETURN VALUE)...
186		 * ~If no duplicate is found, we return TRUE (with no side-effects).
187		 * ~If a duplicate is found and the given item has a HIGHER version than the found item,
188		 *  we return TRUE **AND** we remove the found duplicate from headerItems or footerItems!!
189		 * ~If a duplicate is found and the given item does NOT have a higher version than
190		 *  the found item, we return FALSE (with no side-effects).
191		 */
192		private function resolveItemConflicts($checkItem, &$againstItems = null) {
193			
194			//Only check items that have "unique handles"
195			if (empty($checkItem->handle)) {
196				return true;
197			}
198			
199			//Recursively check header items AND footer items
200			if (is_null($againstItems)) {
201				return ($this->resolveItemConflicts($checkItem, $this->headerItems) && $this->resolveItemConflicts($checkItem, $this->footerItems));
202			}
203			
204			//Loop through all items and check for duplicates
205			foreach ($againstItems as $itemNamespace => $namespaceItems) {
206				foreach ($namespaceItems as $itemKey => $againstItem) {
207					//Check the "unique handles"
208					if (!empty($againstItem->handle) && (strtolower($checkItem->handle['handle']) == strtolower($againstItem->handle['handle']))) {
209						//Check the item types (so js and css items can have the same handle without conflicting)
210						//Note that we consider both the JavaScript and InlineScript items to be the same "type".
211						$checkClass = get_class($checkItem);
212						$againstClass = get_class($againstItem);
213						if (($checkClass == $againstClass) || (!array_diff(array($checkClass, $againstClass), array('JavaScriptOutputObject', 'InlineScriptOutputObject')))) {
214							//Does the given item have a higher version than the existing found item?
215							if (version_compare($checkItem->handle['version'], $againstItem->handle['version'], '>')) {
216								//Yes (new item is higher) so remove old item
217								// and return true to indicate that the new item should be added.
218								unset($againstItems[$itemNamespace][$itemKey]); // bug note: if we didn't return in the next line, this would cause problems the next time the loop iterated!
219								return true;
220							} else {
221								//No (new item is not higher) so leave old item where it is
222								// and return false to indicate that the new item should *not* be added.
223								return false;
224							}
225						}
226					}
227				}
228			}
229			
230			//No duplicates found, so return true to indicate that it's okay to add the item.
231			return true;
232		}
233		
234		/**
235		 * returns an array of string header items, typically inserted into the html <head> of a page through the header_required element
236		 * @return array
237		 */
238		public function getHeaderItems() {
239			//Combine items from all namespaces into one list
240			$a1 = (is_array($this->headerItems['CORE'])) ? $this->headerItems['CORE'] : array();
241			$a2 = (is_array($this->headerItems['VIEW'])) ? $this->headerItems['VIEW'] : array();
242			$a3 = (is_array($this->headerItems['CONTROLLER'])) ? $this->headerItems['CONTROLLER'] : array();
243			
244			$items = array_merge($a1, $a2, $a3);
245			
246			//Remove exact string duplicates (items whose string representations are equal)
247			if (version_compare(PHP_VERSION, '5.2.9', '<')) {
248				$items = array_unique($items);
249			} else {
250				// stupid PHP (see http://php.net/array_unique#refsect1-function.array-unique-changelog )
251				$items = array_unique($items, SORT_STRING);
252			}
253			return $items;
254		}
255		
256		/**
257		 * returns an array of string footer items, typically inserted into the html before the close of the </body> tag of a page through the footer_required element
258		 * @return array
259		 */
260		public function getFooterItems() {
261			//Combine items from all namespaces into one list
262			$a1 = (is_array($this->footerItems['CORE'])) ? $this->footerItems['CORE'] : array();
263			$a2 = (is_array($this->footerItems['VIEW'])) ? $this->footerItems['VIEW'] : array();
264			$a3 = (is_array($this->footerItems['CONTROLLER'])) ? $this->footerItems['CONTROLLER'] : array();
265			$a4 = (is_array($this->footerItems['SCRIPT'])) ? $this->footerItems['SCRIPT'] : array();
266			
267			$items = array_merge($a1, $a2, $a3, $a4);
268			
269			//Remove exact string duplicates (items whose string representations are equal)
270			if (version_compare(PHP_VERSION, '5.2.9', '<')) {
271				$items = array_unique($items);
272			} else {
273				// stupid PHP (see http://php.net/array_unique#refsect1-function.array-unique-changelog )
274				$items = array_unique($items, SORT_STRING);
275			}
276			
277			//Also remove items having exact string duplicates in the header
278			$headerItems = $this->getHeaderItems();
279			$retItems = array();
280			foreach($items as $it) {
281				if (!in_array($it, $headerItems)) {
282					$retItems[] = $it;
283				}
284			}
285			
286			return $retItems;
287		}
288		
289		/** 
290		 * Function responsible for outputting header items
291		 * @access private
292		 */
293		public function outputHeaderItems() {
294			$items = $this->getHeaderItems();
295			
296			foreach($items as $hi) {
297				print $hi; // caled on two seperate lines because of pre php 5.2 __toString issues
298				print "\n";
299			}			
300		}
301		
302		/** 
303		 * Function responsible for outputting footer items
304		 * @access private
305		 */
306		public function outputFooterItems() {
307			$items = $this->getFooterItems();
308			
309			foreach($items as $hi) {
310				print $hi; // caled on two seperate lines because of pre php 5.2 __toString issues
311				print "\n";
312			}
313		}
314
315		/**
316		 * @param string
317		 * @return mixed
318		 */
319		public function field($fieldName) {
320			return $this->controller->field($fieldName);
321		}
322		
323		
324		/** 
325		 * @access private
326		 * @return void
327		 */
328		public function enablePreview() {
329			$this->isPreview = true;
330		}
331		
332		/** 
333		 * @access private
334		 * @return bool
335		 */
336		public function isPreview() {
337			return $this->isPreview;
338		}
339		
340		/** 
341		 * @access private
342		 * @return void
343		 */
344		public function disableLinks() {
345			$this->areLinksDisabled = true;
346		}
347		
348		/** 
349		 * @access private
350		 * @return void
351		 */
352		public function enableLinks() {
353			$this->areLinksDisabled = false;
354		}
355		
356		/** 
357		 * @access private
358		 * @return bool
359		 */
360		public function areLinksDisabled() {
361			return $this->areLinksDisabled;
362		}
363		
364		/** 
365		 * Returns the path used to access this view
366		 * @return string
367		 */
368		private function getViewPath() {
369			return $this->viewPath;
370		}
371		
372		/** 
373		 * Returns the handle of the currently active theme
374		 * @return string
375		 */
376		public function getThemeHandle() { return $this->ptHandle;}
377		
378		/**
379		 * gets the theme include file for this particular view		
380		 * @access public
381		 * @return string $theme
382		*/
383		public function getTheme() { return $this->theme;}
384	
385	
386		/**
387		 * gets the relative theme path for use in templates
388		 * @access public
389		 * @return string $themePath
390		*/
391		public function getThemePath() { return $this->themePath; }
392
393
394		/**
395		 * set directory of current theme for use when loading an element
396		 * @access public
397		 * @param string $path
398		*/
399		public function setThemeDirectory($path) { $this->themeDir=$path; }
400
401		/**
402		 * get directory of current theme for use when loading an element
403		 * @access public
404		 * @return string $themeDir
405		*/
406		public function getThemeDirectory() {return $this->themeDir;}
407		
408	
409		/**
410		 * used by the theme_paths and site_theme_paths files in config/ to hard coded certain paths to various themes
411		 * @access public
412		 * @param $path string
413		 * @param $theme object, if null site theme is default
414		 * @return void
415		*/
416		public function setThemeByPath($path, $theme = NULL) {
417			if ($theme != VIEW_CORE_THEME && $theme != 'dashboard') { // this is a hack until we figure this code out.
418				if (is_string($theme)) {
419					$pageTheme = PageTheme::getByHandle($theme);
420					if(is_object($pageTheme) && $pageTheme->getThemeHandle() == $theme) { // is it the theme that's been requested?
421						$theme = $pageTheme;
422					}
423				}
424			}
425			$this->themePaths[$path] = $theme;
426		}
427		
428		/**
429		 * Returns the value of the item in the POST array.
430		 * @access public
431		 * @param $key
432		 * @return void
433		*/
434		public function post($key) {
435			return $this->controller->post($key);
436		}
437
438		
439		/**
440		 * gets the collection object for the current view
441		 * @access public
442		 * @return Collection Object $c
443		*/
444		public function getCollectionObject() {
445			return $this->c;
446		}
447		
448		/**
449		 * sets the collection object for the current view
450		 * @access public
451		 * @return void
452		*/
453		public function setCollectionObject($c) {
454			$this->c = $c;
455		}
456
457
458		/**
459		 * Includes file from the current theme path. Similar to php's include().
460		 * Files included with this function will have all variables set using $this->controller->set() in their local scope,
461		 * As well as access to all that controller's helper objects.
462		 * @access public
463		 * @param string $file
464		 * @param array $args
465		 * @return void
466		*/
467		public function inc($file, $args = array()) {
468			extract($args);
469			if (isset($this->c)) {
470				$c = $this->c;
471			}
472			extract($this->controller->getSets());
473			extract($this->controller->getHelperObjects());
474			$env = Environment::get();
475			include($env->getPath(DIRNAME_THEMES . '/' . $this->getThemeHandle() . '/' . $file, $this->pkgHandle));
476		}
477
478	
479		/**
480		 * editing is enabled true | false
481		 * @access private
482		 * @return boolean
483		*/		
484		public function editingEnabled() {
485			return $this->isEditingEnabled;
486		}
487		
488	
489		/**
490		 * set's editing to disabled
491		 * @access private
492		 * @return void
493		*/
494		public function disableEditing() {
495			$this->isEditingEnabled = false;
496		}
497
498
499		/**
500		 * sets editing to enabled
501		 * @access private
502		 * @return void
503		*/
504		public function enableEditing() {
505			$this->isEditingEnabled = true;
506		}
507		
508	
509	
510	
511		/**
512		 * This is rarely used. We want to render another view
513		 * but keep the current controller. Views should probably not
514		 * auto-grab the controller anyway but whatever
515		 * @access private
516		 * @param object $cnt
517		 * @return void
518		*/
519		public function setController($cnt) {
520			$this->controller = $cnt;
521		}
522
523
524		/**
525		 * checks the current view to see if you're in that page's "section" (top level)
526		 * (with one exception: passing in the home page url ('' or '/') will always return false)
527		 * @access public
528		 * @param string $url
529		 * @return boolean | void
530		*/	
531		public function section($url) {
532			$cPath = Page::getCurrentPage()->getCollectionPath();
533			if (!empty($cPath)) {
534				$url = '/' . trim($url, '/');
535				if (strpos($cPath, $url) !== false && strpos($cPath, $url) == 0) {
536					return true;
537				}
538			}
539		}
540		
541		
542		/**
543		 * url is a utility function that is used inside a view to setup urls w/tasks and parameters		
544		 * @access public
545		 * @param string $action
546		 * @param string $task
547		 * @return string $url
548		*/	
549		public static function url($action, $task = null) {
550			$dispatcher = '';
551			if ((!defined('URL_REWRITING_ALL')) || (!URL_REWRITING_ALL)) {
552				$dispatcher = '/' . DISPATCHER_FILENAME;
553			}
554			
555			$action = trim($action, '/');
556			if ($action == '') {
557				return DIR_REL . '/';
558			}
559			
560			// if a query string appears in this variable, then we just pass it through as is
561			if (strpos($action, '?') > -1) {
562				return DIR_REL . $dispatcher. '/' . $action;
563			} else {
564				$_action = DIR_REL . $dispatcher. '/' . $action . '/';
565			}
566			
567			if ($task != null) {
568				if (ENABLE_LEGACY_CONTROLLER_URLS) {
569					$_action .= '-/' . $task;
570				} else {
571					$_action .= $task;			
572				}
573				$args = func_get_args();
574				if (count($args) > 2) {
575					for ($i = 2; $i < count($args); $i++){
576						$_action .= '/' . $args[$i];
577					}
578				}
579				
580				if (strpos($_action, '?') === false) {
581					$_action .= '/';
582				}
583			}
584			
585			return $_action;
586		}
587
588		public function checkMobileView() {
589			if(isset($_COOKIE['ccmDisableMobileView']) && $_COOKIE['ccmDisableMobileView'] == true) {
590				define('MOBILE_THEME_IS_ACTIVE', false);
591				return false; // break out if we've said we don't want the mobile theme
592			}
593			
594			$page = Page::getCurrentPage();
595			if($page instanceof Page && $page->isAdminArea()) {
596				define('MOBILE_THEME_IS_ACTIVE', false);
597				return false; // no mobile theme for the dashboard
598			}
599			
600			Loader::library('3rdparty/mobile_detect');
601			$md = new Mobile_Detect();
602			if ($md->isMobile()) {
603				$themeId = Config::get('MOBILE_THEME_ID');
604				if ($themeId > 0) {
605					$mobileTheme = PageTheme::getByID($themeId);
606					if($mobileTheme instanceof PageTheme) {
607						define('MOBILE_THEME_IS_ACTIVE',true);
608						// we have to grab the instance of the view
609						// since on_page_view doesn't give it to us
610						$this->setTheme($mobileTheme);
611					}
612				}
613			}
614			
615			if (!defined('MOBILE_THEME_IS_ACTIVE')) {
616				define('MOBILE_THEME_IS_ACTIVE', false);
617			}
618		}
619		
620		/**
621		 * A shortcut to posting back to the current page with a task and optional parameters. Only works in the context of 
622		 * @param string $action
623		 * @param string $task
624		 * @return string $url
625		 */
626		public function action($action, $task = null) {
627			$a = func_get_args();
628			array_unshift($a, $this->viewPath);
629			$ret = call_user_func_array(array($this, 'url'), $a);
630			return $ret;
631		}
632
633		/**
634		 * render's a fata error using the built-in view. This is currently only
635		 * used when the database connection fails
636		 * @access public
637		 * @param string $title
638		 * @param string $error
639		 * @return void
640		*/	
641		public function renderError($title, $error, $errorObj = null) {
642			$innerContent = $error;
643			$titleContent = $title; 
644			header('HTTP/1.1 500 Internal Server Error');
645			if (!isset($this) || (!$this)) {
646				$v = new View();
647				$v->setThemeForView(DIRNAME_THEMES_CORE, FILENAME_THEMES_ERROR . '.php', true);
648				include($v->getTheme());	
649				exit;
650			}
651			if (!isset($this->theme) || (!$this->theme) || (!file_exists($this->theme))) {
652				$this->setThemeForView(DIRNAME_THEMES_CORE, FILENAME_THEMES_ERROR . '.php', true);
653				include($this->theme);	
654				exit;			
655			} else {
656				Loader::element('error_fatal', array('innerContent' => $innerContent, 
657					'titleContent' => $titleContent));
658			}
659		}
660		
661		/**
662		 * sets the current theme
663		 * @access public
664		 * @param string $theme
665		 * @return void
666		*/	
667		public function setTheme($theme) {
668			$this->themeOverride = $theme;
669		}
670		
671		/**
672		 * set theme takes either a text-based theme ("concrete" or "dashboard" or something)
673		 * or a PageTheme object and sets information in the view about that theme. This is called internally
674		 * and is always passed the correct item based on context
675		 * 
676		 * @access protected
677		 * @param PageTheme object $pl
678		 * @param string $filename
679		 * @param boolean $wrapTemplateInTheme
680		 * @return void
681		*/	
682		protected function setThemeForView($pl, $filename, $wrapTemplateInTheme = false) {
683			// wrapTemplateInTheme gets set to true if we're passing the filename of a single page or page type file through 
684			$pkgID = 0;
685			$env = Environment::get();
686			if ($pl instanceof PageTheme) {
687				$this->ptHandle = $pl->getThemeHandle();
688				if ($pl->getPackageID() > 0) {
689					$pkgID = $pl->getPackageID();
690					$this->pkgHandle = $pl->getPackageHandle();
691				}
692			
693				$rec = $env->getRecord(DIRNAME_THEMES . '/' . $pl->getThemeHandle() . '/' . $filename, $this->pkgHandle);
694				if (!$rec->exists()) {
695					if ($wrapTemplateInTheme) {
696						$theme = $env->getPath(DIRNAME_THEMES . '/' . $pl->getThemeHandle() . '/' . FILENAME_THEMES_VIEW, $this->pkgHandle);
697					} else {
698						$theme = $env->getPath(DIRNAME_THEMES . '/' . $pl->getThemeHandle() . '/' . FILENAME_THEMES_DEFAULT, $this->pkgHandle);
699					}
700				} else {
701					$theme = $rec->file;
702					$this->disableContentInclude = true;
703				}
704				
705				$themeDir = str_replace('/' . FILENAME_THEMES_DEFAULT, '', $env->getPath(DIRNAME_THEMES . '/' . $pl->getThemeHandle() . '/' . FILENAME_THEMES_DEFAULT, $this->pkgHandle));
706				$themePath = str_replace('/' . FILENAME_THEMES_DEFAULT, '', $env->getURL(DIRNAME_THEMES . '/' . $pl->getThemeHandle() . '/' . FILENAME_THEMES_DEFAULT, $this->pkgHandle));
707			} else {
708				$this->ptHandle = $pl;
709				if (file_exists(DIR_FILES_THEMES . '/' . $pl . '/' . $filename)) {
710					$themePath = DIR_REL . '/' . DIRNAME_THEMES . '/' . $pl;
711					$theme = DIR_FILES_THEMES . "/" . $pl . '/' . $filename;
712					$themeDir = DIR_FILES_THEMES . "/" . $pl;
713				} else if (file_exists(DIR_FILES_THEMES . '/' . $pl . '/' . FILENAME_THEMES_VIEW)) {
714					$themePath = DIR_REL . '/' . DIRNAME_THEMES . '/' . $pl;
715					$theme = DIR_FILES_THEMES . "/" . $pl . '/' . FILENAME_THEMES_VIEW;
716					$themeDir = DIR_FILES_THEMES . "/" . $pl;
717				} else if (file_exists(DIR_FILES_THEMES . '/' . DIRNAME_THEMES_CORE . '/' . $pl . '.php')) {
718					$theme = DIR_FILES_THEMES . '/' . DIRNAME_THEMES_CORE . "/" . $pl . '.php';
719					$themeDir = DIR_FILES_THEMES . '/' . DIRNAME_THEMES_CORE;
720				} else if (file_exists(DIR_FILES_THEMES_CORE . "/" . $pl . '/' . $filename)) {
721					$themePath = ASSETS_URL . '/' . DIRNAME_THEMES . '/' . DIRNAME_THEMES_CORE . '/' . $pl;
722					$theme = DIR_FILES_THEMES_CORE . "/" . $pl . '/' . $filename;
723					$themeDir = DIR_FILES_THEMES_CORE . "/" . $pl;
724				} else if (file_exists(DIR_FILES_THEMES_CORE . "/" . $pl . '/' . FILENAME_THEMES_VIEW)) {
725					$themePath = ASSETS_URL . '/' . DIRNAME_THEMES . '/' . DIRNAME_THEMES_CORE . '/' . $pl;
726					$theme = DIR_FILES_THEMES_CORE . "/" . $pl . '/' . FILENAME_THEMES_VIEW;
727					$themeDir = DIR_FILES_THEMES_CORE . "/" . $pl;
728				} else if (file_exists(DIR_FILES_THEMES_CORE_ADMIN . "/" . $pl . '.php')) {
729					$theme = DIR_FILES_THEMES_CORE_ADMIN . "/" . $pl . '.php';
730					$themeDir = DIR_FILES_THEMES_CORE_ADMIN;
731				}
732			}
733			
734			$this->theme = $theme;
735			$this->themePath = $themePath;
736			$this->themeDir = $themeDir;
737			$this->themePkgID = $pkgID;
738		}
739		public function escape($text){
740			Loader::helper('text');
741			return TextHelper::sanitize($text);
742		}
743		/**
744		 * render takes one argument - the item being rendered - and it can either be a path or a page object
745		 * @access public
746		 * @param string $view
747		 * @param array $args
748		 * @return void
749		*/	
750		public function render($view, $args = null) { 
751		
752			if (is_array($args)) {
753				extract($args);
754			}
755
756			// strip off a slash if there is one at the end
757			if (is_string($view)) {
758				if (substr($view, strlen($view) - 1) == '/') {
759					$view = substr($view, 0, strlen($view) - 1);
760				}
761			}
762			
763			$dsh = Loader::helper('concrete/dashboard');
764
765			$wrapTemplateInTheme = false;
766			$this->checkMobileView();
767			if (defined('DB_DATABASE') && ($view !== '/upgrade')) {
768				Events::fire('on_start', $this);
769			}
770			
771			// Extract controller information from the view, and put it in the current context
772			if (!isset($this->controller)) {
773				$this->controller = Loader::controller($view);
774				$this->controller->setupAndRun();
775			}
776
777			if ($this->controller->getRenderOverride() != '') {
778			   $view = $this->controller->getRenderOverride();
779			}
780			
781			// Determine which inner item to load, load it, and stick it in $innerContent
782			$content = false;
783
784			ob_start();			
785			if ($view instanceof Page) {
786
787				$_pageBlocks = $view->getBlocks();
788
789				if (!$dsh->inDashboard()) {
790					$_pageBlocksGlobal = $view->getGlobalBlocks();
791					$_pageBlocks = array_merge($_pageBlocks, $_pageBlocksGlobal);
792				}
793
794				// do we have any custom menu plugins?
795				$cp = new Permissions($view);
796				if ($cp->canViewToolbar()) { 
797					$ih = Loader::helper('concrete/interface/menu');
798					$_interfaceItems = $ih->getPageHeaderMenuItems();
799					foreach($_interfaceItems as $_im) {
800						$_controller = $_im->getController();
801						$_controller->outputAutoHeaderItems();
802					}
803					unset($_interfaceItems);
804					unset($_im);
805					unset($_controller);
806				}
807				unset($_interfaceItems);
808				unset($_im);
809				unset($_controller);
810				
811				
812				// now, we output all the custom style records for the design tab in blocks/areas on the page
813				$c = $this->getCollectionObject();
814				$view->outputCustomStyleHeaderItems(); 	
815				
816				$viewPath = $view->getCollectionPath();
817				$this->viewPath = $viewPath;
818				
819				$cFilename = $view->getCollectionFilename();
820				$ctHandle = $view->getCollectionTypeHandle();
821				$editMode = $view->isEditMode();
822				$c = $view;
823				$this->c = $c;
824				
825				$env = Environment::get();
826				// $view is a page. It can either be a SinglePage or just a Page, but we're not sure at this point, unfortunately
827				if ($view->getCollectionTypeID() == 0 && $cFilename) {
828					$wrapTemplateInTheme = true;
829					$cFilename = trim($cFilename, '/');
830					$content = $env->getPath(DIRNAME_PAGES . '/' . $cFilename, $view->getPackageHandle());
831					$themeFilename = $c->getCollectionHandle() . '.php';						
832				} else {
833					$rec = $env->getRecord(DIRNAME_PAGE_TYPES . '/' . $ctHandle . '.php', $view->getPackageHandle());
834					if ($rec->exists()) {
835						$wrapTemplateInTheme = true;
836						$content = $rec->file;
837					}
838					$themeFilename = $ctHandle . '.php';
839				}
840				
841				
842			} else if (is_string($view)) {
843				
844				// if we're passing a view but our render override is not null, that means that we're passing 
845				// a new view from within a controller. If that's the case, then we DON'T override the viewPath, we want to keep it
846				
847				// In order to enable editable 404 pages, other editable pages that we render without actually visiting
848				if (defined('DB_DATABASE') && $view == '/page_not_found') {
849					$pp = Page::getByPath($view);
850					if (!$pp->isError()) {
851						$this->c = $pp;
852					}
853				}
854				
855				$viewPath = $view;
856				if ($this->controller->getRenderOverride() != '' && $this->getCollectionObject() != null) {
857					// we are INSIDE a collection renderring a view. Which means we want to keep the viewPath that of the collection
858					$this->viewPath = $this->getCollectionObject()->getCollectionPath();
859				}
860				
861				// we're just passing something like "/login" or whatever. This will typically just be 
862				// internal Concrete stuff, but we also prepare for potentially having something in DIR_FILES_CONTENT (ie: the webroot)
863				if (file_exists(DIR_FILES_CONTENT . "/{$view}/" . FILENAME_COLLECTION_VIEW)) {
864					$content = DIR_FILES_CONTENT . "/{$view}/" . FILENAME_COLLECTION_VIEW;
865				} else if (file_exists(DIR_FILES_CONTENT . "/{$view}.php")) {
866					$content = DIR_FILES_CONTENT . "/{$view}.php";
867				} else if (file_exists(DIR_FILES_CONTENT_REQUIRED . "/{$view}/" . FILENAME_COLLECTION_VIEW)) {
868					$content = DIR_FILES_CONTENT_REQUIRED . "/{$view}/" . FILENAME_COLLECTION_VIEW;
869				} else if (file_exists(DIR_FILES_CONTENT_REQUIRED . "/{$view}.php")) {
870					$content = DIR_FILES_CONTENT_REQUIRED . "/{$view}.php";
871				} else if ($this->getCollectionObject() != null && $this->getCollectionObject()->isGeneratedCollection() && $this->getCollectionObject()->getPackageID() > 0) {
872					//This is a single_page associated with a package, so check the package views as well
873					$pagePkgPath = Package::getByID($this->getCollectionObject()->getPackageID())->getPackagePath();
874					if (file_exists($pagePkgPath . "/single_pages/{$view}/" . FILENAME_COLLECTION_VIEW)) {
875						$content = $pagePkgPath . "/single_pages/{$view}/" . FILENAME_COLLECTION_VIEW;
876					} else if (file_exists($pagePkgPath . "/single_pages/{$view}.php")) {
877						$content = $pagePkgPath . "/single_pages/{$view}.php";
878					}
879				}
880				$wrapTemplateInTheme = true;
881				$themeFilename = $view . '.php';
882			}
883			
884			
885			if (is_object($this->c)) {
886				$c = $this->c;
887				if (defined('DB_DATABASE') && ($view == '/page_not_found' || $view == '/login')) {
888					$view = $c;
889					$req = Request::get();
890					$req->setCurrentPage($c);
891					$_pageBlocks = $view->getBlocks();
892					$_pageBlocksGlobal = $view->getGlobalBlocks();
893					$_pageBlocks = array_merge($_pageBlocks, $_pageBlocksGlobal);
894				}
895			}
896			
897			if (is_array($_pageBlocks)) {
898				foreach($_pageBlocks as $b1) {
899					$b1p = new Permissions($b1);
900					if ($b1p->canRead()) { 
901						$btc = $b1->getInstance();
902						// now we inject any custom template CSS and JavaScript into the header
903						if('Controller' != get_class($btc)){
904							$btc->outputAutoHeaderItems();
905						}
906						$btc->runTask('on_page_view', array($view));
907					}
908				}
909			}			
910			
911			// Determine which outer item/theme to load
912			// obtain theme information for this collection
913			if (isset($this->themeOverride)) {
914				$theme = $this->themeOverride;
915			} else if ($this->controller->theme != false) {
916				$theme = $this->controller->theme;
917			} else if (($tmpTheme = $this->getThemeFromPath($viewPath)) != false) {
918				$theme = $tmpTheme;
919			} else if (is_object($this->c) && ($tmpTheme = $this->c->getCollectionThemeObject()) != false) {
920				$theme = $tmpTheme;
921			} else {
922				$theme = FILENAME_COLLECTION_DEFAULT_THEME;
923			}		
924			
925			$this->setThemeForView($theme, $themeFilename, $wrapTemplateInTheme);
926
927
928			// finally, we include the theme (which was set by setTheme and will automatically include innerContent)
929			// disconnect from our db and exit
930
931			$this->controller->on_before_render();
932			extract($this->controller->getSets());
933			extract($this->controller->getHelperObjects());
934
935			if ($content != false && (!$this->disableContentInclude)) {
936				include($content);
937			}
938
939			$innerContent = ob_get_contents();
940			
941			if (ob_get_level() > OB_INITIAL_LEVEL) {
942				ob_end_clean();
943			}
944
945			if (defined('DB_DATABASE') && ($view !== '/upgrade')) {
946				Events::fire('on_before_render', $this);
947			}
948						
949			if (defined('APP_CHARSET')) {
950				header("Content-Type: text/html; charset=" . APP_CHARSET);
951			}
952			
953			if (file_exists($this->theme)) {
954				
955				$cache = PageCache::getLibrary();
956				$shouldAddToCache = $cache->shouldAddToCache($this);
957				if ($shouldAddToCache) {
958					$cache->outputCacheHeaders($c);
959				}
960
961				ob_start();
962				include($this->theme);
963				$pageContent = ob_get_contents();
964				ob_end_clean();
965				
966				$ret = Events::fire('on_page_output', $pageContent);
967				if($ret != '') {
968					print $ret;
969					$pageContent = $ret;
970				} else {
971					print $pageContent;
972				}
973
974				$cache = PageCache::getLibrary();
975				if ($shouldAddToCache) {
976					$cache->set($c, $pageContent);
977				}
978
979			
980			} else {
981				throw new Exception(t('File %s not found. All themes need default.php and view.php files in them. Consult concrete5 documentation on how to create these files.', $this->theme));
982			}
983			
984			if (defined('DB_DATABASE') && ($view !== '/upgrade')) {
985				Events::fire('on_render_complete', $this);
986			}
987			
988			if (ob_get_level() == OB_INITIAL_LEVEL) {
989				require(DIR_BASE_CORE . '/startup/jobs.php');
990				require(DIR_BASE_CORE . '/startup/shutdown.php');
991				exit;
992			}
993			
994		}
995	}