From 045d18696000a752973ca18189f0ac1e6e152353 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Thu, 17 Mar 2016 19:25:50 +1100 Subject: [PATCH] Finishing porting old functionality - Add validation to Cms Objects - Add local viewBag logic - Implement new initCacheItem process - Implement getter for viewBag/settings properties --- modules/cms/classes/CmsCompoundObject.php | 110 +++++++++++++++++++++- modules/cms/classes/CmsObject.php | 52 +++++++++- modules/cms/classes/Content.php | 51 ++++------ modules/cms/classes/Page.php | 11 ++- modules/cms/controllers/Index.php | 4 +- 5 files changed, 189 insertions(+), 39 deletions(-) diff --git a/modules/cms/classes/CmsCompoundObject.php b/modules/cms/classes/CmsCompoundObject.php index f9d9c8a57..f09b1d131 100644 --- a/modules/cms/classes/CmsCompoundObject.php +++ b/modules/cms/classes/CmsCompoundObject.php @@ -54,6 +54,7 @@ class CmsCompoundObject extends CmsObject protected $passthru = [ 'lists', 'where', + 'sortBy', 'whereComponent', 'withComponent' ]; @@ -69,15 +70,22 @@ class CmsCompoundObject extends CmsObject protected static $objectComponentPropertyMap = null; /** - * After fetch event + * @var mixed Cache store for the getViewBag method. + */ + protected $viewBagCache = false; + + /** + * Triggered after the object is loaded. + * @return void */ public function afterFetch() { $this->parseComponentSettings(); + $this->parseSettings(); } /** - * Create a new Eloquent Collection instance. + * Create a new Collection instance. * * @param array $models * @return \October\Rain\Halcyon\Collection @@ -87,6 +95,17 @@ class CmsCompoundObject extends CmsObject return new CmsObjectCollection($models); } + /** + * Parses the settings array. + * Child classes can override this method in order to update + * the content of the $settings property after the object + * is loaded from a file. + */ + protected function parseSettings() + { + $this->fillViewBagArray(); + } + // // Components // @@ -255,6 +274,57 @@ class CmsCompoundObject extends CmsObject return []; } + /** + * Clears the object cache. + * @param \Cms\Classes\Theme $theme Specifies a parent theme. + * @return void + */ + public static function clearCache($theme) + { + $key = crc32($theme->getPath()).'component-properties'; + Cache::forget($key); + } + + // + // View Bag + // + + /** + * Returns the configured view bag component. + * This method is used only in the back-end and for internal system needs when + * the standard way to access components is not an option. + * @return \Cms\Classes\ViewBag Returns the view bag component instance. + */ + public function getViewBag() + { + if ($this->viewBagCache !== false) { + return $this->viewBagCache; + } + + $componentName = 'viewBag'; + + if (!isset($this->settings['components'][$componentName])) { + $viewBag = new ViewBag(null, []); + $viewBag->name = $componentName; + + return $this->viewBagCache = $viewBag; + } + + return $this->viewBagCache = $this->getComponent($componentName); + } + + /* + * Copies view bag properties to the view bag array. + * This is required for the back-end editors. + */ + protected function fillViewBagArray() + { + $viewBag = $this->getViewBag(); + foreach ($viewBag->getProperties() as $name => $value) { + $this->viewBag[$name] = $value; + } + } + // // Twig // @@ -292,6 +362,42 @@ class CmsCompoundObject extends CmsObject // Magic // + /** + * Implements getter functionality for visible properties defined in + * the settings section or view bag array. + */ + public function __get($name) + { + if (is_array($this->settings) && array_key_exists($name, $this->settings)) { + return $this->settings[$name]; + } + + if (is_array($this->viewBag) && array_key_exists($name, $this->viewBag)) { + return $this->viewBag[$name]; + } + + return parent::__get($name); + } + + /** + * Determine if an attribute exists on the object. + * + * @param string $key + * @return void + */ + public function __isset($key) + { + if (parent::__isset($key) === true) { + return true; + } + + if (isset($this->viewBag[$key]) === true) { + return true; + } + + return isset($this->settings[$key]); + } + /** * Dynamically handle calls into the query instance. * diff --git a/modules/cms/classes/CmsObject.php b/modules/cms/classes/CmsObject.php index 75ba1a6c3..ebc511148 100644 --- a/modules/cms/classes/CmsObject.php +++ b/modules/cms/classes/CmsObject.php @@ -6,6 +6,7 @@ use October\Rain\Halcyon\Model as HalcyonModel; use Cms\Contracts\CmsObject as CmsObjectContract; use ApplicationException; use ValidationException; +use SystemException; use Exception; /** @@ -17,6 +18,23 @@ use Exception; */ class CmsObject extends HalcyonModel implements CmsObjectContract { + use \October\Rain\Halcyon\Traits\Validation; + + /** + * @var array The rules to be applied to the data. + */ + public $rules = []; + + /** + * @var array The array of custom attribute names. + */ + public $attributeNames = []; + + /** + * @var array The array of custom error messages. + */ + public $customMessages = []; + /** * @var array The attributes that are mass assignable. */ @@ -29,6 +47,22 @@ class CmsObject extends HalcyonModel implements CmsObjectContract */ protected $isCompoundObject = false; + /** + * @var \Cms\Classes\Theme A reference to the CMS theme containing the object. + */ + protected $themeCache; + + /** + * Create a new CMS object instance. + * + * @param array $attributes + * @return void + */ + public function __construct(array $attributes = []) + { + parent::__construct($attributes); + } + /** * Loads the object from a file. * This method is used in the CMS back-end. It doesn't use any caching. @@ -99,6 +133,22 @@ class CmsObject extends HalcyonModel implements CmsObjectContract } } + /** + * Returns the CMS theme this object belongs to. + * @return \Cms\Classes\Theme + */ + public function getThemeAttribute() + { + if ($this->themeCache !== null) { + return $this->themeCache; + } + + $themeName = $this->getDatasourceName() + ?: static::getDatasourceResolver()->getDefaultDatasource(); + + return $this->themeCache = Theme::load($themeName); + } + /** * Returns the full path to the template file corresponding to this object. * @return string @@ -109,7 +159,7 @@ class CmsObject extends HalcyonModel implements CmsObjectContract $fileName = $this->fileName; } - return $this->getTheme()->getBasePath().'/'.$this->getObjectTypeDirName().'/'.$fileName; + return $this->theme->getPath().'/'.$this->getObjectTypeDirName().'/'.$fileName; } /** diff --git a/modules/cms/classes/Content.php b/modules/cms/classes/Content.php index ea6609a93..7a115bf3e 100644 --- a/modules/cms/classes/Content.php +++ b/modules/cms/classes/Content.php @@ -22,50 +22,38 @@ class Content extends CmsCompoundObject protected $allowedExtensions = ['htm', 'txt', 'md']; /** - * @var string Contains the parsed markup. + * @var array List of attribute names which are not considered "settings". */ - public $parsedMarkup = null; + protected $purgeable = ['parsedMarkup']; /** - * Loads the object from a file. - * @param \Cms\Classes\Theme $theme Specifies the theme the object belongs to. - * @param string $fileName Specifies the file name, with the extension. - * The file name can contain only alphanumeric symbols, dashes and dots. - * @return boolean Returns true if the object was successfully loaded. Otherwise returns false. - */ - public static function load($theme, $fileName) - { - if ($obj = parent::load($theme, $fileName)) { - $obj->parsedMarkup = $obj->parseMarkup(); - } - - return $obj; - } - - /** - * Initializes the object properties from the cached data. + * Initializes the object properties from the cached data. The extra data + * set here becomes available as attributes set on the model after fetch. * @param array $cached The cached data array. */ - protected function initFromCache($cached) + public static function initCacheItem(&$item) { - parent::initFromCache($cached); - - $this->parsedMarkup = array_key_exists('parsed-markup', $cached) - ? $cached['parsed-markup'] - : $this->parseMarkup($this->markup); + $item['parsedMarkup'] = (new static($item))->parseMarkup(); } /** - * Initializes a cache item. - * @param array &$item The cached item array. + * Returns a default value for parsedMarkup attribute. + * @return string */ - protected function initCacheItem(&$item) + public function getParsedMarkupAttribute() { - parent::initCacheItem($item); - $item['parsed-markup'] = $this->parsedMarkup; + if (array_key_exists('parsedMarkup', $this->attributes)) { + return $this->attributes['parsedMarkup']; + } + + return $this->attributes['parsedMarkup'] = $this->parseMarkup(); } - protected function parseMarkup() + /** + * Parses the content markup according to the file type. + * @return string + */ + public function parseMarkup() { $extension = strtolower(File::extension($this->fileName)); @@ -82,4 +70,5 @@ class Content extends CmsCompoundObject return $result; } + } diff --git a/modules/cms/classes/Page.php b/modules/cms/classes/Page.php index c63852f33..625fac40d 100644 --- a/modules/cms/classes/Page.php +++ b/modules/cms/classes/Page.php @@ -23,8 +23,10 @@ class Page extends CmsCompoundObject */ protected $fillable = [ 'url', + 'layout', 'title', 'description', + 'is_hidden', 'meta_title', 'meta_description', 'markup', @@ -38,20 +40,23 @@ class Page extends CmsCompoundObject */ public $apiBag = []; - protected $settingsValidationRules = [ + /** + * @var array The rules to be applied to the data. + */ + public $rules = [ 'title' => 'required', 'url' => ['required', 'regex:/^\/[a-z0-9\/\:_\-\*\[\]\+\?\|\.\^\\\$]*$/i'] ]; /** * Creates an instance of the object and associates it with a CMS theme. - * @param \Cms\Classes\Theme $theme Specifies the theme the object belongs to. + * @param array $attributes */ public function __construct(array $attributes = []) { parent::__construct($attributes); - $this->settingsValidationMessages = [ + $this->customMessages = [ 'url.regex' => Lang::get('cms::lang.page.invalid_url') ]; } diff --git a/modules/cms/controllers/Index.php b/modules/cms/controllers/Index.php index 9f2bedf21..fa6063e3d 100644 --- a/modules/cms/controllers/Index.php +++ b/modules/cms/controllers/Index.php @@ -273,7 +273,7 @@ class Index extends Controller { $this->validateRequestTheme(); - $page = new Page($this->theme); + $page = Page::inTheme($this->theme); return [ 'layouts' => $page->getLayoutOptions() ]; @@ -356,7 +356,7 @@ class Index extends Controller { $class = $this->resolveTypeClassName($type); - if (!($template = new $class($this->theme))) { + if (!($template = $class::inTheme($this->theme))) { throw new ApplicationException(trans('cms::lang.template.not_found')); }