mirror of
https://github.com/wintercms/winter.git
synced 2024-06-28 05:33:29 +02:00
473 lines
13 KiB
PHP
473 lines
13 KiB
PHP
<?php namespace Cms\Classes;
|
|
|
|
use App;
|
|
use Url;
|
|
use File;
|
|
use Yaml;
|
|
use Lang;
|
|
use Cache;
|
|
use Event;
|
|
use Config;
|
|
use Cms\Models\ThemeData;
|
|
use System\Models\Parameter;
|
|
use October\Rain\Halcyon\Datasource\FileDatasource;
|
|
use ApplicationException;
|
|
use SystemException;
|
|
use DirectoryIterator;
|
|
use Exception;
|
|
|
|
/**
|
|
* This class represents the CMS theme.
|
|
* CMS theme is a directory that contains all CMS objects - pages, layouts, partials and asset files..
|
|
* The theme parameters are specified in the theme.ini file in the theme root directory.
|
|
*
|
|
* @package october\cms
|
|
* @author Alexey Bobkov, Samuel Georges
|
|
*/
|
|
class Theme
|
|
{
|
|
/**
|
|
* @var string Specifies the theme directory name.
|
|
*/
|
|
protected $dirName;
|
|
|
|
/**
|
|
* @var mixed Keeps the cached configuration file values.
|
|
*/
|
|
protected $configCache = null;
|
|
|
|
/**
|
|
* @var mixed Active theme cache in memory
|
|
*/
|
|
protected static $activeThemeCache = false;
|
|
|
|
/**
|
|
* @var mixed Edit theme cache in memory
|
|
*/
|
|
protected static $editThemeCache = false;
|
|
|
|
const ACTIVE_KEY = 'cms::theme.active';
|
|
const EDIT_KEY = 'cms::theme.edit';
|
|
|
|
/**
|
|
* Loads the theme.
|
|
* @return self
|
|
*/
|
|
public static function load($dirName)
|
|
{
|
|
$theme = new static;
|
|
$theme->setDirName($dirName);
|
|
$theme->registerHalyconDatasource();
|
|
|
|
return $theme;
|
|
}
|
|
|
|
/**
|
|
* Returns the absolute theme path.
|
|
* @param string $dirName Optional theme directory. Defaults to $this->getDirName()
|
|
* @return string
|
|
*/
|
|
public function getPath($dirName = null)
|
|
{
|
|
if (!$dirName) {
|
|
$dirName = $this->getDirName();
|
|
}
|
|
|
|
return themes_path().'/'.$dirName;
|
|
}
|
|
|
|
/**
|
|
* Sets the theme directory name.
|
|
* @return void
|
|
*/
|
|
public function setDirName($dirName)
|
|
{
|
|
$this->dirName = $dirName;
|
|
}
|
|
|
|
/**
|
|
* Returns the theme directory name.
|
|
* @return string
|
|
*/
|
|
public function getDirName()
|
|
{
|
|
return $this->dirName;
|
|
}
|
|
|
|
/**
|
|
* Helper for {{ theme.id }} twig vars
|
|
* Returns a unique string for this theme.
|
|
* @return string
|
|
*/
|
|
public function getId()
|
|
{
|
|
return snake_case(str_replace('/', '-', $this->getDirName()));
|
|
}
|
|
|
|
/**
|
|
* Determines if a theme with given directory name exists
|
|
* @param string $dirName The theme directory
|
|
* @return bool
|
|
*/
|
|
public static function exists($dirName)
|
|
{
|
|
$theme = static::load($dirName);
|
|
$path = $theme->getPath();
|
|
|
|
return File::isDirectory($path);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of pages in the theme.
|
|
* This method is used internally in the routing process and in the back-end UI.
|
|
* @param boolean $skipCache Indicates if the pages should be reloaded from the disk bypassing the cache.
|
|
* @return array Returns an array of \Cms\Classes\Page objects.
|
|
*/
|
|
public function listPages($skipCache = false)
|
|
{
|
|
return Page::listInTheme($this, $skipCache);
|
|
}
|
|
|
|
/**
|
|
* Returns true if this theme is the chosen active theme.
|
|
*/
|
|
public function isActiveTheme()
|
|
{
|
|
$activeTheme = self::getActiveTheme();
|
|
|
|
return $activeTheme && $activeTheme->getDirName() == $this->getDirName();
|
|
}
|
|
|
|
/**
|
|
* Returns the active theme code.
|
|
* By default the active theme is loaded from the cms.activeTheme parameter,
|
|
* but this behavior can be overridden by the cms.theme.getActiveTheme event listener.
|
|
* @return string
|
|
* If the theme doesn't exist, returns null.
|
|
*/
|
|
public static function getActiveThemeCode()
|
|
{
|
|
$activeTheme = Config::get('cms.activeTheme');
|
|
|
|
if (App::hasDatabase()) {
|
|
try {
|
|
try {
|
|
$dbResult = Cache::remember(self::ACTIVE_KEY, 1440, function () {
|
|
return Parameter::applyKey(self::ACTIVE_KEY)->value('value');
|
|
});
|
|
}
|
|
catch (Exception $ex) {
|
|
// Cache failed
|
|
$dbResult = Parameter::applyKey(self::ACTIVE_KEY)->value('value');
|
|
}
|
|
}
|
|
catch (Exception $ex) {
|
|
// Database failed
|
|
$dbResult = null;
|
|
}
|
|
|
|
if ($dbResult !== null && static::exists($dbResult)) {
|
|
$activeTheme = $dbResult;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @event cms.theme.getActiveTheme
|
|
* Overrides the active theme code.
|
|
*
|
|
* If a value is returned from this halting event, it will be used as the active
|
|
* theme code. Example usage:
|
|
*
|
|
* Event::listen('cms.theme.getActiveTheme', function() { return 'mytheme'; });
|
|
*
|
|
*/
|
|
$apiResult = Event::fire('cms.theme.getActiveTheme', [], true);
|
|
if ($apiResult !== null) {
|
|
$activeTheme = $apiResult;
|
|
}
|
|
|
|
if (!strlen($activeTheme)) {
|
|
throw new SystemException(Lang::get('cms::lang.theme.active.not_set'));
|
|
}
|
|
|
|
return $activeTheme;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the active theme object.
|
|
* @return \Cms\Classes\Theme Returns the loaded theme object.
|
|
* If the theme doesn't exist, returns null.
|
|
*/
|
|
public static function getActiveTheme()
|
|
{
|
|
if (self::$activeThemeCache !== false) {
|
|
return self::$activeThemeCache;
|
|
}
|
|
|
|
$theme = static::load(static::getActiveThemeCode());
|
|
|
|
if (!File::isDirectory($theme->getPath())) {
|
|
return self::$activeThemeCache = null;
|
|
}
|
|
|
|
return self::$activeThemeCache = $theme;
|
|
}
|
|
|
|
/**
|
|
* Sets the active theme.
|
|
* The active theme code is stored in the database and overrides the configuration cms.activeTheme parameter.
|
|
* @param string $code Specifies the active theme code.
|
|
*/
|
|
public static function setActiveTheme($code)
|
|
{
|
|
self::resetCache();
|
|
|
|
Parameter::set(self::ACTIVE_KEY, $code);
|
|
|
|
Event::fire('cms.theme.setActiveTheme', compact('code'));
|
|
}
|
|
|
|
/**
|
|
* Returns the edit theme code.
|
|
* By default the edit theme is loaded from the cms.editTheme parameter,
|
|
* but this behavior can be overridden by the cms.theme.getEditTheme event listeners.
|
|
* If the edit theme is not defined in the configuration file, the active theme
|
|
* is returned.
|
|
* @return string
|
|
*/
|
|
public static function getEditThemeCode()
|
|
{
|
|
$editTheme = Config::get('cms.editTheme');
|
|
if (!$editTheme) {
|
|
$editTheme = static::getActiveThemeCode();
|
|
}
|
|
|
|
$apiResult = Event::fire('cms.theme.getEditTheme', [], true);
|
|
if ($apiResult !== null) {
|
|
$editTheme = $apiResult;
|
|
}
|
|
|
|
if (!strlen($editTheme)) {
|
|
throw new SystemException(Lang::get('cms::lang.theme.edit.not_set'));
|
|
}
|
|
|
|
return $editTheme;
|
|
}
|
|
|
|
/**
|
|
* Returns the edit theme.
|
|
* @return \Cms\Classes\Theme Returns the loaded theme object.
|
|
*/
|
|
public static function getEditTheme()
|
|
{
|
|
if (self::$editThemeCache !== false) {
|
|
return self::$editThemeCache;
|
|
}
|
|
|
|
$theme = static::load(static::getEditThemeCode());
|
|
|
|
if (!File::isDirectory($theme->getPath())) {
|
|
return self::$editThemeCache = null;
|
|
}
|
|
|
|
return self::$editThemeCache = $theme;
|
|
}
|
|
|
|
/**
|
|
* Returns a list of all themes.
|
|
* @return array Returns an array of the Theme objects.
|
|
*/
|
|
public static function all()
|
|
{
|
|
$it = new DirectoryIterator(themes_path());
|
|
$it->rewind();
|
|
|
|
$result = [];
|
|
foreach ($it as $fileinfo) {
|
|
if (!$fileinfo->isDir() || $fileinfo->isDot()) {
|
|
continue;
|
|
}
|
|
|
|
$theme = static::load($fileinfo->getFilename());
|
|
|
|
$result[] = $theme;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Reads the theme.yaml file and returns the theme configuration values.
|
|
* @return array Returns the parsed configuration file values.
|
|
*/
|
|
public function getConfig()
|
|
{
|
|
if ($this->configCache !== null) {
|
|
return $this->configCache;
|
|
}
|
|
|
|
$path = $this->getPath().'/theme.yaml';
|
|
if (!File::exists($path)) {
|
|
return $this->configCache = [];
|
|
}
|
|
|
|
return $this->configCache = Yaml::parseFile($path);
|
|
}
|
|
|
|
/**
|
|
* Returns a value from the theme configuration file by its name.
|
|
* @param string $name Specifies the configuration parameter name.
|
|
* @param mixed $default Specifies the default value to return in case if the parameter
|
|
* doesn't exist in the configuration file.
|
|
* @return mixed Returns the parameter value or a default value
|
|
*/
|
|
public function getConfigValue($name, $default = null)
|
|
{
|
|
return array_get($this->getConfig(), $name, $default);
|
|
}
|
|
|
|
/**
|
|
* Returns an array value from the theme configuration file by its name.
|
|
* If the value is a string, it is treated as a YAML file and loaded.
|
|
* @param string $name Specifies the configuration parameter name.
|
|
* @return array
|
|
*/
|
|
public function getConfigArray($name)
|
|
{
|
|
$result = array_get($this->getConfig(), $name, []);
|
|
|
|
if (is_string($result)) {
|
|
$fileName = File::symbolizePath($result);
|
|
|
|
if (File::isLocalPath($fileName)) {
|
|
$path = $fileName;
|
|
}
|
|
else {
|
|
$path = $this->getPath().'/'.$result;
|
|
}
|
|
|
|
if (!File::exists($path)) {
|
|
throw new ApplicationException('Path does not exist: '.$path);
|
|
}
|
|
|
|
$result = Yaml::parseFile($path);
|
|
}
|
|
|
|
return (array) $result;
|
|
}
|
|
|
|
/**
|
|
* Writes to the theme.yaml file with the supplied array values.
|
|
* @param array $values Data to write
|
|
* @param array $overwrite If true, undefined values are removed.
|
|
* @return void
|
|
*/
|
|
public function writeConfig($values = [], $overwrite = false)
|
|
{
|
|
if (!$overwrite) {
|
|
$values = $values + (array) $this->getConfig();
|
|
}
|
|
|
|
$path = $this->getPath().'/theme.yaml';
|
|
if (!File::exists($path)) {
|
|
throw new ApplicationException('Path does not exist: '.$path);
|
|
}
|
|
|
|
$contents = Yaml::render($values);
|
|
File::put($path, $contents);
|
|
$this->configCache = $values;
|
|
}
|
|
|
|
/**
|
|
* Returns the theme preview image URL.
|
|
* If the image file doesn't exist returns the placeholder image URL.
|
|
* @return string Returns the image URL.
|
|
*/
|
|
public function getPreviewImageUrl()
|
|
{
|
|
$previewPath = $this->getConfigValue('previewImage', 'assets/images/theme-preview.png');
|
|
|
|
if (File::exists($this->getPath().'/'.$previewPath)) {
|
|
return Url::asset('themes/'.$this->getDirName().'/'.$previewPath);
|
|
}
|
|
|
|
return Url::asset('modules/cms/assets/images/default-theme-preview.png');
|
|
}
|
|
|
|
/**
|
|
* Resets any memory or cache involved with the active or edit theme.
|
|
* @return void
|
|
*/
|
|
public static function resetCache()
|
|
{
|
|
self::$activeThemeCache = false;
|
|
self::$editThemeCache = false;
|
|
|
|
Cache::forget(self::ACTIVE_KEY);
|
|
Cache::forget(self::EDIT_KEY);
|
|
}
|
|
|
|
/**
|
|
* Returns true if this theme has form fields that supply customization data.
|
|
* @return bool
|
|
*/
|
|
public function hasCustomData()
|
|
{
|
|
return $this->getConfigValue('form', false);
|
|
}
|
|
|
|
/**
|
|
* Returns data specific to this theme
|
|
* @return Cms\Models\ThemeData
|
|
*/
|
|
public function getCustomData()
|
|
{
|
|
return ThemeData::forTheme($this);
|
|
}
|
|
|
|
/**
|
|
* Ensures this theme is registered as a Halcyon them datasource.
|
|
* @return void
|
|
*/
|
|
public function registerHalyconDatasource()
|
|
{
|
|
$resolver = App::make('halcyon');
|
|
|
|
if (!$resolver->hasDatasource($this->dirName)) {
|
|
$datasource = new FileDatasource($this->getPath(), App::make('files'));
|
|
$resolver->addDatasource($this->dirName, $datasource);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements the getter functionality.
|
|
* @param string $name
|
|
* @return void
|
|
*/
|
|
public function __get($name)
|
|
{
|
|
if ($this->hasCustomData()) {
|
|
$theme = $this->getCustomData();
|
|
return $theme->{$name};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Determine if an attribute exists on the object.
|
|
* @param string $key
|
|
* @return void
|
|
*/
|
|
public function __isset($key)
|
|
{
|
|
if ($this->hasCustomData()) {
|
|
$theme = $this->getCustomData();
|
|
return $theme->offsetExists($key);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|