Convert CmsObject and CmsCompoundObject to use Halcyon
getFullPath -> getFilePath
This commit is contained in:
parent
855a97b35e
commit
24a620e890
|
|
@ -1,13 +1,7 @@
|
|||
<?php namespace Cms\Classes;
|
||||
|
||||
use Ini;
|
||||
use Cache;
|
||||
use Config;
|
||||
use Validator;
|
||||
use SystemException;
|
||||
use ValidationException;
|
||||
use Cms\Classes\ViewBag;
|
||||
use Cms\Classes\CodeBase;
|
||||
use Cms\Twig\Loader as TwigLoader;
|
||||
use Cms\Twig\Extension as CmsTwigExtension;
|
||||
use System\Twig\Extension as SystemTwigExtension;
|
||||
|
|
@ -29,19 +23,13 @@ class CmsCompoundObject extends CmsObject
|
|||
public $components = [];
|
||||
|
||||
/**
|
||||
* @var array INI settings defined in the template file.
|
||||
* @var array INI settings defined in the template file. Not to be confused
|
||||
* with the attribute called settings. In this array, components are bumped
|
||||
* to their own array inside the 'components' key.
|
||||
*/
|
||||
public $settings = [];
|
||||
|
||||
/**
|
||||
* @var string PHP code section of the template file.
|
||||
*/
|
||||
public $code;
|
||||
|
||||
/**
|
||||
* @var string Twig markup section of the template file.
|
||||
*/
|
||||
public $markup;
|
||||
public $settings = [
|
||||
'components' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array Contains the view bag properties.
|
||||
|
|
@ -50,96 +38,59 @@ class CmsCompoundObject extends CmsObject
|
|||
public $viewBag = [];
|
||||
|
||||
/**
|
||||
* @var array Properties that can be set with fill()
|
||||
* @var array The attributes that are mass assignable.
|
||||
*/
|
||||
protected static $fillable = [
|
||||
protected $fillable = [
|
||||
'markup',
|
||||
'settings',
|
||||
'code',
|
||||
'fileName'
|
||||
'code'
|
||||
];
|
||||
|
||||
protected $settingsValidationRules = [];
|
||||
/**
|
||||
* The methods that should be returned from the collection of all objects.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $passthru = [
|
||||
'lists',
|
||||
'where',
|
||||
'whereComponent',
|
||||
'withComponent'
|
||||
];
|
||||
|
||||
protected $settingsValidationMessages = [];
|
||||
|
||||
protected $viewBagValidationRules = [];
|
||||
|
||||
protected $viewBagValidationMessages = [];
|
||||
|
||||
protected $viewBagCache = false;
|
||||
|
||||
protected $originalData = [];
|
||||
/**
|
||||
* @var bool Model supports code and settings sections.
|
||||
*/
|
||||
protected $isCompoundObject = true;
|
||||
|
||||
/**
|
||||
* @var array|null Cache for component properties.
|
||||
*/
|
||||
protected static $objectComponentPropertyMap = null;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* After fetch event
|
||||
*/
|
||||
public static function load($theme, $fileName)
|
||||
public function afterFetch()
|
||||
{
|
||||
if (($obj = parent::load($theme, $fileName)) === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CmsException::mask($obj, 200);
|
||||
$parsedData = SectionParser::parse($obj->content);
|
||||
CmsException::unmask();
|
||||
|
||||
$obj->settings = $parsedData['settings'];
|
||||
$obj->code = $parsedData['code'];
|
||||
$obj->markup = $parsedData['markup'];
|
||||
|
||||
$obj->originalData['settings'] = $obj->settings;
|
||||
$obj->originalData['code'] = $obj->code;
|
||||
$obj->originalData['markup'] = $obj->markup;
|
||||
|
||||
$obj->parseComponentSettings();
|
||||
$obj->parseSettings();
|
||||
|
||||
return $obj;
|
||||
$this->parseComponentSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Create a new Eloquent Collection instance.
|
||||
*
|
||||
* @param string $key
|
||||
* @return void
|
||||
* @param array $models
|
||||
* @return \October\Rain\Halcyon\Collection
|
||||
*/
|
||||
public function __isset($key)
|
||||
public function newCollection(array $models = [])
|
||||
{
|
||||
if (parent::__isset($key) === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isset($this->viewBag[$key]) === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isset($this->settings[$key]);
|
||||
return new CmsObjectCollection($models);
|
||||
}
|
||||
|
||||
//
|
||||
// Components
|
||||
//
|
||||
|
||||
/**
|
||||
* Runs components defined in the settings
|
||||
* Process halts if a component returns a value
|
||||
|
|
@ -154,12 +105,14 @@ class CmsCompoundObject extends CmsObject
|
|||
}
|
||||
|
||||
/**
|
||||
* Parse component sections.
|
||||
* Replace the multiple component sections with a single "components"
|
||||
* Parse component sections.
|
||||
* Replace the multiple component sections with a single "components"
|
||||
* element in the $settings property.
|
||||
*/
|
||||
public function parseComponentSettings()
|
||||
protected function parseComponentSettings()
|
||||
{
|
||||
$this->settings = $this->getSettingsAttribute();
|
||||
|
||||
$manager = ComponentManager::instance();
|
||||
$components = [];
|
||||
foreach ($this->settings as $setting => $value) {
|
||||
|
|
@ -177,102 +130,6 @@ class CmsCompoundObject extends CmsObject
|
|||
$this->settings['components'] = $components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns name of a PHP class to us a parent for the PHP class created for the object's PHP section.
|
||||
* @return mixed Returns the class name or null.
|
||||
*/
|
||||
public function getCodeClassParent()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the PHP code content string.
|
||||
* @param string $value Specifies the PHP code string.
|
||||
* @return \Cms\Classes\CmsCompoundObject Returns the object instance.
|
||||
*/
|
||||
public function setCode($value)
|
||||
{
|
||||
$value = trim($value);
|
||||
$this->code = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the object to the disk.
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$this->code = trim($this->code);
|
||||
$this->markup = trim($this->markup);
|
||||
|
||||
$trim = function (&$values) use (&$trim) {
|
||||
foreach ($values as &$value) {
|
||||
if (!is_array($value)) {
|
||||
$value = trim($value);
|
||||
}
|
||||
else {
|
||||
$trim($value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$trim($this->settings);
|
||||
|
||||
if (array_key_exists('components', $this->settings) && count($this->settings['components']) == 0) {
|
||||
unset($this->settings['components']);
|
||||
}
|
||||
|
||||
$this->validate();
|
||||
|
||||
$content = [];
|
||||
|
||||
if ($this->settings) {
|
||||
$content[] = Ini::render($this->settings);
|
||||
}
|
||||
|
||||
if ($this->code) {
|
||||
if ($this->wrapCodeToPhpTags() && array_get($this->originalData, 'code') != $this->code) {
|
||||
$code = preg_replace('/^\<\?php/', '', $this->code);
|
||||
$code = preg_replace('/^\<\?/', '', $code);
|
||||
$code = preg_replace('/\?>$/', '', $code);
|
||||
|
||||
$content[] = '<?php'.PHP_EOL.$this->code.PHP_EOL.'?>';
|
||||
}
|
||||
else {
|
||||
$content[] = $this->code;
|
||||
}
|
||||
}
|
||||
|
||||
$content[] = $this->markup;
|
||||
|
||||
$this->content = trim(implode(PHP_EOL.'=='.PHP_EOL, $content));
|
||||
parent::save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a component by its name.
|
||||
* This method is used only in the back-end and for internal system needs when
|
||||
|
|
@ -398,98 +255,6 @@ class CmsCompoundObject extends CmsObject
|
|||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the object cache.
|
||||
*/
|
||||
public static function clearCache($theme)
|
||||
{
|
||||
$key = crc32($theme->getPath()).'component-properties';
|
||||
Cache::forget($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the object properties from the cached data.
|
||||
* @param array $cached The cached data array.
|
||||
*/
|
||||
protected function initFromCache($cached)
|
||||
{
|
||||
$this->viewBag = array_get($cached, 'viewBag', []);
|
||||
$this->settings = array_get($cached, 'settings', []);
|
||||
$this->code = array_get($cached, 'code');
|
||||
$this->markup = array_get($cached, 'markup');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a cache item.
|
||||
* @param array &$item The cached item array.
|
||||
*/
|
||||
protected function initCacheItem(&$item)
|
||||
{
|
||||
$item['viewBag'] = $this->viewBag;
|
||||
$item['settings'] = $this->settings;
|
||||
$item['code'] = $this->code;
|
||||
$item['markup'] = $this->markup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the object properties.
|
||||
* Throws a ValidationException in case of an error.
|
||||
*/
|
||||
protected function validate()
|
||||
{
|
||||
$validation = Validator::make(
|
||||
$this->settings,
|
||||
$this->settingsValidationRules,
|
||||
$this->settingsValidationMessages
|
||||
);
|
||||
if ($validation->fails()) {
|
||||
throw new ValidationException($validation);
|
||||
}
|
||||
|
||||
if ($this->viewBagValidationRules && isset($this->settings['viewBag'])) {
|
||||
$validation = Validator::make(
|
||||
$this->settings['viewBag'],
|
||||
$this->viewBagValidationRules,
|
||||
$this->viewBagValidationMessages
|
||||
);
|
||||
if ($validation->fails()) {
|
||||
throw new ValidationException($validation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the content of the code section should be wrapped to PHP tags.
|
||||
* @return boolean
|
||||
*/
|
||||
protected function wrapCodeToPhpTags()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Twig
|
||||
//
|
||||
|
|
@ -522,4 +287,26 @@ class CmsCompoundObject extends CmsObject
|
|||
$stream = $twig->tokenize($markup === false ? $this->markup : $markup, 'getTwigNodeTree');
|
||||
return $twig->parse($stream);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Magic
|
||||
//
|
||||
|
||||
/**
|
||||
* Dynamically handle calls into the query instance.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
if (in_array($method, $this->passthru)) {
|
||||
$collection = $this->get();
|
||||
return call_user_func_array(array($collection, $method), $parameters);
|
||||
}
|
||||
|
||||
return parent::__call($method, $parameters);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -81,7 +81,7 @@ class CmsException extends ApplicationException
|
|||
break;
|
||||
}
|
||||
if ($result !== false) {
|
||||
$this->file = $this->compoundObject->getFullPath();
|
||||
$this->file = $this->compoundObject->getFilePath();
|
||||
|
||||
if (File::isFile($this->file) && is_readable($this->file)) {
|
||||
$this->fileContent = @file($this->file);
|
||||
|
|
|
|||
|
|
@ -1,19 +1,12 @@
|
|||
<?php namespace Cms\Classes;
|
||||
|
||||
use File;
|
||||
use Lang;
|
||||
use Cache;
|
||||
use Config;
|
||||
use Validator;
|
||||
use Cms\Helpers\File as FileHelper;
|
||||
use October\Rain\Extension\Extendable;
|
||||
use October\Rain\Halcyon\Model as HalcyonModel;
|
||||
use Cms\Contracts\CmsObject as CmsObjectContract;
|
||||
use ApplicationException;
|
||||
use ValidationException;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use ArrayAccess;
|
||||
use Exception;
|
||||
use Cms\Contracts\CmsObject as CmsObjectContract;
|
||||
|
||||
/**
|
||||
* This is a base class for all CMS objects - content files, pages, partials and layouts.
|
||||
|
|
@ -22,133 +15,19 @@ use Cms\Contracts\CmsObject as CmsObjectContract;
|
|||
* @package october\cms
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class CmsObject extends Extendable implements ArrayAccess, CmsObjectContract
|
||||
class CmsObject extends HalcyonModel implements CmsObjectContract
|
||||
{
|
||||
/**
|
||||
* @var string Specifies the file name corresponding the CMS object.
|
||||
* @var array The attributes that are mass assignable.
|
||||
*/
|
||||
protected $fileName;
|
||||
|
||||
/**
|
||||
* @var string Specifies the file name, the CMS object was loaded from.
|
||||
*/
|
||||
protected $originalFileName = null;
|
||||
|
||||
/**
|
||||
* @var string The entire file content.
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* @var \Cms\Classes\Theme A reference to the CMS theme containing the object.
|
||||
*/
|
||||
protected $theme;
|
||||
|
||||
/**
|
||||
* @var boolean Indicated whether the object was loaded from the cache.
|
||||
*/
|
||||
protected $loadedFromCache = false;
|
||||
|
||||
protected static $fillable = [
|
||||
'content',
|
||||
'fileName'
|
||||
protected $fillable = [
|
||||
'content'
|
||||
];
|
||||
|
||||
protected static $allowedExtensions = ['htm'];
|
||||
|
||||
protected static $defaultExtension = 'htm';
|
||||
|
||||
/**
|
||||
* @var integer The template file modification time.
|
||||
* @var bool Model supports code and settings sections.
|
||||
*/
|
||||
public $mtime;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* If the theme is specified as NULL, then a query can be performed on the object directly.
|
||||
*/
|
||||
public function __construct(Theme $theme = null)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->theme = $theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the object from a cache.
|
||||
* This method is used by the CMS in the runtime. If the cache is not found, it is created.
|
||||
* @param $theme Specifies the theme the object belongs to.
|
||||
* @param string $fileName Specifies the file name, with the extension.
|
||||
* @return mixed Returns a CMS object instance or null if the object wasn't found.
|
||||
*/
|
||||
public static function loadCached($theme, $fileName)
|
||||
{
|
||||
if (!FileHelper::validatePath($fileName, static::getMaxAllowedPathNesting())) {
|
||||
throw new ApplicationException(Lang::get('cms::lang.cms_object.invalid_file', ['name'=>$fileName]));
|
||||
}
|
||||
|
||||
if (!strlen(File::extension($fileName))) {
|
||||
$fileName .= '.'.static::$defaultExtension;
|
||||
}
|
||||
|
||||
$filePath = static::getFilePath($theme, $fileName);
|
||||
if (array_key_exists($filePath, ObjectMemoryCache::$cache)) {
|
||||
return ObjectMemoryCache::$cache[$filePath];
|
||||
}
|
||||
|
||||
$key = self::getObjectTypeDirName().crc32($filePath);
|
||||
|
||||
clearstatcache($filePath);
|
||||
$cached = Cache::get($key, false);
|
||||
if ($cached !== false && ($cached = @unserialize($cached)) !== false) {
|
||||
if ($cached['mtime'] != @File::lastModified($filePath)) {
|
||||
$cached = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($cached && !File::isFile($filePath)) {
|
||||
$cached = false;
|
||||
}
|
||||
|
||||
if ($cached !== false) {
|
||||
/*
|
||||
* The cached item exists and successfully unserialized.
|
||||
* Initialize the object from the cached data.
|
||||
*/
|
||||
$obj = new static($theme);
|
||||
$obj->content = $cached['content'];
|
||||
$obj->fileName = $fileName;
|
||||
$obj->mtime = File::lastModified($filePath);
|
||||
$obj->loadedFromCache = true;
|
||||
$obj->initFromCache($cached);
|
||||
|
||||
return ObjectMemoryCache::$cache[$filePath] = $obj;
|
||||
}
|
||||
|
||||
/*
|
||||
* The cached item doesn't exists.
|
||||
* Load the object from the file and create the cache.
|
||||
*/
|
||||
if (($obj = static::load($theme, $fileName)) === null) {
|
||||
/*
|
||||
* If the object cannot be loaded from the disk, delete the cache item.
|
||||
*/
|
||||
Cache::forget($key);
|
||||
return null;
|
||||
}
|
||||
|
||||
$cached = [
|
||||
'mtime' => @File::lastModified($filePath),
|
||||
'content' => $obj->content
|
||||
];
|
||||
|
||||
$obj->loadedFromCache = false;
|
||||
$obj->initCacheItem($cached);
|
||||
Cache::put($key, serialize($cached), Config::get('cms.parsedPageCacheTTL', 1440));
|
||||
|
||||
return ObjectMemoryCache::$cache[$filePath] = $obj;
|
||||
}
|
||||
protected $isCompoundObject = false;
|
||||
|
||||
/**
|
||||
* Loads the object from a file.
|
||||
|
|
@ -160,50 +39,77 @@ class CmsObject extends Extendable implements ArrayAccess, CmsObjectContract
|
|||
*/
|
||||
public static function load($theme, $fileName)
|
||||
{
|
||||
if (!FileHelper::validatePath($fileName, static::getMaxAllowedPathNesting())) {
|
||||
throw new ApplicationException(Lang::get('cms::lang.cms_object.invalid_file', ['name'=>$fileName]));
|
||||
}
|
||||
|
||||
if (!strlen(File::extension($fileName))) {
|
||||
$fileName .= '.'.static::$defaultExtension;
|
||||
}
|
||||
|
||||
$fullPath = static::getFilePath($theme, $fileName);
|
||||
|
||||
if (!File::isFile($fullPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (($content = @File::get($fullPath)) === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$obj = new static($theme);
|
||||
$obj->fileName = $fileName;
|
||||
$obj->originalFileName = $fileName;
|
||||
$obj->mtime = File::lastModified($fullPath);
|
||||
$obj->content = $content;
|
||||
return $obj;
|
||||
return static::inTheme($theme)->find($fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum allowed path nesting level.
|
||||
* The default value is 2, meaning that files
|
||||
* can only exist in the root directory, or in a subdirectory.
|
||||
* @return mixed Returns the maximum nesting level or null if any level is allowed.
|
||||
* Loads the object from a cache.
|
||||
* This method is used by the CMS in the runtime. If the cache is not found, it is created.
|
||||
* @param $theme Specifies the theme the object belongs to.
|
||||
* @param string $fileName Specifies the file name, with the extension.
|
||||
* @return mixed Returns a CMS object instance or null if the object wasn't found.
|
||||
*/
|
||||
protected static function getMaxAllowedPathNesting()
|
||||
public static function loadCached($theme, $fileName)
|
||||
{
|
||||
return 2;
|
||||
return static::inTheme($theme)
|
||||
->remember(Config::get('cms.parsedPageCacheTTL', 1440))
|
||||
->find($fileName)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file content.
|
||||
* Returns the list of objects in the specified theme.
|
||||
* This method is used internally by the system.
|
||||
* @param \Cms\Classes\Theme $theme Specifies a parent theme.
|
||||
* @param boolean $skipCache Indicates if objects should be reloaded from the disk bypassing the cache.
|
||||
* @return array Returns an array of CMS objects.
|
||||
*/
|
||||
public static function listInTheme($theme, $skipCache = false)
|
||||
{
|
||||
return static::inTheme($theme)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the theme datasource for the model.
|
||||
* @param \Cms\Classes\Theme $theme Specifies a parent theme.
|
||||
* @return $this
|
||||
*/
|
||||
public static function inTheme($theme)
|
||||
{
|
||||
if (is_string($theme)) {
|
||||
$theme = Theme::load($theme);
|
||||
}
|
||||
|
||||
return static::on($theme->getDirName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the object to the theme.
|
||||
*
|
||||
* @param array $options
|
||||
* @return bool
|
||||
*/
|
||||
public function save(array $options = null)
|
||||
{
|
||||
try {
|
||||
parent::save($options);
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
$this->throwHalcyonSaveException($ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path to the template file corresponding to this object.
|
||||
* @return string
|
||||
*/
|
||||
public function getContent()
|
||||
public function getFilePath($fileName = null)
|
||||
{
|
||||
return $this->content;
|
||||
if ($fileName === null) {
|
||||
$fileName = $this->fileName;
|
||||
}
|
||||
|
||||
return $this->getTheme()->getBasePath().'/'.$this->getObjectTypeDirName().'/'.$fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -240,383 +146,69 @@ class CmsObject extends Extendable implements ArrayAccess, CmsObjectContract
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the object file name.
|
||||
* @param string $fileName Specifies the file name.
|
||||
* @return \Cms\Classes\CmsObject Returns the object instance.
|
||||
*/
|
||||
public function setFileName($fileName)
|
||||
{
|
||||
$fileName = trim($fileName);
|
||||
|
||||
if (!strlen($fileName)) {
|
||||
throw new ValidationException(['fileName' =>
|
||||
Lang::get('cms::lang.cms_object.file_name_required', [
|
||||
'allowed' => implode(', ', static::$allowedExtensions),
|
||||
'invalid' => pathinfo($fileName, PATHINFO_EXTENSION)
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
if (!FileHelper::validateExtension($fileName, static::$allowedExtensions)) {
|
||||
throw new ValidationException(['fileName' =>
|
||||
Lang::get('cms::lang.cms_object.invalid_file_extension', [
|
||||
'allowed' => implode(', ', static::$allowedExtensions),
|
||||
'invalid' => pathinfo($fileName, PATHINFO_EXTENSION)
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
if (!FileHelper::validatePath($fileName, static::getMaxAllowedPathNesting())) {
|
||||
throw new ValidationException([
|
||||
'fileName' => Lang::get('cms::lang.cms_object.invalid_file', ['name'=>$fileName])
|
||||
]);
|
||||
}
|
||||
|
||||
if (!strlen(File::extension($fileName))) {
|
||||
$fileName .= '.htm';
|
||||
}
|
||||
|
||||
$this->fileName = $fileName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path to the template file corresponding to this object.
|
||||
* Returns the file content.
|
||||
* @return string
|
||||
*/
|
||||
public function getFullPath()
|
||||
public function getContent()
|
||||
{
|
||||
return static::getFilePath($this->theme, $this->fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the object was loaded from the cache.
|
||||
* This method is used by the CMS internally.
|
||||
* @return boolean
|
||||
*/
|
||||
public function isLoadedFromCache()
|
||||
{
|
||||
return $this->loadedFromCache;
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Twig content string.
|
||||
* @return string
|
||||
*/
|
||||
public function getTwigContent()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the object attributes.
|
||||
* @param array $attributes A list of attributes to set.
|
||||
*/
|
||||
public function fill(array $attributes)
|
||||
{
|
||||
foreach ($attributes as $key => $value) {
|
||||
if (!in_array($key, static::$fillable)) {
|
||||
throw new ApplicationException(Lang::get(
|
||||
'cms::lang.cms_object.invalid_property',
|
||||
['name' => $key]
|
||||
));
|
||||
}
|
||||
|
||||
$methodName = 'set'.ucfirst($key);
|
||||
if (method_exists($this, $methodName)) {
|
||||
$this->$methodName($value);
|
||||
}
|
||||
else {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the object to the disk.
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$fullPath = static::getFilePath($this->theme, $this->fileName);
|
||||
|
||||
if (File::isFile($fullPath) && $this->originalFileName !== $this->fileName) {
|
||||
throw new ApplicationException(Lang::get(
|
||||
'cms::lang.cms_object.file_already_exists',
|
||||
['name'=>$this->fileName]
|
||||
));
|
||||
}
|
||||
|
||||
$dirPath = rtrim(static::getFilePath($this->theme, ''), '/');
|
||||
if (!file_exists($dirPath) || !is_dir($dirPath)) {
|
||||
if (!File::makeDirectory($dirPath, 0777, true, true)) {
|
||||
throw new ApplicationException(Lang::get(
|
||||
'cms::lang.cms_object.error_creating_directory',
|
||||
['name'=>$dirPath]
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (($pos = strpos($this->fileName, '/')) !== false) {
|
||||
$dirPath = static::getFilePath($this->theme, dirname($this->fileName));
|
||||
|
||||
if (!is_dir($dirPath) && !File::makeDirectory($dirPath, 0777, true, true)) {
|
||||
throw new ApplicationException(Lang::get(
|
||||
'cms::lang.cms_object.error_creating_directory',
|
||||
['name'=>$dirPath]
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$newFullPath = $fullPath;
|
||||
if (@File::put($fullPath, $this->content) === false) {
|
||||
throw new ApplicationException(Lang::get(
|
||||
'cms::lang.cms_object.error_saving',
|
||||
['name'=>$this->fileName]
|
||||
));
|
||||
}
|
||||
|
||||
if (strlen($this->originalFileName) && $this->originalFileName !== $this->fileName) {
|
||||
$fullPath = static::getFilePath($this->theme, $this->originalFileName);
|
||||
|
||||
if (File::isFile($fullPath)) {
|
||||
@unlink($fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
clearstatcache();
|
||||
|
||||
$this->mtime = @File::lastModified($newFullPath);
|
||||
$this->originalFileName = $this->fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the object from the disk.
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$fullPath = static::getFilePath($this->theme, $this->fileName);
|
||||
if (File::isFile($fullPath) && !is_dir($fullPath) && !@unlink($fullPath)) {
|
||||
throw new ApplicationException(Lang::get('cms::lang.cms_object.error_deleting', ['name'=>$this->fileName]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the internal request-level object cache.
|
||||
*/
|
||||
public static function clearInternalCache()
|
||||
{
|
||||
ObjectMemoryCache::$cache = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of objects in the specified theme.
|
||||
* This method is used internally by the system.
|
||||
* @param \Cms\Classes\Theme $theme Specifies a parent theme.
|
||||
* @param boolean $skipCache Indicates if objects should be reloaded from the disk bypassing the cache.
|
||||
* @return array Returns an array of CMS objects.
|
||||
*/
|
||||
public static function listInTheme($theme, $skipCache = false)
|
||||
{
|
||||
if (!$theme) {
|
||||
throw new ApplicationException(Lang::get('cms::lang.theme.active.not_set'));
|
||||
}
|
||||
|
||||
$dirPath = $theme->getPath().'/'.static::getObjectTypeDirName();
|
||||
$result = [];
|
||||
|
||||
if (!File::isDirectory($dirPath)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dirPath));
|
||||
$it->setMaxDepth(1); // Support only a single level of subdirectories
|
||||
$it->rewind();
|
||||
|
||||
while ($it->valid()) {
|
||||
if ($it->isFile() && in_array($it->getExtension(), static::$allowedExtensions)) {
|
||||
$filePath = $it->getBasename();
|
||||
if ($it->getDepth() > 0) {
|
||||
$filePath = basename($it->getPath()).'/'.$filePath;
|
||||
}
|
||||
|
||||
$page = $skipCache ? static::load($theme, $filePath) : static::loadCached($theme, $filePath);
|
||||
$result[] = $page;
|
||||
}
|
||||
|
||||
$it->next();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute file path.
|
||||
* @param $theme Specifies a theme the file belongs to.
|
||||
* @param string$fileName Specifies the file name to return the path to.
|
||||
* @return string
|
||||
*/
|
||||
protected static function getFilePath($theme, $fileName)
|
||||
{
|
||||
return $theme->getPath().'/'.static::getObjectTypeDirName().'/'.$fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the getter functionality.
|
||||
* @param string $name
|
||||
* @return null|string
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
$methodName = 'get'.ucfirst($name);
|
||||
if (method_exists($this, $methodName)) {
|
||||
return $this->$methodName();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an attribute exists on the object.
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset($key)
|
||||
{
|
||||
$methodName = 'get'.ucfirst($key);
|
||||
if (method_exists($this, $methodName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an attribute on the object.
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function __set($key, $value)
|
||||
{
|
||||
$methodName = 'set'.ucfirst($key);
|
||||
if (method_exists($this, $methodName)) {
|
||||
$this->$methodName($value);
|
||||
}
|
||||
// Do not allow setting protected properties
|
||||
elseif (!property_exists($this, $key)) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given attribute exists.
|
||||
* @param mixed $offset
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return isset($this->$offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value for a given offset.
|
||||
* @param mixed $offset
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->$offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value for a given offset.
|
||||
* @param mixed $offset
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
$this->$offset = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the value for a given offset.
|
||||
* @param mixed $offset
|
||||
* @return void
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
unset($this->$offset);
|
||||
}
|
||||
|
||||
//
|
||||
// Queries
|
||||
// Internals
|
||||
//
|
||||
|
||||
/**
|
||||
* Get a new query builder for the object
|
||||
* @return CmsObjectQuery
|
||||
* Converts an exception type thrown by Halcyon to a native CMS exception.
|
||||
* @param Exception $ex
|
||||
*/
|
||||
public function newQuery()
|
||||
protected function throwHalcyonSaveException(Exception $ex)
|
||||
{
|
||||
$query = new CmsObjectQuery($this, $this->theme);
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dynamic method calls into the method.
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
// If this object is populated with a theme, then a query
|
||||
// cannot be performed on it to reduce overhead on populated objects.
|
||||
if (!$this->theme) {
|
||||
$query = $this->newQuery();
|
||||
return call_user_func_array(array($query, $method), $parameters);
|
||||
if ($ex instanceof \October\Rain\Halcyon\Exception\MissingFileNameException) {
|
||||
throw new ValidationException([
|
||||
'fileName' => Lang::get('cms::lang.cms_object.file_name_required')
|
||||
]);
|
||||
}
|
||||
elseif ($ex instanceof \October\Rain\Halcyon\Exception\InvalidExtensionException) {
|
||||
throw new ValidationException(['fileName' =>
|
||||
Lang::get('cms::lang.cms_object.invalid_file_extension', [
|
||||
'allowed' => implode(', ', $ex->getAllowedExtensions()),
|
||||
'invalid' => $ex->getInvalidExtension()
|
||||
])
|
||||
]);
|
||||
}
|
||||
elseif ($ex instanceof \October\Rain\Halcyon\Exception\InvalidFileNameException) {
|
||||
throw new ValidationException([
|
||||
'fileName' => Lang::get('cms::lang.cms_object.invalid_file', ['name'=>$ex->getInvalidFileName()])
|
||||
]);
|
||||
}
|
||||
elseif ($ex instanceof \October\Rain\Halcyon\Exception\FileExistsException) {
|
||||
throw new ApplicationException(
|
||||
Lang::get('cms::lang.cms_object.file_already_exists', ['name' => $ex->getInvalidPath()])
|
||||
);
|
||||
}
|
||||
elseif ($ex instanceof \October\Rain\Halcyon\Exception\CreateDirectoryException) {
|
||||
throw new ApplicationException(
|
||||
Lang::get('cms::lang.cms_object.error_creating_directory', ['name' => $ex->getInvalidPath()])
|
||||
);
|
||||
}
|
||||
elseif ($ex instanceof \October\Rain\Halcyon\Exception\CreateFileException) {
|
||||
throw new ApplicationException(
|
||||
Lang::get('cms::lang.cms_object.error_saving', ['name' => $ex->getInvalidPath()])
|
||||
);
|
||||
}
|
||||
else {
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
return parent::__call($method, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dynamic static method calls into the method.
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public static function __callStatic($method, $parameters)
|
||||
{
|
||||
$instance = new static;
|
||||
return call_user_func_array([$instance, $method], $parameters);
|
||||
}
|
||||
|
||||
//
|
||||
// Overrides
|
||||
//
|
||||
|
||||
/**
|
||||
* Initializes the object properties from the cached data.
|
||||
* @param array $cached The cached data array.
|
||||
*/
|
||||
protected function initFromCache($cached)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a cache item.
|
||||
* @param array &$item The cached item array.
|
||||
*/
|
||||
protected function initCacheItem(&$item)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory name corresponding to the object type.
|
||||
* For pages the directory name is "pages", for layouts - "layouts", etc.
|
||||
* @return string
|
||||
*/
|
||||
public static function getObjectTypeDirName()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ class CodeParser
|
|||
public function __construct(CmsCompoundObject $object)
|
||||
{
|
||||
$this->object = $object;
|
||||
$this->filePath = $object->getFullPath();
|
||||
$this->filePath = $object->getFilePath();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -226,12 +226,4 @@ class ComponentPartial extends Extendable implements CmsObjectContract
|
|||
|
||||
return $path;
|
||||
}
|
||||
|
||||
// @deprecated This method should be removed globally
|
||||
// remove if year >= 2016
|
||||
public function getFullPath()
|
||||
{
|
||||
return $this->getFilePath();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,23 +11,21 @@ use Markdown;
|
|||
*/
|
||||
class Content extends CmsCompoundObject
|
||||
{
|
||||
protected static $allowedExtensions = ['htm', 'txt', 'md'];
|
||||
/**
|
||||
* @var string The container name associated with the model, eg: pages.
|
||||
*/
|
||||
protected $dirName = 'content';
|
||||
|
||||
/**
|
||||
* @var array Allowable file extensions.
|
||||
*/
|
||||
protected $allowedExtensions = ['htm', 'txt', 'md'];
|
||||
|
||||
/**
|
||||
* @var string Contains the parsed markup.
|
||||
*/
|
||||
public $parsedMarkup = null;
|
||||
|
||||
/**
|
||||
* Returns the directory name corresponding to the object type.
|
||||
* For pages the directory name is "pages", for layouts - "layouts", etc.
|
||||
* @return string
|
||||
*/
|
||||
public static function getObjectTypeDirName()
|
||||
{
|
||||
return 'content';
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the object from a file.
|
||||
* @param \Cms\Classes\Theme $theme Specifies the theme the object belongs to.
|
||||
|
|
|
|||
|
|
@ -372,7 +372,7 @@ class Controller
|
|||
*/
|
||||
CmsException::mask($this->page, 400);
|
||||
$this->loader->setObject($this->page);
|
||||
$template = $this->twig->loadTemplate($this->page->getFullPath());
|
||||
$template = $this->twig->loadTemplate($this->page->getFilePath());
|
||||
$this->pageContents = $template->render($this->vars);
|
||||
CmsException::unmask();
|
||||
}
|
||||
|
|
@ -382,7 +382,7 @@ class Controller
|
|||
*/
|
||||
CmsException::mask($this->layout, 400);
|
||||
$this->loader->setObject($this->layout);
|
||||
$template = $this->twig->loadTemplate($this->layout->getFullPath());
|
||||
$template = $this->twig->loadTemplate($this->layout->getFilePath());
|
||||
$result = $template->render($this->vars);
|
||||
CmsException::unmask();
|
||||
|
||||
|
|
@ -901,7 +901,7 @@ class Controller
|
|||
*/
|
||||
CmsException::mask($partial, 400);
|
||||
$this->loader->setObject($partial);
|
||||
$template = $this->twig->loadTemplate($partial->getFullPath());
|
||||
$template = $this->twig->loadTemplate($partial->getFilePath());
|
||||
$result = $template->render(array_merge($this->vars, $parameters));
|
||||
CmsException::unmask();
|
||||
|
||||
|
|
|
|||
|
|
@ -8,22 +8,20 @@
|
|||
*/
|
||||
class Layout extends CmsCompoundObject
|
||||
{
|
||||
/**
|
||||
* Fallback layout name.
|
||||
*/
|
||||
const FALLBACK_FILE_NAME = 'fallback';
|
||||
|
||||
/**
|
||||
* @var string The container name associated with the model, eg: pages.
|
||||
*/
|
||||
protected $dirName = 'layouts';
|
||||
|
||||
protected function parseSettings()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory name corresponding to the object type.
|
||||
* For pages the directory name is "pages", for layouts - "layouts", etc.
|
||||
* @return string
|
||||
*/
|
||||
public static function getObjectTypeDirName()
|
||||
{
|
||||
return 'layouts';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the fallback layout.
|
||||
* @param \Cms\ClassesTheme $theme Specifies a theme the file belongs to.
|
||||
|
|
@ -31,7 +29,7 @@ class Layout extends CmsCompoundObject
|
|||
*/
|
||||
public static function initFallback($theme)
|
||||
{
|
||||
$obj = new self($theme);
|
||||
$obj = self::inTheme($theme);
|
||||
$obj->markup = '{% page %}';
|
||||
$obj->fileName = self::FALLBACK_FILE_NAME;
|
||||
return $obj;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,25 @@ use ApplicationException;
|
|||
*/
|
||||
class Page extends CmsCompoundObject
|
||||
{
|
||||
/**
|
||||
* @var string The container name associated with the model, eg: pages.
|
||||
*/
|
||||
protected $dirName = 'pages';
|
||||
|
||||
/**
|
||||
* @var array The attributes that are mass assignable.
|
||||
*/
|
||||
protected $fillable = [
|
||||
'url',
|
||||
'title',
|
||||
'description',
|
||||
'meta_title',
|
||||
'meta_description',
|
||||
'markup',
|
||||
'settings',
|
||||
'code'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array The API bag allows the API handler code to bind arbitrary
|
||||
* data to the page object.
|
||||
|
|
@ -28,9 +47,9 @@ class Page extends CmsCompoundObject
|
|||
* 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.
|
||||
*/
|
||||
public function __construct(Theme $theme = null)
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
parent::__construct($theme);
|
||||
parent::__construct($attributes);
|
||||
|
||||
$this->settingsValidationMessages = [
|
||||
'url.regex' => Lang::get('cms::lang.page.invalid_url')
|
||||
|
|
@ -41,16 +60,6 @@ class Page extends CmsCompoundObject
|
|||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory name corresponding to the object type.
|
||||
* For pages the directory name is "pages", for layouts - "layouts", etc.
|
||||
* @return string
|
||||
*/
|
||||
public static function getObjectTypeDirName()
|
||||
{
|
||||
return 'pages';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns name of a PHP class to us a parent for the PHP class created for the object's PHP section.
|
||||
* @return mixed Returns the class name or null.
|
||||
|
|
|
|||
|
|
@ -9,14 +9,9 @@
|
|||
class Partial extends CmsCompoundObject
|
||||
{
|
||||
/**
|
||||
* Returns the directory name corresponding to the object type.
|
||||
* For pages the directory name is "pages", for layouts - "layouts", etc.
|
||||
* @return string
|
||||
* @var string The container name associated with the model, eg: pages.
|
||||
*/
|
||||
public static function getObjectTypeDirName()
|
||||
{
|
||||
return 'partials';
|
||||
}
|
||||
protected $dirName = 'partials';
|
||||
|
||||
/**
|
||||
* Returns name of a PHP class to us a parent for the PHP class created for the object's PHP section.
|
||||
|
|
|
|||
|
|
@ -27,7 +27,25 @@ interface CmsObject
|
|||
* @param string $fileName
|
||||
* @return string
|
||||
*/
|
||||
// public static function getFilePath($fileName = null);
|
||||
public function getFilePath($fileName = null);
|
||||
|
||||
/**
|
||||
* Returns the file name.
|
||||
* @return string
|
||||
*/
|
||||
public function getFileName();
|
||||
|
||||
/**
|
||||
* Returns the file name without the extension.
|
||||
* @return string
|
||||
*/
|
||||
public function getBaseFileName();
|
||||
|
||||
/**
|
||||
* Returns the file content.
|
||||
* @return string
|
||||
*/
|
||||
public function getContent();
|
||||
|
||||
/**
|
||||
* Returns the Twig content string.
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class Loader implements Twig_LoaderInterface
|
|||
*/
|
||||
public function getCacheKey($name)
|
||||
{
|
||||
return $this->obj->getFullPath();
|
||||
return $this->obj->getFilePath();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue