PageRenderTime 162ms CodeModel.GetById 30ms app.highlight 82ms RepoModel.GetById 39ms app.codeStats 1ms

/concrete/core/libraries/view.php

https://bitbucket.org/selfeky/xclusivescardwebsite
PHP | 1000 lines | 548 code | 139 blank | 313 comment | 167 complexity | 5650c19522ff844b2edbccb020c2d133 MD5 | raw file
   1<?php 
   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			
 295			$items = $this->getHeaderItems();
 296			
 297			// Loop through all items
 298			// If it is a header output object, place each item in a separate array for its container directory
 299			// Otherwise, put it in the outputPost array
 300			
 301			$outputPost = array();
 302			$output = array();
 303			
 304			foreach($items as $hi) {
 305				print $hi; // caled on two seperate lines because of pre php 5.2 __toString issues
 306				print "\n";
 307			}			
 308			
 309		}
 310		
 311		/** 
 312		 * Function responsible for outputting footer items
 313		 * @access private
 314		 */
 315		public function outputFooterItems() {
 316			$items = $this->getFooterItems();
 317			
 318			foreach($items as $hi) {
 319				print $hi; // caled on two seperate lines because of pre php 5.2 __toString issues
 320				print "\n";
 321			}
 322		}
 323
 324		/**
 325		 * @param string
 326		 * @return mixed
 327		 */
 328		public function field($fieldName) {
 329			return $this->controller->field($fieldName);
 330		}
 331		
 332		
 333		/** 
 334		 * @access private
 335		 * @return void
 336		 */
 337		public function enablePreview() {
 338			$this->isPreview = true;
 339		}
 340		
 341		/** 
 342		 * @access private
 343		 * @return bool
 344		 */
 345		public function isPreview() {
 346			return $this->isPreview;
 347		}
 348		
 349		/** 
 350		 * @access private
 351		 * @return void
 352		 */
 353		public function disableLinks() {
 354			$this->areLinksDisabled = true;
 355		}
 356		
 357		/** 
 358		 * @access private
 359		 * @return void
 360		 */
 361		public function enableLinks() {
 362			$this->areLinksDisabled = false;
 363		}
 364		
 365		/** 
 366		 * @access private
 367		 * @return bool
 368		 */
 369		public function areLinksDisabled() {
 370			return $this->areLinksDisabled;
 371		}
 372		
 373		/** 
 374		 * Returns the path used to access this view
 375		 * @return string
 376		 */
 377		private function getViewPath() {
 378			return $this->viewPath;
 379		}
 380		
 381		/** 
 382		 * Returns the handle of the currently active theme
 383		 * @return string
 384		 */
 385		public function getThemeHandle() { return $this->ptHandle;}
 386		
 387		/**
 388		 * gets the theme include file for this particular view		
 389		 * @access public
 390		 * @return string $theme
 391		*/
 392		public function getTheme() { return $this->theme;}
 393	
 394	
 395		/**
 396		 * gets the relative theme path for use in templates
 397		 * @access public
 398		 * @return string $themePath
 399		*/
 400		public function getThemePath() { return $this->themePath; }
 401
 402
 403		/**
 404		 * set directory of current theme for use when loading an element
 405		 * @access public
 406		 * @param string $path
 407		*/
 408		public function setThemeDirectory($path) { $this->themeDir=$path; }
 409
 410		/**
 411		 * get directory of current theme for use when loading an element
 412		 * @access public
 413		 * @return string $themeDir
 414		*/
 415		public function getThemeDirectory() {return $this->themeDir;}
 416		
 417	
 418		/**
 419		 * used by the theme_paths and site_theme_paths files in config/ to hard coded certain paths to various themes
 420		 * @access public
 421		 * @param $path string
 422		 * @param $theme object, if null site theme is default
 423		 * @return void
 424		*/
 425		public function setThemeByPath($path, $theme = NULL) {
 426			if ($theme != VIEW_CORE_THEME && $theme != 'dashboard') { // this is a hack until we figure this code out.
 427				if (is_string($theme)) {
 428					$pageTheme = PageTheme::getByHandle($theme);
 429					if(is_object($pageTheme) && $pageTheme->getThemeHandle() == $theme) { // is it the theme that's been requested?
 430						$theme = $pageTheme;
 431					}
 432				}
 433			}
 434			$this->themePaths[$path] = $theme;
 435		}
 436		
 437		/**
 438		 * Returns the value of the item in the POST array.
 439		 * @access public
 440		 * @param $key
 441		 * @return void
 442		*/
 443		public function post($key) {
 444			return $this->controller->post($key);
 445		}
 446
 447		
 448		/**
 449		 * gets the collection object for the current view
 450		 * @access public
 451		 * @return Collection Object $c
 452		*/
 453		public function getCollectionObject() {
 454			return $this->c;
 455		}
 456		
 457		/**
 458		 * sets the collection object for the current view
 459		 * @access public
 460		 * @return void
 461		*/
 462		public function setCollectionObject($c) {
 463			$this->c = $c;
 464		}
 465
 466
 467		/**
 468		 * Includes file from the current theme path. Similar to php's include().
 469		 * Files included with this function will have all variables set using $this->controller->set() in their local scope,
 470		 * As well as access to all that controller's helper objects.
 471		 * @access public
 472		 * @param string $file
 473		 * @param array $args
 474		 * @return void
 475		*/
 476		public function inc($file, $args = array()) {
 477			extract($args);
 478			if (isset($this->c)) {
 479				$c = $this->c;
 480			}
 481			extract($this->controller->getSets());
 482			extract($this->controller->getHelperObjects());
 483			$env = Environment::get();
 484			include($env->getPath(DIRNAME_THEMES . '/' . $this->getThemeHandle() . '/' . $file, $this->pkgHandle));
 485		}
 486
 487	
 488		/**
 489		 * editing is enabled true | false
 490		 * @access private
 491		 * @return boolean
 492		*/		
 493		public function editingEnabled() {
 494			return $this->isEditingEnabled;
 495		}
 496		
 497	
 498		/**
 499		 * set's editing to disabled
 500		 * @access private
 501		 * @return void
 502		*/
 503		public function disableEditing() {
 504			$this->isEditingEnabled = false;
 505		}
 506
 507
 508		/**
 509		 * sets editing to enabled
 510		 * @access private
 511		 * @return void
 512		*/
 513		public function enableEditing() {
 514			$this->isEditingEnabled = true;
 515		}
 516		
 517	
 518	
 519	
 520		/**
 521		 * This is rarely used. We want to render another view
 522		 * but keep the current controller. Views should probably not
 523		 * auto-grab the controller anyway but whatever
 524		 * @access private
 525		 * @param object $cnt
 526		 * @return void
 527		*/
 528		public function setController($cnt) {
 529			$this->controller = $cnt;
 530		}
 531
 532
 533		/**
 534		 * checks the current view to see if you're in that page's "section" (top level)
 535		 * (with one exception: passing in the home page url ('' or '/') will always return false)
 536		 * @access public
 537		 * @param string $url
 538		 * @return boolean | void
 539		*/	
 540		public function section($url) {
 541			$cPath = Page::getCurrentPage()->getCollectionPath();
 542			if (!empty($cPath)) {
 543				$url = '/' . trim($url, '/');
 544				if (strpos($cPath, $url) !== false && strpos($cPath, $url) == 0) {
 545					return true;
 546				}
 547			}
 548		}
 549		
 550		
 551		/**
 552		 * url is a utility function that is used inside a view to setup urls w/tasks and parameters		
 553		 * @access public
 554		 * @param string $action
 555		 * @param string $task
 556		 * @return string $url
 557		*/	
 558		public function url($action, $task = null) {
 559			$dispatcher = '';
 560			if ((!URL_REWRITING_ALL) || !defined('URL_REWRITING_ALL')) {
 561				$dispatcher = '/' . DISPATCHER_FILENAME;
 562			}
 563			
 564			$action = trim($action, '/');
 565			if ($action == '') {
 566				return DIR_REL . '/';
 567			}
 568			
 569			// if a query string appears in this variable, then we just pass it through as is
 570			if (strpos($action, '?') > -1) {
 571				return DIR_REL . $dispatcher. '/' . $action;
 572			} else {
 573				$_action = DIR_REL . $dispatcher. '/' . $action . '/';
 574			}
 575			
 576			if ($task != null) {
 577				if (ENABLE_LEGACY_CONTROLLER_URLS) {
 578					$_action .= '-/' . $task;
 579				} else {
 580					$_action .= $task;			
 581				}
 582				$args = func_get_args();
 583				if (count($args) > 2) {
 584					for ($i = 2; $i < count($args); $i++){
 585						$_action .= '/' . $args[$i];
 586					}
 587				}
 588				
 589				if (strpos($_action, '?') === false) {
 590					$_action .= '/';
 591				}
 592			}
 593			
 594			return $_action;
 595		}
 596
 597		public function checkMobileView() {
 598			if(isset($_COOKIE['ccmDisableMobileView']) && $_COOKIE['ccmDisableMobileView'] == true) {
 599				define('MOBILE_THEME_IS_ACTIVE', false);
 600				return false; // break out if we've said we don't want the mobile theme
 601			}
 602			
 603			$page = Page::getCurrentPage();
 604			if($page instanceof Page && $page->isAdminArea()) {
 605				define('MOBILE_THEME_IS_ACTIVE', false);
 606				return false; // no mobile theme for the dashboard
 607			}
 608			
 609			Loader::library('3rdparty/mobile_detect');
 610			$md = new Mobile_Detect();
 611			if ($md->isMobile()) {
 612				$themeId = Config::get('MOBILE_THEME_ID');
 613				if ($themeId > 0) {
 614					$mobileTheme = PageTheme::getByID($themeId);
 615					if($mobileTheme instanceof PageTheme) {
 616						define('MOBILE_THEME_IS_ACTIVE',true);
 617						// we have to grab the instance of the view
 618						// since on_page_view doesn't give it to us
 619						$this->setTheme($mobileTheme);
 620					}
 621				}
 622			}
 623			
 624			if (!defined('MOBILE_THEME_IS_ACTIVE')) {
 625				define('MOBILE_THEME_IS_ACTIVE', false);
 626			}
 627		}
 628		
 629		/**
 630		 * A shortcut to posting back to the current page with a task and optional parameters. Only works in the context of 
 631		 * @param string $action
 632		 * @param string $task
 633		 * @return string $url
 634		 */
 635		public function action($action, $task = null) {
 636			$a = func_get_args();
 637			array_unshift($a, $this->viewPath);
 638			$ret = call_user_func_array(array($this, 'url'), $a);
 639			return $ret;
 640		}
 641
 642		/**
 643		 * render's a fata error using the built-in view. This is currently only
 644		 * used when the database connection fails
 645		 * @access public
 646		 * @param string $title
 647		 * @param string $error
 648		 * @return void
 649		*/	
 650		public function renderError($title, $error, $errorObj = null) {
 651			$innerContent = $error;
 652			$titleContent = $title; 
 653			header('HTTP/1.1 500 Internal Server Error');
 654			if (!isset($this) || (!$this)) {
 655				$v = new View();
 656				$v->setThemeForView(DIRNAME_THEMES_CORE, FILENAME_THEMES_ERROR . '.php', true);
 657				include($v->getTheme());	
 658				exit;
 659			}
 660			if (!isset($this->theme) || (!$this->theme) || (!file_exists($this->theme))) {
 661				$this->setThemeForView(DIRNAME_THEMES_CORE, FILENAME_THEMES_ERROR . '.php', true);
 662				include($this->theme);	
 663				exit;			
 664			} else {
 665				Loader::element('error_fatal', array('innerContent' => $innerContent, 
 666					'titleContent' => $titleContent));
 667			}
 668		}
 669		
 670		/**
 671		 * sets the current theme
 672		 * @access public
 673		 * @param string $theme
 674		 * @return void
 675		*/	
 676		public function setTheme($theme) {
 677			$this->themeOverride = $theme;
 678		}
 679		
 680		/**
 681		 * set theme takes either a text-based theme ("concrete" or "dashboard" or something)
 682		 * or a PageTheme object and sets information in the view about that theme. This is called internally
 683		 * and is always passed the correct item based on context
 684		 * 
 685		 * @access protected
 686		 * @param PageTheme object $pl
 687		 * @param string $filename
 688		 * @param boolean $wrapTemplateInTheme
 689		 * @return void
 690		*/	
 691		protected function setThemeForView($pl, $filename, $wrapTemplateInTheme = false) {
 692			// wrapTemplateInTheme gets set to true if we're passing the filename of a single page or page type file through 
 693			$pkgID = 0;
 694			$env = Environment::get();
 695			if ($pl instanceof PageTheme) {
 696				$this->ptHandle = $pl->getThemeHandle();
 697				if ($pl->getPackageID() > 0) {
 698					$pkgID = $pl->getPackageID();
 699					$this->pkgHandle = $pl->getPackageHandle();
 700				}
 701			
 702				$rec = $env->getRecord(DIRNAME_THEMES . '/' . $pl->getThemeHandle() . '/' . $filename, $this->pkgHandle);
 703				if (!$rec->exists()) {
 704					if ($wrapTemplateInTheme) {
 705						$theme = $env->getPath(DIRNAME_THEMES . '/' . $pl->getThemeHandle() . '/' . FILENAME_THEMES_VIEW, $this->pkgHandle);
 706					} else {
 707						$theme = $env->getPath(DIRNAME_THEMES . '/' . $pl->getThemeHandle() . '/' . FILENAME_THEMES_DEFAULT, $this->pkgHandle);
 708					}
 709				} else {
 710					$theme = $rec->file;
 711					$this->disableContentInclude = true;
 712				}
 713				
 714				$themeDir = str_replace('/' . FILENAME_THEMES_DEFAULT, '', $env->getPath(DIRNAME_THEMES . '/' . $pl->getThemeHandle() . '/' . FILENAME_THEMES_DEFAULT, $this->pkgHandle));
 715				$themePath = str_replace('/' . FILENAME_THEMES_DEFAULT, '', $env->getURL(DIRNAME_THEMES . '/' . $pl->getThemeHandle() . '/' . FILENAME_THEMES_DEFAULT, $this->pkgHandle));
 716			} else {
 717				$this->ptHandle = $pl;
 718				if (file_exists(DIR_FILES_THEMES . '/' . $pl . '/' . $filename)) {
 719					$themePath = DIR_REL . '/' . DIRNAME_THEMES . '/' . $pl;
 720					$theme = DIR_FILES_THEMES . "/" . $pl . '/' . $filename;
 721					$themeDir = DIR_FILES_THEMES . "/" . $pl;
 722				} else if (file_exists(DIR_FILES_THEMES . '/' . $pl . '/' . FILENAME_THEMES_VIEW)) {
 723					$themePath = DIR_REL . '/' . DIRNAME_THEMES . '/' . $pl;
 724					$theme = DIR_FILES_THEMES . "/" . $pl . '/' . FILENAME_THEMES_VIEW;
 725					$themeDir = DIR_FILES_THEMES . "/" . $pl;
 726				} else if (file_exists(DIR_FILES_THEMES . '/' . DIRNAME_THEMES_CORE . '/' . $pl . '.php')) {
 727					$theme = DIR_FILES_THEMES . '/' . DIRNAME_THEMES_CORE . "/" . $pl . '.php';
 728					$themeDir = DIR_FILES_THEMES . '/' . DIRNAME_THEMES_CORE;
 729				} else if (file_exists(DIR_FILES_THEMES_CORE . "/" . $pl . '/' . $filename)) {
 730					$themePath = ASSETS_URL . '/' . DIRNAME_THEMES . '/' . DIRNAME_THEMES_CORE . '/' . $pl;
 731					$theme = DIR_FILES_THEMES_CORE . "/" . $pl . '/' . $filename;
 732					$themeDir = DIR_FILES_THEMES_CORE . "/" . $pl;
 733				} else if (file_exists(DIR_FILES_THEMES_CORE . "/" . $pl . '/' . FILENAME_THEMES_VIEW)) {
 734					$themePath = ASSETS_URL . '/' . DIRNAME_THEMES . '/' . DIRNAME_THEMES_CORE . '/' . $pl;
 735					$theme = DIR_FILES_THEMES_CORE . "/" . $pl . '/' . FILENAME_THEMES_VIEW;
 736					$themeDir = DIR_FILES_THEMES_CORE . "/" . $pl;
 737				} else if (file_exists(DIR_FILES_THEMES_CORE_ADMIN . "/" . $pl . '.php')) {
 738					$theme = DIR_FILES_THEMES_CORE_ADMIN . "/" . $pl . '.php';
 739					$themeDir = DIR_FILES_THEMES_CORE_ADMIN;
 740				}
 741			}
 742			
 743			$this->theme = $theme;
 744			$this->themePath = $themePath;
 745			$this->themeDir = $themeDir;
 746			$this->themePkgID = $pkgID;
 747		}
 748		public function escape($text){
 749			Loader::helper('text');
 750			return TextHelper::sanitize($text);
 751		}
 752		/**
 753		 * render takes one argument - the item being rendered - and it can either be a path or a page object
 754		 * @access public
 755		 * @param string $view
 756		 * @param array $args
 757		 * @return void
 758		*/	
 759		public function render($view, $args = null) { 
 760		
 761			if (is_array($args)) {
 762				extract($args);
 763			}
 764
 765			// strip off a slash if there is one at the end
 766			if (is_string($view)) {
 767				if (substr($view, strlen($view) - 1) == '/') {
 768					$view = substr($view, 0, strlen($view) - 1);
 769				}
 770			}
 771			
 772			$dsh = Loader::helper('concrete/dashboard');
 773
 774			$wrapTemplateInTheme = false;
 775			$this->checkMobileView();
 776			if (defined('DB_DATABASE') && ($view !== '/upgrade')) {
 777				Events::fire('on_start', $this);
 778			}
 779			
 780			// Extract controller information from the view, and put it in the current context
 781			if (!isset($this->controller)) {
 782				$this->controller = Loader::controller($view);
 783				$this->controller->setupAndRun();
 784			}
 785
 786			if ($this->controller->getRenderOverride() != '') {
 787			   $view = $this->controller->getRenderOverride();
 788			}
 789			
 790			// Determine which inner item to load, load it, and stick it in $innerContent
 791			$content = false;
 792
 793			ob_start();			
 794			if ($view instanceof Page) {
 795
 796				$_pageBlocks = $view->getBlocks();
 797
 798				if (!$dsh->inDashboard()) {
 799					$_pageBlocksGlobal = $view->getGlobalBlocks();
 800					$_pageBlocks = array_merge($_pageBlocks, $_pageBlocksGlobal);
 801				}
 802
 803				// do we have any custom menu plugins?
 804				$cp = new Permissions($view);
 805				if ($cp->canViewToolbar()) { 
 806					$ih = Loader::helper('concrete/interface/menu');
 807					$_interfaceItems = $ih->getPageHeaderMenuItems();
 808					foreach($_interfaceItems as $_im) {
 809						$_controller = $_im->getController();
 810						$_controller->outputAutoHeaderItems();
 811					}
 812					unset($_interfaceItems);
 813					unset($_im);
 814					unset($_controller);
 815				}
 816				unset($_interfaceItems);
 817				unset($_im);
 818				unset($_controller);
 819				
 820				
 821				// now, we output all the custom style records for the design tab in blocks/areas on the page
 822				$c = $this->getCollectionObject();
 823				$view->outputCustomStyleHeaderItems(); 	
 824				
 825				$viewPath = $view->getCollectionPath();
 826				$this->viewPath = $viewPath;
 827				
 828				$cFilename = $view->getCollectionFilename();
 829				$ctHandle = $view->getCollectionTypeHandle();
 830				$editMode = $view->isEditMode();
 831				$c = $view;
 832				$this->c = $c;
 833				
 834				$env = Environment::get();
 835				// $view is a page. It can either be a SinglePage or just a Page, but we're not sure at this point, unfortunately
 836				if ($view->getCollectionTypeID() == 0 && $cFilename) {
 837					$wrapTemplateInTheme = true;
 838					$cFilename = trim($cFilename, '/');
 839					$content = $env->getPath(DIRNAME_PAGES . '/' . $cFilename, $view->getPackageHandle());
 840					$themeFilename = $c->getCollectionHandle() . '.php';						
 841				} else {
 842					$rec = $env->getRecord(DIRNAME_PAGE_TYPES . '/' . $ctHandle . '.php', $view->getPackageHandle());
 843					if ($rec->exists()) {
 844						$wrapTemplateInTheme = true;
 845						$content = $rec->file;
 846					}
 847					$themeFilename = $ctHandle . '.php';
 848				}
 849				
 850				
 851			} else if (is_string($view)) {
 852				
 853				// if we're passing a view but our render override is not null, that means that we're passing 
 854				// a new view from within a controller. If that's the case, then we DON'T override the viewPath, we want to keep it
 855				
 856				// In order to enable editable 404 pages, other editable pages that we render without actually visiting
 857				if (defined('DB_DATABASE') && $view == '/page_not_found') {
 858					$pp = Page::getByPath($view);
 859					if (!$pp->isError()) {
 860						$this->c = $pp;
 861					}
 862				}
 863				
 864				$viewPath = $view;
 865				if ($this->controller->getRenderOverride() != '' && $this->getCollectionObject() != null) {
 866					// we are INSIDE a collection renderring a view. Which means we want to keep the viewPath that of the collection
 867					$this->viewPath = $this->getCollectionObject()->getCollectionPath();
 868				}
 869				
 870				// we're just passing something like "/login" or whatever. This will typically just be 
 871				// internal Concrete stuff, but we also prepare for potentially having something in DIR_FILES_CONTENT (ie: the webroot)
 872				if (file_exists(DIR_FILES_CONTENT . "/{$view}/" . FILENAME_COLLECTION_VIEW)) {
 873					$content = DIR_FILES_CONTENT . "/{$view}/" . FILENAME_COLLECTION_VIEW;
 874				} else if (file_exists(DIR_FILES_CONTENT . "/{$view}.php")) {
 875					$content = DIR_FILES_CONTENT . "/{$view}.php";
 876				} else if (file_exists(DIR_FILES_CONTENT_REQUIRED . "/{$view}/" . FILENAME_COLLECTION_VIEW)) {
 877					$content = DIR_FILES_CONTENT_REQUIRED . "/{$view}/" . FILENAME_COLLECTION_VIEW;
 878				} else if (file_exists(DIR_FILES_CONTENT_REQUIRED . "/{$view}.php")) {
 879					$content = DIR_FILES_CONTENT_REQUIRED . "/{$view}.php";
 880				} else if ($this->getCollectionObject() != null && $this->getCollectionObject()->isGeneratedCollection() && $this->getCollectionObject()->getPackageID() > 0) {
 881					//This is a single_page associated with a package, so check the package views as well
 882					$pagePkgPath = Package::getByID($this->getCollectionObject()->getPackageID())->getPackagePath();
 883					if (file_exists($pagePkgPath . "/single_pages/{$view}/" . FILENAME_COLLECTION_VIEW)) {
 884						$content = $pagePkgPath . "/single_pages/{$view}/" . FILENAME_COLLECTION_VIEW;
 885					} else if (file_exists($pagePkgPath . "/single_pages/{$view}.php")) {
 886						$content = $pagePkgPath . "/single_pages/{$view}.php";
 887					}
 888				}
 889				$wrapTemplateInTheme = true;
 890				$themeFilename = $view . '.php';
 891			}
 892			
 893			
 894			if (is_object($this->c)) {
 895				$c = $this->c;
 896				if (defined('DB_DATABASE') && ($view == '/page_not_found' || $view == '/login')) {
 897					$view = $c;
 898					$req = Request::get();
 899					$req->setCurrentPage($c);
 900					$_pageBlocks = $view->getBlocks();
 901					$_pageBlocksGlobal = $view->getGlobalBlocks();
 902					$_pageBlocks = array_merge($_pageBlocks, $_pageBlocksGlobal);
 903				}
 904			}
 905			
 906			if (is_array($_pageBlocks)) {
 907				foreach($_pageBlocks as $b1) {
 908					$b1p = new Permissions($b1);
 909					if ($b1p->canRead()) { 
 910						$btc = $b1->getInstance();
 911						// now we inject any custom template CSS and JavaScript into the header
 912						if('Controller' != get_class($btc)){
 913							$btc->outputAutoHeaderItems();
 914						}
 915						$btc->runTask('on_page_view', array($view));
 916					}
 917				}
 918			}			
 919			
 920			// Determine which outer item/theme to load
 921			// obtain theme information for this collection
 922			if (isset($this->themeOverride)) {
 923				$theme = $this->themeOverride;
 924			} else if ($this->controller->theme != false) {
 925				$theme = $this->controller->theme;
 926			} else if (($tmpTheme = $this->getThemeFromPath($viewPath)) != false) {
 927				$theme = $tmpTheme;
 928			} else if (is_object($this->c) && ($tmpTheme = $this->c->getCollectionThemeObject()) != false) {
 929				$theme = $tmpTheme;
 930			} else {
 931				$theme = FILENAME_COLLECTION_DEFAULT_THEME;
 932			}		
 933			
 934			$this->setThemeForView($theme, $themeFilename, $wrapTemplateInTheme);
 935
 936
 937			// finally, we include the theme (which was set by setTheme and will automatically include innerContent)
 938			// disconnect from our db and exit
 939
 940			$this->controller->on_before_render();
 941			extract($this->controller->getSets());
 942			extract($this->controller->getHelperObjects());
 943
 944			if ($content != false && (!$this->disableContentInclude)) {
 945				include($content);
 946			}
 947
 948			$innerContent = ob_get_contents();
 949			
 950			if (ob_get_level() > OB_INITIAL_LEVEL) {
 951				ob_end_clean();
 952			}
 953			
 954			Events::fire('on_before_render', $this);
 955			
 956			if (defined('APP_CHARSET')) {
 957				header("Content-Type: text/html; charset=" . APP_CHARSET);
 958			}
 959			
 960			if (file_exists($this->theme)) {
 961				
 962				$cache = PageCache::getLibrary();
 963				$shouldAddToCache = $cache->shouldAddToCache($this);
 964				if ($shouldAddToCache) {
 965					$cache->outputCacheHeaders($c);
 966				}
 967
 968				ob_start();
 969				include($this->theme);
 970				$pageContent = ob_get_contents();
 971				ob_end_clean();
 972				
 973				$ret = Events::fire('on_page_output', $pageContent);
 974				if($ret != '') {
 975					print $ret;
 976					$pageContent = $ret;
 977				} else {
 978					print $pageContent;
 979				}
 980
 981				$cache = PageCache::getLibrary();
 982				if ($shouldAddToCache) {
 983					$cache->set($c, $pageContent);
 984				}
 985
 986			
 987			} else {
 988				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));
 989			}
 990			
 991			Events::fire('on_render_complete', $this);
 992			
 993			if (ob_get_level() == OB_INITIAL_LEVEL) {
 994				require(DIR_BASE_CORE . '/startup/jobs.php');
 995				require(DIR_BASE_CORE . '/startup/shutdown.php');
 996				exit;
 997			}
 998			
 999		}
1000	}