PageRenderTime 21ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/concrete/src/View/View.php

http://github.com/concrete5/concrete5
PHP | 478 lines | 361 code | 65 blank | 52 comment | 42 complexity | 5440160d8b835c9fad25a9b1165a7f2c MD5 | raw file
Possible License(s): MIT, LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause
  1. <?php
  2. namespace Concrete\Core\View;
  3. use Concrete\Core\Asset\Asset;
  4. use Concrete\Core\Asset\Output\StandardFormatter;
  5. use Concrete\Core\Filesystem\FileLocator;
  6. use Concrete\Core\Http\ResponseAssetGroup;
  7. use Concrete\Core\Page\Theme\ThemeRouteCollection;
  8. use Environment;
  9. use Events;
  10. use Concrete\Core\Support\Facade\Facade;
  11. use PageTheme;
  12. use Page;
  13. use Config;
  14. use Illuminate\Filesystem\Filesystem;
  15. class View extends AbstractView
  16. {
  17. protected $viewPath;
  18. protected $innerContentFile;
  19. protected $themeHandle;
  20. protected $themeObject;
  21. protected $themeRelativePath;
  22. protected $themeAbsolutePath;
  23. protected $viewPkgHandle;
  24. protected $themePkgHandle;
  25. protected $viewRootDirectoryName = DIRNAME_VIEWS;
  26. protected function constructView($path = false)
  27. {
  28. $path = '/'.trim($path, '/');
  29. $this->viewPath = $path;
  30. }
  31. public function setPackageHandle($pkgHandle)
  32. {
  33. $this->viewPkgHandle = $pkgHandle;
  34. }
  35. public function getThemeDirectory()
  36. {
  37. return $this->themeAbsolutePath;
  38. }
  39. public function getViewPath()
  40. {
  41. return $this->viewPath;
  42. }
  43. /**
  44. * gets the relative theme path for use in templates.
  45. *
  46. *
  47. * @return string $themePath
  48. */
  49. public function getThemePath()
  50. {
  51. return $this->themeRelativePath;
  52. }
  53. public function getThemeHandle()
  54. {
  55. return $this->themeHandle;
  56. }
  57. public function setInnerContentFile($innerContentFile)
  58. {
  59. $this->innerContentFile = $innerContentFile;
  60. }
  61. /**
  62. * @return mixed
  63. */
  64. public function getInnerContentFile()
  65. {
  66. return $this->innerContentFile;
  67. }
  68. public function setViewRootDirectoryName($directory)
  69. {
  70. $this->viewRootDirectoryName = $directory;
  71. }
  72. public function inc($file, $args = [])
  73. {
  74. $__data__ = [
  75. 'scopedItems' => $this->getScopeItems(),
  76. ];
  77. if ($args && is_array($args)) {
  78. $__data__['scopedItems'] += $args;
  79. }
  80. $env = Environment::get();
  81. $path = $env->getPath(DIRNAME_THEMES.'/'.$this->themeHandle.'/'.$file, $this->themePkgHandle);
  82. if (!file_exists($path)) {
  83. $path2 = $env->getPath(DIRNAME_THEMES.'/'.$this->themeHandle.'/'.$file, $this->viewPkgHandle);
  84. if (file_exists($path2)) {
  85. $path = $path2;
  86. }
  87. unset($path2);
  88. }
  89. $__data__['path'] = $path;
  90. unset($file);
  91. unset($args);
  92. unset($env);
  93. unset($path);
  94. if (!empty($__data__['scopedItems'])) {
  95. if (array_key_exists('__data__', $__data__['scopedItems'])) {
  96. throw new \Exception(t(/*i18n: %1$s is a variable name, %2$s is a function name*/'Illegal variable name \'%1$s\' in %2$s args.', '__data__', __CLASS__.'::'.__METHOD__));
  97. }
  98. extract($__data__['scopedItems']);
  99. }
  100. include $__data__['path'];
  101. }
  102. /**
  103. * A shortcut to posting back to the current page with a task and optional parameters. Only works in the context of.
  104. *
  105. * @param string $action
  106. * @param string $task
  107. *
  108. * @return string $url
  109. */
  110. public function action($action)
  111. {
  112. $a = func_get_args();
  113. $controllerPath = $this->controller->getControllerActionPath();
  114. array_unshift($a, $controllerPath);
  115. $ret = call_user_func_array([$this, 'url'], $a);
  116. return $ret;
  117. }
  118. public function setViewTheme($theme)
  119. {
  120. if (is_object($theme)) {
  121. $this->themeHandle = $theme->getThemeHandle();
  122. } else {
  123. $this->themeHandle = $theme;
  124. }
  125. if (isset($this->themeObject)) {
  126. $this->themeObject = null;
  127. $this->loadViewThemeObject();
  128. }
  129. }
  130. public function getViewTemplateFile()
  131. {
  132. $app = Facade::getFacadeApplication();
  133. $collection = $app->make(ThemeRouteCollection::class);
  134. $tmpTheme = $collection->getThemeByRoute($this->getViewPath());
  135. if ($tmpTheme) {
  136. return $tmpTheme[1];
  137. }
  138. if (isset($this->template)) {
  139. return $this->template;
  140. }
  141. return FILENAME_THEMES_VIEW;
  142. }
  143. /**
  144. * Load all the theme-related variables for which theme to use for this request. May update the themeHandle
  145. * property on the view based on themeByRoute settings.
  146. */
  147. protected function loadViewThemeObject()
  148. {
  149. $env = Environment::get();
  150. $app = Facade::getFacadeApplication();
  151. $tmpTheme = $app->make(ThemeRouteCollection::class)
  152. ->getThemeByRoute($this->getViewPath());
  153. if (isset($tmpTheme[0])) {
  154. $this->themeHandle = $tmpTheme[0];
  155. }
  156. if ($this->themeHandle) {
  157. switch ($this->themeHandle) {
  158. case VIEW_CORE_THEME:
  159. $this->themeObject = new \Concrete\Theme\Concrete\PageTheme();
  160. $this->pkgHandle = false;
  161. break;
  162. case 'dashboard':
  163. $this->themeObject = new \Concrete\Theme\Dashboard\PageTheme();
  164. $this->pkgHandle = false;
  165. break;
  166. default:
  167. if (!isset($this->themeObject)) {
  168. $this->themeObject = PageTheme::getByHandle($this->themeHandle);
  169. $this->themePkgHandle = $this->themeObject->getPackageHandle();
  170. }
  171. }
  172. $this->themeAbsolutePath = $env->getPath(DIRNAME_THEMES.'/'.$this->themeHandle, $this->themePkgHandle);
  173. $this->themeRelativePath = $env->getURL(DIRNAME_THEMES.'/'.$this->themeHandle, $this->themePkgHandle);
  174. }
  175. }
  176. /**
  177. * Begin the render.
  178. */
  179. public function start($state)
  180. {
  181. }
  182. public function setupRender()
  183. {
  184. // Set the theme object that we should use for this requested page.
  185. // Only run setup if the theme is unset. Usually it will be but if we set it
  186. // programmatically we already have a theme.
  187. $this->loadViewThemeObject();
  188. $env = Environment::get();
  189. if (!$this->innerContentFile) { // will already be set in a legacy tools file
  190. $this->setInnerContentFile($env->getPath($this->viewRootDirectoryName.'/'.trim($this->viewPath, '/').'.php', $this->viewPkgHandle));
  191. }
  192. if ($this->themeHandle) {
  193. if (is_object($this->controller)) {
  194. $templateFile = $this->controller->getThemeViewTemplate();
  195. } else {
  196. $templateFile = $this->getViewTemplateFile();
  197. }
  198. $this->setViewTemplate($env->getPath(DIRNAME_THEMES.'/'.$this->themeHandle.'/'.$templateFile, $this->themePkgHandle));
  199. }
  200. }
  201. public function startRender()
  202. {
  203. $event = new \Symfony\Component\EventDispatcher\GenericEvent();
  204. $event->setArgument('view', $this);
  205. Events::dispatch('on_start', $event);
  206. parent::startRender();
  207. }
  208. protected function onBeforeGetContents()
  209. {
  210. $event = new \Symfony\Component\EventDispatcher\GenericEvent();
  211. $event->setArgument('view', $this);
  212. Events::dispatch('on_before_render', $event);
  213. $this->themeObject->registerAssets();
  214. }
  215. public function renderViewContents($scopeItems)
  216. {
  217. $contents = '';
  218. // Render the main view file
  219. if ($this->innerContentFile) {
  220. $contents = $this->renderInnerContents($scopeItems);
  221. }
  222. // Render the template around it
  223. if (file_exists($this->template)) {
  224. $contents = $this->renderTemplate($scopeItems, $contents);
  225. }
  226. return $contents;
  227. }
  228. /**
  229. * Render the file set to $this->innerContentFile
  230. * @param $scopeItems
  231. * @return string
  232. */
  233. protected function renderInnerContents($scopeItems)
  234. {
  235. // Extract the items into the current scope
  236. extract($scopeItems);
  237. ob_start();
  238. include $this->innerContentFile;
  239. $innerContent = ob_get_contents();
  240. ob_end_clean();
  241. return $innerContent;
  242. }
  243. /**
  244. * Render the file set to $this->template
  245. * @param $scopeItems
  246. * @return string
  247. */
  248. protected function renderTemplate($scopeItems, $innerContent)
  249. {
  250. // Extract the items into the current scope
  251. extract($scopeItems);
  252. ob_start();
  253. // Fire a `before` event
  254. $this->onBeforeGetContents();
  255. include $this->template;
  256. // Fire an `after` event
  257. $this->onAfterGetContents();
  258. $contents = ob_get_contents();
  259. ob_end_clean();
  260. return $contents;
  261. }
  262. public function finishRender($contents)
  263. {
  264. $event = new \Symfony\Component\EventDispatcher\GenericEvent();
  265. $event->setArgument('view', $this);
  266. Events::dispatch('on_render_complete', $event);
  267. return $contents;
  268. }
  269. /**
  270. * Function responsible for outputting header items.
  271. */
  272. public function markHeaderAssetPosition()
  273. {
  274. echo '<!--ccm:assets:'.Asset::ASSET_POSITION_HEADER.'//-->';
  275. }
  276. /**
  277. * Function responsible for outputting footer items.
  278. */
  279. public function markFooterAssetPosition()
  280. {
  281. echo '<!--ccm:assets:'.Asset::ASSET_POSITION_FOOTER.'//-->';
  282. }
  283. protected function getAssetsToOutput()
  284. {
  285. $responseGroup = ResponseAssetGroup::get();
  286. $assets = $responseGroup->getAssetsToOutput();
  287. return $assets;
  288. }
  289. public function postProcessViewContents($contents)
  290. {
  291. $assets = $this->getAssetsToOutput();
  292. $contents = $this->replaceAssetPlaceholders($assets, $contents);
  293. // replace any empty placeholders
  294. $contents = $this->replaceEmptyAssetPlaceholders($contents);
  295. return $contents;
  296. }
  297. protected function postProcessAssets($assets)
  298. {
  299. $c = Page::getCurrentPage();
  300. if (!Config::get('concrete.cache.assets')) {
  301. return $assets;
  302. }
  303. if (!count($assets)) {
  304. return [];
  305. }
  306. // goes through all assets in this list, creating new URLs and post-processing them where possible.
  307. $segment = 0;
  308. $groupedAssets = [];
  309. for ($i = 0; $i < count($assets); ++$i) {
  310. $asset = $assets[$i];
  311. $nextasset = isset($assets[$i + 1]) ? $assets[$i + 1] : null;
  312. $groupedAssets[$segment][] = $asset;
  313. if (!($asset instanceof Asset) || !($nextasset instanceof Asset)) {
  314. ++$segment;
  315. continue;
  316. }
  317. if ($asset->getOutputAssetType() != $nextasset->getOutputAssetType()) {
  318. ++$segment;
  319. continue;
  320. }
  321. if (!$asset->assetSupportsCombination() || !$nextasset->assetSupportsCombination()) {
  322. ++$segment;
  323. continue;
  324. }
  325. }
  326. $return = [];
  327. // now we have a sub assets array with different segments split by whether they can be combined.
  328. foreach ($groupedAssets as $assets) {
  329. if (
  330. ($assets[0] instanceof Asset)
  331. &&
  332. (
  333. (count($assets) > 1)
  334. ||
  335. $assets[0]->assetSupportsMinification()
  336. )
  337. ) {
  338. $class = get_class($assets[0]);
  339. $assets = call_user_func([$class, 'process'], $assets);
  340. }
  341. $return = array_merge($return, $assets);
  342. }
  343. return $return;
  344. }
  345. protected function replaceEmptyAssetPlaceholders($pageContent)
  346. {
  347. foreach (['<!--ccm:assets:'.Asset::ASSET_POSITION_HEADER.'//-->', '<!--ccm:assets:'.Asset::ASSET_POSITION_FOOTER.'//-->'] as $comment) {
  348. $pageContent = str_replace($comment, '', $pageContent);
  349. }
  350. return $pageContent;
  351. }
  352. protected function replaceAssetPlaceholders($outputAssets, $pageContent)
  353. {
  354. $outputItems = [];
  355. foreach ($outputAssets as $position => $assets) {
  356. $output = '';
  357. $transformed = $this->postProcessAssets($assets);
  358. foreach ($transformed as $item) {
  359. $itemstring = (string) $item;
  360. if (!in_array($itemstring, $outputItems)) {
  361. $output .= $this->outputAssetIntoView($item);
  362. $outputItems[] = $itemstring;
  363. }
  364. }
  365. $pageContent = str_replace('<!--ccm:assets:'.$position.'//-->', $output, $pageContent);
  366. }
  367. return $pageContent;
  368. }
  369. protected function outputAssetIntoView($item)
  370. {
  371. $formatter = new StandardFormatter();
  372. if ($item instanceof Asset) {
  373. return $formatter->output($item) . "\n";
  374. } else {
  375. return $item . "\n";
  376. }
  377. }
  378. public static function element($_file, $args = null, $_pkgHandle = null)
  379. {
  380. if (is_array($args)) {
  381. $collisions = array_intersect(['_file', '_pkgHandle'], array_keys($args));
  382. if ($collisions) {
  383. throw new \Exception(t("Illegal variable name '%s' in element args.", implode(', ', $collisions)));
  384. }
  385. $collisions = null;
  386. extract($args);
  387. }
  388. $view = self::getRequestInstance();
  389. $_c = Page::getCurrentPage();
  390. $_app = Facade::getFacadeApplication();
  391. if (is_object($_c)) {
  392. $_theme = $_c->getCollectionThemeObject();
  393. } else if ($_app->isInstalled()) {
  394. $_theme = PageTheme::getSiteTheme();
  395. }
  396. $_fs = $_app->make(Filesystem::class);
  397. $_locator = new FileLocator($_fs, $_app);
  398. if (isset($_theme) && is_object($_theme)) {
  399. $_locator->addLocation(new FileLocator\ThemeElementLocation($_theme));
  400. }
  401. if ($_pkgHandle) {
  402. $_locator->addPackageLocation($_pkgHandle);
  403. }
  404. $_record = $_locator->getRecord(DIRNAME_ELEMENTS . '/' . $_file . '.php');
  405. $_file = $_record->getFile();
  406. unset($_record);
  407. unset($_app);
  408. unset($_fs);
  409. unset($_locator);
  410. unset($_theme);
  411. include $_file;
  412. }
  413. }