winter/modules/cms/classes/Controller.php

1314 lines
38 KiB
PHP
Raw Normal View History

2014-05-14 23:24:20 +10:00
<?php namespace Cms\Classes;
use URL;
use Str;
2014-05-14 23:24:20 +10:00
use App;
use File;
use View;
use Lang;
use Route;
2014-05-14 23:24:20 +10:00
use Event;
use Config;
2015-01-05 15:02:22 +11:00
use Session;
2014-05-14 23:24:20 +10:00
use Request;
use Response;
use Exception;
use BackendAuth;
2014-05-14 23:24:20 +10:00
use Twig_Environment;
use Cms\Twig\Loader as TwigLoader;
use Cms\Twig\DebugExtension;
use Cms\Twig\Extension as CmsTwigExtension;
2014-05-14 23:24:20 +10:00
use Cms\Classes\FileHelper as CmsFileHelper;
use Cms\Models\MaintenanceSettings;
2014-07-30 17:33:26 +10:00
use System\Models\RequestLog;
2014-05-14 23:24:20 +10:00
use System\Classes\ErrorHandler;
use System\Classes\CombineAssets;
2014-07-30 17:33:26 +10:00
use System\Twig\Extension as SystemTwigExtension;
2015-03-03 18:41:09 +11:00
use October\Rain\Exception\AjaxException;
use October\Rain\Exception\SystemException;
use October\Rain\Exception\ValidationException;
use October\Rain\Exception\ApplicationException;
2014-05-14 23:24:20 +10:00
use Illuminate\Http\RedirectResponse;
/**
* The CMS controller class.
* The controller finds and serves requested pages.
*
* @package october\cms
* @author Alexey Bobkov, Samuel Georges
*/
class Controller
2014-05-14 23:24:20 +10:00
{
use \System\Traits\AssetMaker;
use \October\Rain\Support\Traits\Emitter;
2014-05-14 23:24:20 +10:00
/**
* @var \Cms\Classes\Theme A reference to the CMS theme processed by the controller.
*/
protected $theme;
/**
* @var \Cms\Classes\Router A reference to the Router object.
*/
protected $router;
/**
* @var \Cms\Twig\Loader A reference to the Twig template loader.
*/
protected $loader;
/**
* @var \Cms\Classes\Page A reference to the CMS page template being processed.
2014-05-14 23:24:20 +10:00
*/
protected $page;
/**
* @var \Cms\Classes\CodeBase A reference to the CMS page code section object.
*/
protected $pageObj;
/**
* @var \Cms\Classes\Layout A reference to the CMS layout template used by the page.
2014-05-14 23:24:20 +10:00
*/
protected $layout;
/**
* @var \Cms\Classes\CodeBase A reference to the CMS layout code section object.
*/
protected $layoutObj;
2014-05-14 23:24:20 +10:00
/**
* @var \Twig_Environment Keeps the Twig environment object.
*/
protected $twig;
/**
* @var string Contains the rendered page contents string.
*/
protected $pageContents;
/**
* @var array A list of variables to pass to the page.
*/
public $vars = [];
/**
* @var int Response status code
*/
protected $statusCode = 200;
/**
* @var self Cache of self
*/
protected static $instance = null;
/**
* @var Cms\Classes\ComponentBase Object of the active component, used internally.
*/
protected $componentContext;
/**
* @var array Component partial stack, used internally.
*/
protected $partialComponentStack = [];
2014-05-14 23:24:20 +10:00
/**
* Creates the controller.
* @param \Cms\Classes\Theme $theme Specifies the CMS theme.
* If the theme is not specified, the current active theme used.
*/
public function __construct($theme = null)
{
$this->theme = $theme ? $theme : Theme::getActiveTheme();
2014-10-11 01:22:03 +02:00
if (!$this->theme) {
throw new CmsException(Lang::get('cms::lang.theme.active.not_found'));
2014-10-11 01:22:03 +02:00
}
$this->assetPath = Config::get('cms.themesPath', '/themes').'/'.$this->theme->getDirName();
$this->router = new Router($this->theme);
$this->initTwigEnvironment();
self::$instance = $this;
}
2014-05-14 23:24:20 +10:00
/**
* Finds and serves the requested page.
* If the page cannot be found, returns the page with the URL /404.
* If the /404 page doesn't exist, returns the system 404 page.
* @param string $url Specifies the requested page URL.
* If the parameter is omitted, the current URL used.
* @return string Returns the processed page content.
*/
public function run($url = '/')
2014-05-14 23:24:20 +10:00
{
2014-10-11 01:22:03 +02:00
if ($url === null) {
2014-05-14 23:24:20 +10:00
$url = Request::path();
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
2014-10-11 01:22:03 +02:00
if (!strlen($url)) {
2014-05-14 23:24:20 +10:00
$url = '/';
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
/*
* Hidden page
*/
2014-05-14 23:24:20 +10:00
$page = $this->router->findByUrl($url);
if ($page && $page->hidden) {
2014-10-11 01:22:03 +02:00
if (!BackendAuth::getUser()) {
$page = null;
2014-10-11 01:22:03 +02:00
}
}
2014-05-14 23:24:20 +10:00
/*
* Maintenance mode
*/
if (
MaintenanceSettings::isConfigured() &&
MaintenanceSettings::get('is_enabled', false) &&
!BackendAuth::getUser()
) {
$page = Page::loadCached($this->theme, MaintenanceSettings::get('cms_page'));
}
2014-05-14 23:24:20 +10:00
/*
* Extensibility
*/
if (
($event = $this->fireEvent('page.beforeDisplay', [$url, $page], true)) ||
($event = Event::fire('cms.page.beforeDisplay', [$this, $url, $page], true))
) {
2014-11-26 17:31:07 +11:00
if ($event instanceof Page) {
$page = $event;
}
else {
return $event;
}
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
/*
* If the page was not found, render the 404 page - either provided by the theme or the built-in one.
*/
2014-06-17 20:11:31 +10:00
if (!$page) {
$this->setStatusCode(404);
2014-07-30 17:33:26 +10:00
// Log the 404 request
2014-10-11 01:22:03 +02:00
if (!App::runningUnitTests()) {
RequestLog::add();
2014-10-11 01:22:03 +02:00
}
2014-07-30 17:33:26 +10:00
2014-10-11 01:22:03 +02:00
if (!$page = $this->router->findByUrl('/404')) {
2014-06-17 20:11:31 +10:00
return Response::make(View::make('cms::404'), $this->statusCode);
2014-10-11 01:22:03 +02:00
}
2014-06-17 20:11:31 +10:00
}
2014-05-14 23:24:20 +10:00
/*
* Run the page
*/
$result = $this->runPage($page);
/*
* Extensibility
*/
if (
($event = $this->fireEvent('page.display', [$url, $page, $result], true)) ||
($event = Event::fire('cms.page.display', [$this, $url, $page, $result], true))
) {
return $event;
}
if (!is_string($result)) {
return $result;
}
return Response::make($result, $this->statusCode);
}
/**
* Renders a page in its entirety, including component initialization.
* AJAX will be disabled for this process.
* @param string $pageFile Specifies the CMS page file name to run.
* @param array $parameters Routing parameters.
* @param \Cms\Classes\Theme $theme Theme object
*/
public static function render($pageFile, $parameters = [], $theme = null)
{
if (!$theme && (!$theme = Theme::getActiveTheme())) {
throw new CmsException(Lang::get('cms::lang.theme.active.not_found'));
}
$controller = new static($theme);
$controller->getRouter()->setParameters($parameters);
if (($page = Page::load($theme, $pageFile)) === null) {
throw new CmsException(Lang::get('cms::lang.page.not_found_name', ['name'=>$pageFile]));
}
return $controller->runPage($page, false);
}
/**
* Runs a page directly from its object and supplied parameters.
* @param \Cms\Classes\Page $page Specifies the CMS page to run.
* @return string
*/
public function runPage($page, $useAjax = true)
{
2014-05-14 23:24:20 +10:00
$this->page = $page;
/*
* If the page doesn't refer any layout, create the fallback layout.
* Otherwise load the layout specified in the page.
*/
2014-10-11 01:22:03 +02:00
if (!$page->layout) {
2014-05-14 23:24:20 +10:00
$layout = Layout::initFallback($this->theme);
}
elseif (($layout = Layout::loadCached($this->theme, $page->layout)) === null) {
throw new CmsException(Lang::get('cms::lang.layout.not_found_name', ['name'=>$page->layout]));
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
$this->layout = $layout;
/*
* The 'this' variable is reserved for default variables.
*/
$this->vars['this'] = [
2014-05-27 13:18:21 +10:00
'page' => $this->page,
'layout' => $this->layout,
'theme' => $this->theme,
'param' => $this->router->getParameters(),
'controller' => $this,
2014-05-27 13:18:21 +10:00
'environment' => App::environment(),
2015-01-05 21:08:30 +11:00
'session' => App::make('session'),
2014-05-14 23:24:20 +10:00
];
/*
* Check for the presence of validation errors in the session.
*/
2015-01-05 15:02:22 +11:00
$this->vars['errors'] = (Config::get('session.driver') && Session::has('errors'))
? Session::get('errors')
: new \Illuminate\Support\ViewErrorBag;
2014-05-14 23:24:20 +10:00
/*
* Handle AJAX requests and execute the life cycle functions
*/
$this->initCustomObjects();
$this->initComponents();
/*
* Give the layout and page an opportunity to participate
* after components are initialized and before AJAX is handled.
*/
if ($this->layoutObj) {
CmsException::mask($this->layout, 300);
$this->layoutObj->onInit();
CmsException::unmask();
}
CmsException::mask($this->page, 300);
$this->pageObj->onInit();
CmsException::unmask();
/*
* Extensibility
*/
if (
($event = $this->fireEvent('page.init', [$page], true)) ||
($event = Event::fire('cms.page.init', [$this, $page], true))
) {
return $event;
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
/*
* Execute AJAX event
*/
if ($useAjax && $ajaxResponse = $this->execAjaxHandlers()) {
2014-05-14 23:24:20 +10:00
return $ajaxResponse;
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
/*
* Execute postback handler
*/
2014-10-11 01:22:03 +02:00
if (
$useAjax &&
2014-10-11 01:22:03 +02:00
($handler = post('_handler')) &&
($handlerResponse = $this->runAjaxHandler($handler)) &&
$handlerResponse !== true
) {
2014-05-14 23:24:20 +10:00
return $handlerResponse;
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
/*
* Execute page lifecycle
*/
2014-10-11 01:22:03 +02:00
if ($cycleResponse = $this->execPageCycle()) {
2014-05-17 18:08:01 +02:00
return $cycleResponse;
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
/*
* Extensibility
2014-05-14 23:24:20 +10:00
*/
if (
($event = $this->fireEvent('page.beforeRenderPage', [$page], true)) ||
($event = Event::fire('cms.page.beforeRenderPage', [$this, $page], true))
) {
$this->pageContents = $event;
}
else {
/*
* Render the page
*/
CmsException::mask($this->page, 400);
$this->loader->setObject($this->page);
$template = $this->twig->loadTemplate($this->page->getFullPath());
$this->pageContents = $template->render($this->vars);
CmsException::unmask();
}
2014-05-14 23:24:20 +10:00
/*
* Render the layout
*/
CmsException::mask($this->layout, 400);
$this->loader->setObject($this->layout);
$template = $this->twig->loadTemplate($this->layout->getFullPath());
$result = $template->render($this->vars);
CmsException::unmask();
2014-05-14 23:24:20 +10:00
return $result;
}
/**
* Invokes the current page cycle without rendering the page,
* used by AJAX handler that may rely on the logic inside the action.
*/
public function pageCycle()
{
return $this->execPageCycle();
}
/**
* Executes the page life cycle.
* Creates an object from the PHP sections of the page and
* it's layout, then executes their life cycle functions.
*/
protected function execPageCycle()
{
/*
* Extensibility
*/
if (
($event = $this->fireEvent('page.start', [], true)) ||
($event = Event::fire('cms.page.start', [$this], true))
) {
return $event;
}
/*
* Run layout functions
*/
if ($this->layoutObj) {
CmsException::mask($this->layout, 300);
$response = (($result = $this->layoutObj->onStart()) ||
($result = $this->layout->runComponents()) ||
($result = $this->layoutObj->onBeforePageStart())) ? $result: null;
CmsException::unmask();
if ($response) {
return $response;
}
2014-10-11 01:22:03 +02:00
}
/*
* Run page functions
*/
CmsException::mask($this->page, 300);
$response = (($result = $this->pageObj->onStart()) ||
($result = $this->page->runComponents()) ||
($result = $this->pageObj->onEnd())) ? $result : null;
CmsException::unmask();
if ($response) {
return $response;
}
/*
* Run remaining layout functions
*/
if ($this->layoutObj) {
CmsException::mask($this->layout, 300);
$response = ($result = $this->layoutObj->onEnd()) ? $result : null;
CmsException::unmask();
}
/*
* Extensibility
*/
if (
($event = $this->fireEvent('page.end', [], true)) ||
($event = Event::fire('cms.page.end', [$this], true))
) {
return $event;
}
return $response;
2014-05-14 23:24:20 +10:00
}
//
// Initialization
//
2014-05-14 23:24:20 +10:00
/**
* Initializes the Twig environment and loader.
* Registers the \Cms\Twig\Extension object with Twig.
* @return void
2014-05-14 23:24:20 +10:00
*/
protected function initTwigEnvironment()
{
$this->loader = new TwigLoader;
$isDebugMode = Config::get('app.debug', false);
2014-05-14 23:24:20 +10:00
$options = [
'auto_reload' => true,
'debug' => $isDebugMode,
];
2014-10-11 01:22:03 +02:00
if (!Config::get('cms.twigNoCache')) {
2015-02-07 14:50:03 +11:00
$options['cache'] = storage_path().'/cms/twig';
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
$this->twig = new Twig_Environment($this->loader, $options);
$this->twig->addExtension(new CmsTwigExtension($this));
$this->twig->addExtension(new SystemTwigExtension);
2014-10-11 01:22:03 +02:00
if ($isDebugMode) {
$this->twig->addExtension(new DebugExtension($this));
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
}
/**
* Initializes the custom layout and page objects.
2014-08-13 21:25:57 +10:00
* @return void
2014-05-14 23:24:20 +10:00
*/
protected function initCustomObjects()
{
$this->layoutObj = null;
2014-05-14 23:24:20 +10:00
if (!$this->layout->isFallBack()) {
CmsException::mask($this->layout, 300);
$parser = new CodeParser($this->layout);
$this->layoutObj = $parser->source($this->page, $this->layout, $this);
CmsException::unmask();
2014-05-14 23:24:20 +10:00
}
CmsException::mask($this->page, 300);
$parser = new CodeParser($this->page);
$this->pageObj = $parser->source($this->page, $this->layout, $this);
CmsException::unmask();
2014-05-14 23:24:20 +10:00
}
/**
* Initializes the components for the layout and page.
2014-08-13 21:25:57 +10:00
* @return void
2014-05-14 23:24:20 +10:00
*/
protected function initComponents()
{
if (!$this->layout->isFallBack()) {
foreach ($this->layout->settings['components'] as $component => $properties) {
2014-10-30 16:54:56 +11:00
list($name, $alias) = strpos($component, ' ')
? explode(' ', $component)
: [$component, $component];
$this->addComponent($name, $alias, $properties, true);
2014-05-14 23:24:20 +10:00
}
}
foreach ($this->page->settings['components'] as $component => $properties) {
2014-10-30 16:54:56 +11:00
list($name, $alias) = strpos($component, ' ')
? explode(' ', $component)
: [$component, $component];
$this->addComponent($name, $alias, $properties);
}
/*
* Extensibility
*/
2015-01-21 22:03:52 +11:00
$this->fireEvent('page.initComponents', [$this->page, $this->layout]);
Event::fire('cms.page.initComponents', [$this, $this->page, $this->layout]);
}
//
// AJAX
//
2014-05-14 23:24:20 +10:00
/**
* Executes the page, layout, component and plugin AJAX handlers.
* @return mixed Returns the AJAX Response object or null.
*/
protected function execAjaxHandlers()
{
if ($handler = trim(Request::header('X_OCTOBER_REQUEST_HANDLER'))) {
try {
/*
* Validate the handler name
*/
2014-10-11 01:22:03 +02:00
if (!preg_match('/^(?:\w+\:{2})?on[A-Z]{1}[\w+]*$/', $handler)) {
2014-05-14 23:24:20 +10:00
throw new CmsException(Lang::get('cms::lang.ajax_handler.invalid_name', ['name'=>$handler]));
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
/*
* Validate the handler partial list
*/
if ($partialList = trim(Request::header('X_OCTOBER_REQUEST_PARTIALS'))) {
$partialList = explode('&', $partialList);
foreach ($partialList as $partial) {
2014-10-11 01:22:03 +02:00
if (!preg_match('/^(?:\w+\:{2}|@)?[a-z0-9\_\-\.\/]+$/i', $partial)) {
2014-05-14 23:24:20 +10:00
throw new CmsException(Lang::get('cms::lang.partial.invalid_name', ['name'=>$partial]));
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
}
}
else {
2014-05-14 23:24:20 +10:00
$partialList = [];
}
$responseContents = [];
/*
* Execute the handler
*/
2014-10-11 01:22:03 +02:00
if (!$result = $this->runAjaxHandler($handler)) {
2014-05-14 23:24:20 +10:00
throw new CmsException(Lang::get('cms::lang.ajax_handler.not_found', ['name'=>$handler]));
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
/*
* If the handler returned an array, we should add it to output for rendering.
* If it is a string, add it to the array with the key "result".
*/
2014-10-11 01:22:03 +02:00
if (is_array($result)) {
2014-05-14 23:24:20 +10:00
$responseContents = array_merge($responseContents, $result);
}
elseif (is_string($result)) {
2014-05-14 23:24:20 +10:00
$responseContents['result'] = $result;
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
/*
* Render partials and return the response as array that will be converted to JSON automatically.
*/
2014-10-11 01:22:03 +02:00
foreach ($partialList as $partial) {
2014-05-14 23:24:20 +10:00
$responseContents[$partial] = $this->renderPartial($partial);
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
/*
* If the handler returned a redirect, process it so framework.js knows to redirect
* the browser and not the request!
*/
if ($result instanceof RedirectResponse) {
$responseContents['X_OCTOBER_REDIRECT'] = $result->getTargetUrl();
}
return Response::make($responseContents, $this->statusCode);
}
catch (ValidationException $ex) {
2014-05-14 23:24:20 +10:00
/*
* Handle validation errors
*/
$responseContents['X_OCTOBER_ERROR_FIELDS'] = $ex->getFields();
$responseContents['X_OCTOBER_ERROR_MESSAGE'] = $ex->getMessage();
2015-03-03 18:41:09 +11:00
throw new AjaxException($responseContents);
}
catch (Exception $ex) {
throw $ex;
2014-05-14 23:24:20 +10:00
}
}
return null;
}
/**
* Tries to find and run an AJAX handler in the page, layout, components and plugins.
* The method stops as soon as the handler is found.
* @return boolean Returns true if the handler was found. Returns false otherwise.
*/
protected function runAjaxHandler($handler)
{
/*
* Process Component handler
*/
if (strpos($handler, '::')) {
list($componentName, $handlerName) = explode('::', $handler);
$componentObj = $this->findComponentByName($componentName);
if ($componentObj && method_exists($componentObj, $handlerName)) {
$this->componentContext = $componentObj;
$result = $componentObj->runAjaxHandler($handlerName);
2014-05-14 23:24:20 +10:00
return ($result) ?: true;
}
}
2014-05-14 23:24:20 +10:00
/*
* Process code section handler
*/
else {
2014-05-14 23:24:20 +10:00
if (method_exists($this->pageObj, $handler)) {
$result = $this->pageObj->$handler();
return ($result) ?: true;
}
if (!$this->layout->isFallBack() && method_exists($this->layoutObj, $handler)) {
$result = $this->layoutObj->$handler();
return ($result) ?: true;
}
/*
* Cycle each component to locate a usable handler
*/
if (($componentObj = $this->findComponentByHandler($handler)) !== null) {
$this->componentContext = $componentObj;
$result = $componentObj->runAjaxHandler($handler);
2014-05-14 23:24:20 +10:00
return ($result) ?: true;
}
}
return false;
}
//
// Rendering
//
2014-05-14 23:24:20 +10:00
/**
* Renders a requested page.
* The framework uses this method internally.
*/
public function renderPage()
{
2014-08-19 12:35:06 +10:00
$contents = $this->pageContents;
/*
* Extensibility
*/
if (
($event = $this->fireEvent('page.render', [$contents], true)) ||
($event = Event::fire('cms.page.render', [$this, $contents], true))
) {
2014-08-19 12:35:06 +10:00
return $event;
2014-10-11 01:22:03 +02:00
}
2014-08-19 12:35:06 +10:00
return $contents;
2014-05-14 23:24:20 +10:00
}
/**
* Renders a requested partial.
* The framework uses this method internally.
* @param string $partial The view to load.
* @param array $parameters Parameter variables to pass to the view.
* @param bool $throwException Throw an exception if the partial is not found.
* @return mixed Partial contents or false if not throwing an exception.
2014-05-14 23:24:20 +10:00
*/
public function renderPartial($name, $parameters = [], $throwException = true)
2014-05-14 23:24:20 +10:00
{
$vars = $this->vars;
2014-05-14 23:24:20 +10:00
/*
* Alias @ symbol for ::
*/
2014-10-11 01:22:03 +02:00
if (substr($name, 0, 1) == '@') {
2014-05-14 23:24:20 +10:00
$name = '::' . substr($name, 1);
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
/*
* Process Component partial
*/
if (strpos($name, '::') !== false) {
list($componentAlias, $partialName) = explode('::', $name);
/*
* Component alias not supplied
*/
if (!strlen($componentAlias)) {
if ($this->componentContext !== null) {
2014-05-14 23:24:20 +10:00
$componentObj = $this->componentContext;
}
elseif (($componentObj = $this->findComponentByPartial($partialName)) === null) {
2014-10-11 01:22:03 +02:00
if ($throwException) {
throw new CmsException(Lang::get('cms::lang.partial.not_found_name', ['name'=>$partialName]));
}
else {
return false;
2014-10-11 01:22:03 +02:00
}
}
2014-05-14 23:24:20 +10:00
/*
* Component alias is supplied
*/
}
else {
if (($componentObj = $this->findComponentByName($componentAlias)) === null) {
2014-10-11 01:22:03 +02:00
if ($throwException) {
throw new CmsException(Lang::get('cms::lang.component.not_found', ['name'=>$componentAlias]));
}
else {
return false;
2014-10-11 01:22:03 +02:00
}
}
2014-05-14 23:24:20 +10:00
}
$partial = null;
$this->componentContext = $componentObj;
/*
* Check if the theme has an override
*/
if (strpos($partialName, '/') === false) {
$overrideName = $componentObj->alias . '/' . $partialName;
2014-05-14 23:24:20 +10:00
$partial = Partial::loadCached($this->theme, $overrideName);
}
/*
* Check the component partial
*/
2014-10-11 01:22:03 +02:00
if ($partial === null) {
2014-05-14 23:24:20 +10:00
$partial = ComponentPartial::loadCached($componentObj, $partialName);
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
if ($partial === null) {
2014-10-11 01:22:03 +02:00
if ($throwException) {
throw new CmsException(Lang::get('cms::lang.partial.not_found_name', ['name'=>$name]));
}
else {
return false;
2014-10-11 01:22:03 +02:00
}
}
2014-05-14 23:24:20 +10:00
/*
* Set context for self access
*/
$this->vars['__SELF__'] = $componentObj;
}
else {
2014-05-14 23:24:20 +10:00
/*
* Process theme partial
*/
if (($partial = Partial::loadCached($this->theme, $name)) === null) {
2014-10-11 01:22:03 +02:00
if ($throwException) {
throw new CmsException(Lang::get('cms::lang.partial.not_found_name', ['name'=>$name]));
}
else {
return false;
2014-10-11 01:22:03 +02:00
}
}
2014-05-14 23:24:20 +10:00
}
/*
2014-10-30 16:54:56 +11:00
* Run functions for CMS partials only (Cms\Classes\Partial)
*/
2014-10-30 16:54:56 +11:00
if ($partial instanceof Partial) {
$manager = ComponentManager::instance();
foreach ($partial->settings['components'] as $component => $properties) {
// Do not inject the viewBag component to the environment.
// Not sure if they're needed there by the requirements,
// but there were problems with array-typed properties used by Static Pages
// snippets and setComponentPropertiesFromParams(). --ab
if ($component == 'viewBag')
continue;
2014-10-30 16:54:56 +11:00
list($name, $alias) = strpos($component, ' ')
? explode(' ', $component)
: [$component, $component];
2014-10-30 16:54:56 +11:00
if (!$componentObj = $manager->makeComponent($name, $this->pageObj, $properties)) {
throw new CmsException(Lang::get('cms::lang.component.not_found', ['name'=>$name]));
2014-10-30 16:54:56 +11:00
}
$componentObj->alias = $alias;
2014-10-30 16:54:56 +11:00
$parameters[$alias] = $partial->components[$alias] = $componentObj;
array_push($this->partialComponentStack, [
'name' => $alias,
'obj' => $componentObj
]);
$this->setComponentPropertiesFromParams($componentObj, $parameters);
$componentObj->init();
}
CmsException::mask($this->page, 300);
$parser = new CodeParser($partial);
$partialObj = $parser->source($this->page, $this->layout, $this);
CmsException::unmask();
CmsException::mask($partial, 300);
$partialObj->onStart();
$partial->runComponents();
$partialObj->onEnd();
CmsException::unmask();
}
/*
* Render the parital
*/
CmsException::mask($partial, 400);
2014-05-14 23:24:20 +10:00
$this->loader->setObject($partial);
$template = $this->twig->loadTemplate($partial->getFullPath());
$result = $template->render(array_merge($this->vars, $parameters));
2014-10-30 16:54:56 +11:00
CmsException::unmask();
2014-10-30 16:54:56 +11:00
if ($partial instanceof Partial) {
if ($this->partialComponentStack) {
array_pop($this->partialComponentStack);
}
}
$this->vars = $vars;
2014-05-14 23:24:20 +10:00
$this->componentContext = null;
return $result;
}
/**
* Renders a requested content file.
* The framework uses this method internally.
*/
public function renderContent($name)
{
/*
* Extensibility
*/
if (
($event = $this->fireEvent('page.beforeRenderContent', [$name], true)) ||
($event = Event::fire('cms.page.beforeRenderContent', [$this, $name], true))
) {
$content = $event;
}
/*
* Load content from theme
*/
elseif (($content = Content::loadCached($this->theme, $name)) === null) {
throw new CmsException(Lang::get('cms::lang.content.not_found_name', ['name'=>$name]));
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
$fileContent = $content->parsedMarkup;
2014-05-14 23:24:20 +10:00
/*
* Extensibility
*/
if (
($event = $this->fireEvent('page.renderContent', [$name, $fileContent], true)) ||
($event = Event::fire('cms.page.renderContent', [$this, $name, $fileContent], true))
) {
return $event;
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
return $fileContent;
}
/**
* Renders a component's default content.
2014-05-14 23:24:20 +10:00
* @return string Returns the component default contents.
*/
public function renderComponent($name, $parameters = [])
2014-05-14 23:24:20 +10:00
{
if ($componentObj = $this->findComponentByName($name)) {
$componentObj->id = uniqid($name);
$componentObj->setProperties(array_merge($componentObj->getProperties(), $parameters));
2014-10-11 01:22:03 +02:00
if ($result = $componentObj->onRender()) {
return $result;
2014-10-11 01:22:03 +02:00
}
}
return $this->renderPartial($name.'::default', [], false);
2014-05-14 23:24:20 +10:00
}
//
// Setters
//
/**
* Sets the status code for the current web response.
* @param int $code Status code
*/
public function setStatusCode($code)
{
$this->statusCode = (int) $code;
return $this;
}
//
// Getters
//
/**
* Returns an existing instance of the controller.
* If the controller doesn't exists, returns null.
* @return mixed Returns the controller object or null.
*/
public static function getController()
{
return self::$instance;
}
2014-05-14 23:24:20 +10:00
/**
* Returns the current CMS theme.
* @return \Cms\Classes\Theme
*/
public function getTheme()
{
return $this->theme;
}
/**
* Returns the Twig environment.
* @return Twig_Environment
*/
public function getTwig()
{
return $this->twig;
}
/**
* Returns the Twig loader.
* @return Cms\Twig\Loader
*/
public function getLoader()
{
return $this->loader;
}
2014-05-14 23:24:20 +10:00
/**
* Returns the routing object.
* @return \Cms\Classes\Router
*/
public function getRouter()
{
return $this->router;
}
/**
* Intended to be called from the layout, returns the page code base object.
* @return \Cms\Classes\CodeBase
*/
public function getPageObject()
{
return $this->pageObj;
}
2015-01-21 21:38:42 -08:00
/**
* Returns the CMS page object being processed by the controller.
* The object is not available on the early stages of the controller
* initialization.
* @return \Cms\Classes\Page Returns the Page object or null.
*/
public function getPage()
{
return $this->page;
}
/**
* Intended to be called from the page, returns the layout code base object.
* @return \Cms\Classes\CodeBase
*/
public function getLayoutObject()
{
return $this->layoutObj;
}
//
// Page helpers
//
2014-05-14 23:24:20 +10:00
/**
* Looks up the URL for a supplied page and returns it relative to the website root.
*
* @param mixed $name Specifies the Cms Page file name.
* @param array $parameters Route parameters to consider in the URL.
2014-05-17 18:08:01 +02:00
* @param bool $routePersistence By default the existing routing parameters will be included
2014-05-14 23:24:20 +10:00
* @return string
*/
public function pageUrl($name, $parameters = [], $routePersistence = true)
2014-05-14 23:24:20 +10:00
{
2014-10-11 01:22:03 +02:00
if (!$name) {
2014-05-14 23:24:20 +10:00
return null;
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
/*
* Second parameter can act as third
*/
if (is_bool($parameters)) {
$routePersistence = $parameters;
$parameters = [];
}
2014-10-11 01:22:03 +02:00
if ($routePersistence) {
2014-05-14 23:24:20 +10:00
$parameters = array_merge($this->router->getParameters(), $parameters);
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
2014-10-11 01:22:03 +02:00
if (!$url = $this->router->findByFile($name, $parameters)) {
return null;
2014-10-11 01:22:03 +02:00
}
2014-10-11 01:22:03 +02:00
if (substr($url, 0, 1) == '/') {
$url = substr($url, 1);
2014-10-11 01:22:03 +02:00
}
$routeAction = 'Cms\Classes\Controller@run';
$actionExists = Route::getRoutes()->getByAction($routeAction) !== null;
if ($actionExists) {
return URL::action($routeAction, ['slug' => $url]);
}
else {
return URL::to($url);
}
2014-05-14 23:24:20 +10:00
}
/**
* Looks up the current page URL with supplied parameters and route persistence.
*/
public function currentPageUrl($parameters = [], $routePersistence = true)
{
return $this->pageUrl($this->page->getFileName(), $parameters, $routePersistence);
}
/**
* Converts supplied URL to a theme URL relative to the website root. If the URL provided is an
* array then the files will be combined.
* @param mixed $url Specifies the theme-relative URL. If null, the theme path is returned.
* @return string
*/
public function themeUrl($url = null)
{
$themeDir = $this->getTheme()->getDirName();
2014-05-14 23:24:20 +10:00
if (is_array($url)) {
$_url = URL::to(CombineAssets::combine($url, themes_path().'/'.$themeDir));
}
else {
$_url = Config::get('cms.themesPath', '/themes').'/'.$themeDir;
2014-10-11 01:22:03 +02:00
if ($url !== null) {
$_url .= '/'.$url;
}
$_url = URL::asset($_url);
}
2014-05-14 23:24:20 +10:00
return $_url;
}
/**
* Returns a routing parameter.
* @param string Routing parameter name.
* @param string Default to use if none is found.
* @return string
*/
public function param($name, $default = null)
{
return $this->router->getParameter($name, $default);
}
//
// Component helpers
//
/**
* Adds a component to the page object
* @param mixed $name Component class name or short name
* @param string $alias Alias to give the component
* @param array $properties Component properties
* @param bool $addToLayout Add to layout, instead of page
* @return ComponentBase Component object
*/
public function addComponent($name, $alias, $properties, $addToLayout = false)
{
$manager = ComponentManager::instance();
if ($addToLayout) {
if (!$componentObj = $manager->makeComponent($name, $this->layoutObj, $properties)) {
throw new CmsException(Lang::get('cms::lang.component.not_found', ['name'=>$name]));
}
$componentObj->alias = $alias;
$this->vars[$alias] = $this->layout->components[$alias] = $componentObj;
}
else {
if (!$componentObj = $manager->makeComponent($name, $this->pageObj, $properties)) {
throw new CmsException(Lang::get('cms::lang.component.not_found', ['name'=>$name]));
}
$componentObj->alias = $alias;
$this->vars[$alias] = $this->page->components[$alias] = $componentObj;
}
$this->setComponentPropertiesFromParams($componentObj);
$componentObj->init();
return $componentObj;
}
2014-05-14 23:24:20 +10:00
/**
* Searches the layout and page components by an alias
* @return ComponentBase The component object, if found
*/
public function findComponentByName($name)
2014-05-14 23:24:20 +10:00
{
2014-10-11 01:22:03 +02:00
if (isset($this->page->components[$name])) {
2014-05-14 23:24:20 +10:00
return $this->page->components[$name];
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
2014-10-11 01:22:03 +02:00
if (isset($this->layout->components[$name])) {
2014-05-14 23:24:20 +10:00
return $this->layout->components[$name];
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
foreach ($this->partialComponentStack as $componentInfo) {
if ($componentInfo['name'] == $name) {
return $componentInfo['obj'];
}
}
2014-05-14 23:24:20 +10:00
return null;
}
/**
* Searches the layout and page components by an AJAX handler
* @return ComponentBase The component object, if found
*/
public function findComponentByHandler($handler)
2014-05-14 23:24:20 +10:00
{
foreach ($this->page->components as $component) {
2014-10-11 01:22:03 +02:00
if (method_exists($component, $handler)) {
2014-05-14 23:24:20 +10:00
return $component;
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
}
foreach ($this->layout->components as $component) {
2014-10-11 01:22:03 +02:00
if (method_exists($component, $handler)) {
2014-05-14 23:24:20 +10:00
return $component;
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
}
return null;
}
/**
* Searches the layout and page components by a partial file
* @return ComponentBase The component object, if found
*/
public function findComponentByPartial($partial)
2014-05-14 23:24:20 +10:00
{
foreach ($this->page->components as $component) {
$fileName = ComponentPartial::getFilePath($component, $partial);
2014-10-11 01:22:03 +02:00
if (!strlen(File::extension($fileName))) {
2014-05-14 23:24:20 +10:00
$fileName .= '.htm';
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
2014-10-11 01:22:03 +02:00
if (File::isFile($fileName)) {
2014-05-14 23:24:20 +10:00
return $component;
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
}
foreach ($this->layout->components as $component) {
$fileName = ComponentPartial::getFilePath($component, $partial);
2014-10-11 01:22:03 +02:00
if (!strlen(File::extension($fileName))) {
2014-05-14 23:24:20 +10:00
$fileName .= '.htm';
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
2014-10-11 01:22:03 +02:00
if (File::isFile($fileName)) {
2014-05-14 23:24:20 +10:00
return $component;
2014-10-11 01:22:03 +02:00
}
2014-05-14 23:24:20 +10:00
}
return null;
}
/**
* Set the component context manually, used by Components when calling renderPartial.
* @param ComponentBase $component
* @return void
*/
public function setComponentContext(ComponentBase $component)
{
$this->componentContext = $component;
}
/**
* Sets component property values from partial parameters.
* The property values should be defined as {{ param }}.
* @param ComponentBase $component The component object.
* @param array $parameters Specifies the partial parameters.
* @return Returns updated properties.
*/
protected function setComponentPropertiesFromParams($component, $parameters = [])
{
$properties = $component->getProperties();
$routerParameters = $this->router->getParameters();
2014-10-30 16:54:56 +11:00
foreach ($properties as $propertyName => $propertyValue) {
$matches = [];
if (preg_match('/^\{\{([^\}]+)\}\}$/', $propertyValue, $matches)) {
$paramName = trim($matches[1]);
2014-10-30 16:54:56 +11:00
if (substr($paramName, 0, 1) == ':') {
$routeParamName = substr($paramName, 1);
$newPropertyValue = array_key_exists($routeParamName, $routerParameters)
? $routerParameters[$routeParamName]
2014-10-30 16:54:56 +11:00
: null;
2014-10-30 16:54:56 +11:00
}
else {
$newPropertyValue = array_key_exists($paramName, $parameters)
2014-10-30 16:54:56 +11:00
? $parameters[$paramName]
: null;
}
$component->setProperty($propertyName, $newPropertyValue);
$component->setExternalPropertyName($propertyName, $paramName);
2014-10-30 16:54:56 +11:00
}
}
}
//
// Keep Laravel Happy
//
/**
* Get the middleware assigned to the controller.
*
* @return array
*/
public function getMiddleware()
{
return [];
}
/**
* Get the registered "before" filters.
*
* @return array
*/
public function getBeforeFilters()
{
return [];
}
/**
* Get the registered "after" filters.
*
* @return array
*/
public function getAfterFilters()
{
return [];
}
/**
* Execute an action on the controller.
*
* @param string $method
* @param array $parameters
* @return \Symfony\Component\HttpFoundation\Response
*/
public function callAction($method, $parameters)
{
return call_user_func_array(array($this, $method), $parameters);
}
2014-09-11 12:44:52 +08:00
}