PageRenderTime 66ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 1ms

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

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