feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
- Base: wintercms/winter branch 1.2 (full framework) - Theme vivespos: Canvas 7 + Bootstrap 5 CDN, custom CSS - Layout: deferred GTM/GA4 tracking, JSON-LD SoftwareApplication - Partials: hero (offline-first), features, modes (offline/nube toggle), screenshots, pricing (3 planes), comparison, FAQ, CTA - Plugin VivesPOS.Site with ContactForm - Dockerfile: PHP 8.2 Apache, port 80, healthcheck - Added winter/wn-pages, blog, sitemap, seo plugins - Active theme set to vivespos
This commit is contained in:
105
modules/cms/models/MaintenanceSetting.php
Normal file
105
modules/cms/models/MaintenanceSetting.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php namespace Cms\Models;
|
||||
|
||||
use Model;
|
||||
use Cms\Classes\Page;
|
||||
use Cms\Classes\Theme;
|
||||
use Winter\Storm\Support\Arr;
|
||||
use Symfony\Component\HttpFoundation\IpUtils;
|
||||
use ApplicationException;
|
||||
|
||||
/**
|
||||
* Maintenance mode settings
|
||||
*
|
||||
* @package winter\wn-cms-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class MaintenanceSetting extends Model
|
||||
{
|
||||
use \Winter\Storm\Database\Traits\Validation;
|
||||
|
||||
/**
|
||||
* @var array Behaviors implemented by this model.
|
||||
*/
|
||||
public $implement = [
|
||||
\System\Behaviors\SettingsModel::class
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string Unique code
|
||||
*/
|
||||
public $settingsCode = 'cms_maintenance_settings';
|
||||
|
||||
/**
|
||||
* @var mixed Settings form field defitions
|
||||
*/
|
||||
public $settingsFields = 'fields.yaml';
|
||||
|
||||
/**
|
||||
* Validation rules
|
||||
*/
|
||||
public $rules = [];
|
||||
|
||||
/**
|
||||
* Initialize the seed data for this model. This only executes when the
|
||||
* model is first created or reset to default.
|
||||
* @return void
|
||||
*/
|
||||
public function initSettingsData()
|
||||
{
|
||||
$this->is_enabled = false;
|
||||
}
|
||||
|
||||
public function getCmsPageOptions()
|
||||
{
|
||||
if (!$theme = Theme::getEditTheme()) {
|
||||
throw new ApplicationException('Unable to find the active theme.');
|
||||
}
|
||||
|
||||
return Page::listInTheme($theme)->lists('fileName', 'fileName');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure each theme has its own CMS page, store it inside a mapping array.
|
||||
* @return void
|
||||
*/
|
||||
public function beforeValidate()
|
||||
{
|
||||
if (!$theme = Theme::getEditTheme()) {
|
||||
throw new ApplicationException('Unable to find the active theme.');
|
||||
}
|
||||
|
||||
$themeMap = $this->getSettingsValue('theme_map', []);
|
||||
$themeMap[$theme->getDirName()] = $this->getSettingsValue('cms_page');
|
||||
$this->setSettingsValue('theme_map', $themeMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the CMS page found in the mapping array, or disable the
|
||||
* maintenance mode.
|
||||
* @return void
|
||||
*/
|
||||
public function afterFetch()
|
||||
{
|
||||
if (
|
||||
($theme = Theme::getEditTheme())
|
||||
&& ($themeMap = array_get($this->value, 'theme_map'))
|
||||
&& ($cmsPage = array_get($themeMap, $theme->getDirName()))
|
||||
) {
|
||||
$this->cms_page = $cmsPage;
|
||||
}
|
||||
else {
|
||||
$this->is_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the provided IP is in the allowed IP list.
|
||||
*
|
||||
* @param string $ip
|
||||
* @return bool
|
||||
*/
|
||||
public static function isAllowedIp(string $ip): bool
|
||||
{
|
||||
return IpUtils::checkIp($ip, Arr::pluck(static::get('allowed_ips', []) ?? [], 'ip'));
|
||||
}
|
||||
}
|
||||
309
modules/cms/models/ThemeData.php
Normal file
309
modules/cms/models/ThemeData.php
Normal file
@@ -0,0 +1,309 @@
|
||||
<?php
|
||||
|
||||
namespace Cms\Models;
|
||||
|
||||
use Cms\Classes\Theme as CmsTheme;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
use System\Classes\CombineAssets;
|
||||
use System\Models\File;
|
||||
use Winter\Storm\Database\Model;
|
||||
|
||||
/**
|
||||
* Customization data used by a theme
|
||||
*
|
||||
* @package winter\wn-cms-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class ThemeData extends Model
|
||||
{
|
||||
use \Winter\Storm\Database\Traits\Validation;
|
||||
|
||||
/**
|
||||
* @var string The database table used by the model.
|
||||
*/
|
||||
public $table = 'cms_theme_data';
|
||||
|
||||
/**
|
||||
* @var array Guarded fields
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* @var array Fillable fields
|
||||
*/
|
||||
protected $fillable = [];
|
||||
|
||||
/**
|
||||
* @var array List of attribute names which are json encoded and decoded from the database.
|
||||
*/
|
||||
protected $jsonable = ['data'];
|
||||
|
||||
/**
|
||||
* @var array The rules to be applied to the data.
|
||||
*/
|
||||
public $rules = [];
|
||||
|
||||
/**
|
||||
* @var array Relations
|
||||
*/
|
||||
public $attachOne = [];
|
||||
|
||||
/**
|
||||
* @var ThemeData Cached array of objects
|
||||
*/
|
||||
protected static $instances = [];
|
||||
|
||||
/**
|
||||
* @var int The number of minutes the theme data is cached for.
|
||||
*/
|
||||
protected static $cacheTtl = 1440;
|
||||
|
||||
/**
|
||||
* Before saving the model, strip dynamic attributes applied from config.
|
||||
* @return void
|
||||
*/
|
||||
public function beforeSave()
|
||||
{
|
||||
/*
|
||||
* Dynamic attributes are stored in the jsonable attribute 'data'.
|
||||
*/
|
||||
$staticAttributes = ['id', 'theme', 'data', 'created_at', 'updated_at'];
|
||||
$dynamicAttributes = array_except($this->getAttributes(), $staticAttributes);
|
||||
|
||||
$this->data = $dynamicAttributes;
|
||||
$this->setRawAttributes(array_only($this->getAttributes(), $staticAttributes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear asset cache after saving to ensure `assetVar` form fields take
|
||||
* immediate effect.
|
||||
*/
|
||||
public function afterSave()
|
||||
{
|
||||
static::flushCache($this->theme);
|
||||
|
||||
try {
|
||||
CombineAssets::resetCache();
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache after deleting so that the record isn't served from the cache.
|
||||
*/
|
||||
public function afterDelete()
|
||||
{
|
||||
static::flushCache($this->theme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cache key used to store the data for the provided theme directory.
|
||||
*/
|
||||
public static function getCacheKey(string $dirName): string
|
||||
{
|
||||
return 'cms::theme.data.' . $dirName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes both the persistent and in memory cache of the provided theme directory's data.
|
||||
*/
|
||||
public static function flushCache(?string $dirName = null): void
|
||||
{
|
||||
if (is_null($dirName)) {
|
||||
self::$instances = [];
|
||||
return;
|
||||
}
|
||||
|
||||
Cache::forget(static::getCacheKey($dirName));
|
||||
unset(self::$instances[$dirName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cached version of this model, based on a Theme object.
|
||||
* @param $theme Cms\Classes\Theme
|
||||
* @return self
|
||||
*/
|
||||
public static function forTheme($theme)
|
||||
{
|
||||
$dirName = $theme->getDirName();
|
||||
if ($themeData = array_get(self::$instances, $dirName)) {
|
||||
return $themeData;
|
||||
}
|
||||
|
||||
try {
|
||||
// The record is cached rather than queried on every request; it is invalidated
|
||||
// by afterSave() / afterDelete(), which also covers the initial creation below.
|
||||
$themeData = self::where('theme', $dirName)
|
||||
->remember(self::$cacheTtl, self::getCacheKey($dirName))
|
||||
->first() ?: self::create(['theme' => $dirName]);
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
// Database failed
|
||||
$themeData = new self(['theme' => $dirName]);
|
||||
}
|
||||
|
||||
return self::$instances[$dirName] = $themeData;
|
||||
}
|
||||
|
||||
/**
|
||||
* After fetching the model, intiialize model relationships based
|
||||
* on form field definitions.
|
||||
* @return void
|
||||
*/
|
||||
public function afterFetch()
|
||||
{
|
||||
$data = (array) $this->data + $this->getDefaultValues();
|
||||
|
||||
foreach ($this->getFormFields() as $id => $field) {
|
||||
if (!isset($field['type'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Repeater and nested form fields store arrays and must be jsonable.
|
||||
*/
|
||||
if (in_array($field['type'], ['repeater', 'nestedform'])) {
|
||||
$this->jsonable[] = $id;
|
||||
} elseif ($field['type'] === 'fileupload') {
|
||||
if (array_get($field, 'multiple', false)) {
|
||||
$this->attachMany[$id] = File::class;
|
||||
} else {
|
||||
$this->attachOne[$id] = File::class;
|
||||
}
|
||||
unset($data[$id]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Fill this model with the jsonable attributes kept in 'data'.
|
||||
*/
|
||||
$this->setRawAttributes((array) $this->getAttributes() + $data, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Before model is validated, set the default values.
|
||||
* @return void
|
||||
*/
|
||||
public function beforeValidate()
|
||||
{
|
||||
if (!$this->exists) {
|
||||
$this->setDefaultValues();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates relationships for this model based on form field definitions.
|
||||
*/
|
||||
public function initFormFields()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default values on this model based on form field definitions.
|
||||
*/
|
||||
public function setDefaultValues()
|
||||
{
|
||||
foreach ($this->getDefaultValues() as $attribute => $value) {
|
||||
$this->{$attribute} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets default values for this model based on form field definitions.
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaultValues()
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this->getFormFields() as $attribute => $field) {
|
||||
if (($value = array_get($field, 'default')) === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[$attribute] = $value;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all fields defined for this model, based on form field definitions.
|
||||
* @return array
|
||||
*/
|
||||
public function getFormFields()
|
||||
{
|
||||
if (!$theme = CmsTheme::load($this->theme)) {
|
||||
throw new Exception(Lang::get('Unable to find theme with name :name', $this->theme));
|
||||
}
|
||||
|
||||
$config = $theme->getFormConfig();
|
||||
|
||||
return array_get($config, 'fields', []) +
|
||||
array_get($config, 'tabs.fields', []) +
|
||||
array_get($config, 'secondaryTabs.fields', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns variables that should be passed to the asset combiner.
|
||||
* @return array
|
||||
*/
|
||||
public function getAssetVariables()
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this->getFormFields() as $attribute => $field) {
|
||||
if (!$varName = array_get($field, 'assetVar')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[$varName] = $this->{$attribute};
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies asset variables to the combiner filters that support it.
|
||||
* @return void
|
||||
*/
|
||||
public static function applyAssetVariablesToCombinerFilters($filters)
|
||||
{
|
||||
$theme = CmsTheme::getActiveTheme();
|
||||
|
||||
if (!$theme) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$theme->hasCustomData()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$assetVars = $theme->getCustomData()->getAssetVariables();
|
||||
|
||||
foreach ($filters as $filter) {
|
||||
if (method_exists($filter, 'setPresets')) {
|
||||
$filter->setPresets($assetVars);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a cache key for the combiner, this allows variables to bust the cache.
|
||||
* @return string
|
||||
*/
|
||||
public static function getCombinerCacheKey()
|
||||
{
|
||||
$theme = CmsTheme::getActiveTheme();
|
||||
if (!$theme->hasCustomData()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$customData = $theme->getCustomData();
|
||||
|
||||
return (string) $customData->updated_at ?: '';
|
||||
}
|
||||
}
|
||||
155
modules/cms/models/ThemeExport.php
Normal file
155
modules/cms/models/ThemeExport.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php namespace Cms\Models;
|
||||
|
||||
use File;
|
||||
use Model;
|
||||
use Response;
|
||||
use ApplicationException;
|
||||
use Winter\Storm\Filesystem\Zip;
|
||||
use Cms\Classes\Theme as CmsTheme;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Theme export model
|
||||
*
|
||||
* @package winter\wn-cms-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class ThemeExport extends Model
|
||||
{
|
||||
use \Winter\Storm\Database\Traits\Validation;
|
||||
|
||||
/**
|
||||
* @var string The database table used by the model.
|
||||
*/
|
||||
public $table = 'cms_theme_data';
|
||||
|
||||
/**
|
||||
* @var array The rules to be applied to the data.
|
||||
*/
|
||||
public $rules = [];
|
||||
|
||||
/**
|
||||
* @var array Guarded fields
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* @var array Fillable fields
|
||||
*/
|
||||
protected $fillable = [];
|
||||
|
||||
/**
|
||||
* @var array Make the model's attributes public so behaviors can modify them.
|
||||
*/
|
||||
public $attributes = [
|
||||
'theme' => null,
|
||||
'themeName' => null,
|
||||
'dirName' => null,
|
||||
'folders' => [
|
||||
'assets' => true,
|
||||
'pages' => true,
|
||||
'layouts' => true,
|
||||
'partials' => true,
|
||||
'content' => true,
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* Import / Export model classes are helpers and are not to write to the database
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save(?array $options = null, $sessionKey = null)
|
||||
{
|
||||
throw new ApplicationException(sprintf("The % model is not intended to be saved, please use %s instead", get_class($this), 'ThemeData'));
|
||||
}
|
||||
|
||||
public function getFoldersOptions()
|
||||
{
|
||||
return [
|
||||
'assets' => 'Assets',
|
||||
'pages' => 'Pages',
|
||||
'layouts' => 'Layouts',
|
||||
'partials' => 'Partials',
|
||||
'content' => 'Content',
|
||||
];
|
||||
}
|
||||
|
||||
public function setThemeAttribute($theme)
|
||||
{
|
||||
if (!$theme instanceof CmsTheme) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->attributes['themeName'] = $theme->getConfigValue('name', $theme->getDirName());
|
||||
$this->attributes['dirName'] = $theme->getDirName();
|
||||
$this->attributes['theme'] = $theme;
|
||||
}
|
||||
|
||||
public function export($theme, $data = [])
|
||||
{
|
||||
$this->theme = $theme;
|
||||
$this->fill($data);
|
||||
|
||||
try {
|
||||
$themePath = $this->theme->getPath();
|
||||
$tempPath = temp_path() . '/'.uniqid('oc');
|
||||
$zipName = uniqid('oc');
|
||||
$zipPath = temp_path().'/'.$zipName;
|
||||
|
||||
if (!File::makeDirectory($tempPath)) {
|
||||
throw new ApplicationException('Unable to create directory '.$tempPath);
|
||||
}
|
||||
|
||||
if (!File::makeDirectory($metaPath = $tempPath . '/meta')) {
|
||||
throw new ApplicationException('Unable to create directory '.$metaPath);
|
||||
}
|
||||
|
||||
File::copy($themePath.'/theme.yaml', $tempPath.'/theme.yaml');
|
||||
File::copyDirectory($themePath.'/meta', $metaPath);
|
||||
|
||||
foreach ($this->folders as $folder) {
|
||||
if (!array_key_exists($folder, $this->getFoldersOptions())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
File::copyDirectory($themePath.'/'.$folder, $tempPath.'/'.$folder);
|
||||
}
|
||||
|
||||
Zip::make($zipPath, $tempPath);
|
||||
File::deleteDirectory($tempPath);
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
if (strlen($tempPath) && File::isDirectory($tempPath)) {
|
||||
File::deleteDirectory($tempPath);
|
||||
}
|
||||
|
||||
if (strlen($zipPath) && File::isFile($zipPath)) {
|
||||
File::delete($zipPath);
|
||||
}
|
||||
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
return $zipName;
|
||||
}
|
||||
|
||||
public static function download($name, $outputName = null)
|
||||
{
|
||||
if (!preg_match('/^oc[0-9a-z]*$/i', $name)) {
|
||||
throw new ApplicationException('File not found');
|
||||
}
|
||||
|
||||
$zipPath = temp_path() . '/' . $name;
|
||||
if (!file_exists($zipPath)) {
|
||||
throw new ApplicationException('File not found');
|
||||
}
|
||||
|
||||
$headers = Response::download($zipPath, $outputName)->headers->all();
|
||||
$result = Response::make(File::get($zipPath), 200, $headers);
|
||||
|
||||
@File::delete($zipPath);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
200
modules/cms/models/ThemeImport.php
Normal file
200
modules/cms/models/ThemeImport.php
Normal file
@@ -0,0 +1,200 @@
|
||||
<?php namespace Cms\Models;
|
||||
|
||||
use File;
|
||||
use Model;
|
||||
use ApplicationException;
|
||||
use Winter\Storm\Filesystem\Zip;
|
||||
use Cms\Classes\Theme as CmsTheme;
|
||||
use FilesystemIterator;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Theme import model
|
||||
*
|
||||
* @package winter\wn-cms-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class ThemeImport extends Model
|
||||
{
|
||||
use \Winter\Storm\Database\Traits\Validation;
|
||||
|
||||
/**
|
||||
* @var string The database table used by the model.
|
||||
*/
|
||||
public $table = 'cms_theme_data';
|
||||
|
||||
/**
|
||||
* @var array The rules to be applied to the data.
|
||||
*/
|
||||
public $rules = [];
|
||||
|
||||
/**
|
||||
* @var array Guarded fields
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* @var array Fillable fields
|
||||
*/
|
||||
protected $fillable = [];
|
||||
|
||||
public $attachOne = [
|
||||
'uploaded_file' => \System\Models\File::class
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array Make the model's attributes public so behaviors can modify them.
|
||||
*/
|
||||
public $attributes = [
|
||||
'theme' => null,
|
||||
'themeName' => null,
|
||||
'dirName' => null,
|
||||
'overwrite' => true,
|
||||
'folders' => [
|
||||
'assets' => true,
|
||||
'pages' => true,
|
||||
'layouts' => true,
|
||||
'partials' => true,
|
||||
'content' => true,
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* Import / Export model classes are helpers and are not to write to the database
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save(?array $options = null, $sessionKey = null)
|
||||
{
|
||||
throw new ApplicationException(sprintf("The % model is not intended to be saved, please use %s instead", get_class($this), 'ThemeData'));
|
||||
}
|
||||
|
||||
public function getFoldersOptions()
|
||||
{
|
||||
return [
|
||||
'assets' => 'Assets',
|
||||
'pages' => 'Pages',
|
||||
'layouts' => 'Layouts',
|
||||
'partials' => 'Partials',
|
||||
'content' => 'Content',
|
||||
];
|
||||
}
|
||||
|
||||
public function setThemeAttribute($theme)
|
||||
{
|
||||
if (!$theme instanceof CmsTheme) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->attributes['themeName'] = $theme->getConfigValue('name', $theme->getDirName());
|
||||
$this->attributes['dirName'] = $theme->getDirName();
|
||||
$this->attributes['theme'] = $theme;
|
||||
}
|
||||
|
||||
public function import($theme, $data = [], $sessionKey = null)
|
||||
{
|
||||
@set_time_limit(3600);
|
||||
|
||||
$this->theme = $theme;
|
||||
$this->fill($data);
|
||||
|
||||
try {
|
||||
$file = $this->uploaded_file()->withDeferred($sessionKey)->first();
|
||||
if (!$file) {
|
||||
throw new ApplicationException('There is no file attached to import!');
|
||||
}
|
||||
|
||||
$themePath = $this->theme->getPath();
|
||||
$tempPath = temp_path() . '/'.uniqid('oc');
|
||||
$zipName = uniqid('oc');
|
||||
$zipPath = temp_path().'/'.$zipName;
|
||||
|
||||
File::put($zipPath, $file->getContents());
|
||||
|
||||
if (!File::makeDirectory($tempPath)) {
|
||||
throw new ApplicationException('Unable to create directory '.$tempPath);
|
||||
}
|
||||
|
||||
Zip::extract($zipPath, $tempPath);
|
||||
|
||||
if (File::isDirectory($tempPath.'/meta')) {
|
||||
$this->copyDirectory($tempPath.'/meta', $themePath.'/meta');
|
||||
}
|
||||
|
||||
foreach ($this->folders as $folder) {
|
||||
if (!array_key_exists($folder, $this->getFoldersOptions())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->copyDirectory($tempPath.'/'.$folder, $themePath.'/'.$folder);
|
||||
}
|
||||
|
||||
File::deleteDirectory($tempPath);
|
||||
File::delete($zipPath);
|
||||
$file->delete();
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
if (!empty($tempPath) && File::isDirectory($tempPath)) {
|
||||
File::deleteDirectory($tempPath);
|
||||
}
|
||||
|
||||
if (!empty($zipPath) && File::isFile($zipPath)) {
|
||||
File::delete($zipPath);
|
||||
}
|
||||
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for copying directories that supports the ability
|
||||
* to not overwrite existing files. Inherited from File::copyDirectory
|
||||
*
|
||||
* @param string $directory
|
||||
* @param string $destination
|
||||
* @return bool
|
||||
*/
|
||||
protected function copyDirectory($directory, $destination)
|
||||
{
|
||||
// Preference is to overwrite existing files
|
||||
if ($this->overwrite) {
|
||||
return File::copyDirectory($directory, $destination);
|
||||
}
|
||||
|
||||
if (!File::isDirectory($directory)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$options = FilesystemIterator::SKIP_DOTS;
|
||||
|
||||
if (!File::isDirectory($destination)) {
|
||||
File::makeDirectory($destination, 0777, true);
|
||||
}
|
||||
|
||||
$items = new FilesystemIterator($directory, $options);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$target = $destination.'/'.$item->getBasename();
|
||||
|
||||
if ($item->isDir()) {
|
||||
$path = $item->getPathname();
|
||||
|
||||
if (!$this->copyDirectory($path, $target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Do not overwrite existing files
|
||||
if (File::isFile($target)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!File::copy($item->getPathname(), $target)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
129
modules/cms/models/ThemeLog.php
Normal file
129
modules/cms/models/ThemeLog.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php namespace Cms\Models;
|
||||
|
||||
use BackendAuth;
|
||||
use Cms\Classes\Theme;
|
||||
use Exception;
|
||||
use Model;
|
||||
use System\Models\LogSetting;
|
||||
use Winter\Storm\Halcyon\Model as HalcyonModel;
|
||||
|
||||
/**
|
||||
* Model for changes made to the theme
|
||||
*
|
||||
* @package winter\wn-cms-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class ThemeLog extends Model
|
||||
{
|
||||
const TYPE_CREATE = 'create';
|
||||
const TYPE_UPDATE = 'update';
|
||||
const TYPE_DELETE = 'delete';
|
||||
|
||||
/**
|
||||
* @var string The database table used by the model.
|
||||
*/
|
||||
protected $table = 'cms_theme_logs';
|
||||
|
||||
/**
|
||||
* @var array Relations
|
||||
*/
|
||||
public $belongsTo = [
|
||||
'user' => \Backend\Models\User::class
|
||||
];
|
||||
|
||||
protected $themeCache;
|
||||
|
||||
/**
|
||||
* Adds observers to the model for logging purposes.
|
||||
*/
|
||||
public static function bindEventsToModel(HalcyonModel $template)
|
||||
{
|
||||
$template->bindEvent('model.beforeDelete', function () use ($template) {
|
||||
self::add($template, self::TYPE_DELETE);
|
||||
});
|
||||
|
||||
$template->bindEvent('model.beforeSave', function () use ($template) {
|
||||
self::add($template, $template->exists ? self::TYPE_UPDATE : self::TYPE_CREATE);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a log record
|
||||
*/
|
||||
public static function add(HalcyonModel $template, ?string $type = null): ?self
|
||||
{
|
||||
if (!LogSetting::hasDatabaseTable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!LogSetting::get('log_theme')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$type) {
|
||||
$type = self::TYPE_UPDATE;
|
||||
}
|
||||
|
||||
$isDelete = $type === self::TYPE_DELETE;
|
||||
$dirName = $template->getObjectTypeDirName();
|
||||
$templateName = $template->fileName;
|
||||
$oldTemplateName = $template->getOriginal('fileName');
|
||||
$newContent = $template->toCompiled();
|
||||
$oldContent = $template->getOriginal('content');
|
||||
|
||||
if ($newContent === $oldContent && $templateName === $oldTemplateName && !$isDelete) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$record = new self;
|
||||
$record->type = $type;
|
||||
$record->theme = Theme::getEditThemeCode();
|
||||
$record->template = $isDelete ? '' : $dirName.'/'.$templateName;
|
||||
$record->old_template = $oldTemplateName ? $dirName.'/'.$oldTemplateName : '';
|
||||
$record->content = $isDelete ? '' : $newContent;
|
||||
$record->old_content = $oldContent;
|
||||
|
||||
if ($user = BackendAuth::getRealUser()) {
|
||||
$record->user_id = $user->id;
|
||||
}
|
||||
|
||||
try {
|
||||
$record->save();
|
||||
} catch (Exception $ex) {
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
public function getThemeNameAttribute()
|
||||
{
|
||||
$code = $this->theme;
|
||||
|
||||
if (!isset($this->themeCache[$code])) {
|
||||
$this->themeCache[$code] = Theme::load($code);
|
||||
}
|
||||
|
||||
$theme = $this->themeCache[$code];
|
||||
|
||||
return $theme->getConfigValue('name', $theme->getDirName());
|
||||
}
|
||||
|
||||
public function getTypeOptions()
|
||||
{
|
||||
return [
|
||||
self::TYPE_CREATE => 'cms::lang.theme_log.type_create',
|
||||
self::TYPE_UPDATE => 'cms::lang.theme_log.type_update',
|
||||
self::TYPE_DELETE => 'cms::lang.theme_log.type_delete'
|
||||
];
|
||||
}
|
||||
|
||||
public function getAnyTemplateAttribute()
|
||||
{
|
||||
return $this->template ?: $this->old_template;
|
||||
}
|
||||
|
||||
public function getTypeNameAttribute()
|
||||
{
|
||||
return array_get($this->getTypeOptions(), $this->type);
|
||||
}
|
||||
}
|
||||
4
modules/cms/models/maintenancesetting/_hint.php
Normal file
4
modules/cms/models/maintenancesetting/_hint.php
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
<p>
|
||||
<?= e(trans('cms::lang.maintenance.hint')) ?>
|
||||
</p>
|
||||
35
modules/cms/models/maintenancesetting/fields.yaml
Normal file
35
modules/cms/models/maintenancesetting/fields.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
# ===================================
|
||||
# Field Definitions
|
||||
# ===================================
|
||||
|
||||
fields:
|
||||
|
||||
hint:
|
||||
type: hint
|
||||
path: ~/modules/cms/models/maintenancesetting/_hint.php
|
||||
|
||||
is_enabled:
|
||||
label: cms::lang.maintenance.is_enabled
|
||||
comment: cms::lang.maintenance.is_enabled_comment
|
||||
type: checkbox
|
||||
|
||||
cms_page:
|
||||
type: dropdown
|
||||
cssClass: checkbox-align
|
||||
|
||||
allowed_ips:
|
||||
label: cms::lang.maintenance.allowed_ips.name
|
||||
type: repeater
|
||||
commentAbove: cms::lang.maintenance.allowed_ips.description
|
||||
prompt: cms::lang.maintenance.allowed_ips.prompt
|
||||
cssClass: checkbox-align
|
||||
form:
|
||||
fields:
|
||||
ip:
|
||||
label: cms::lang.maintenance.allowed_ips.ip
|
||||
type: text
|
||||
span: left
|
||||
label:
|
||||
label: cms::lang.maintenance.allowed_ips.label
|
||||
type: text
|
||||
span: right
|
||||
14
modules/cms/models/themeexport/fields.yaml
Normal file
14
modules/cms/models/themeexport/fields.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
# ===================================
|
||||
# Field Definitions
|
||||
# ===================================
|
||||
|
||||
fields:
|
||||
|
||||
themeName:
|
||||
label: cms::lang.theme.theme_label
|
||||
disabled: true
|
||||
|
||||
folders:
|
||||
label: cms::lang.theme.export_folders_label
|
||||
commentAbove: cms::lang.theme.export_folders_comment
|
||||
type: checkboxlist
|
||||
25
modules/cms/models/themeimport/fields.yaml
Normal file
25
modules/cms/models/themeimport/fields.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
# ===================================
|
||||
# Field Definitions
|
||||
# ===================================
|
||||
|
||||
fields:
|
||||
|
||||
themeName:
|
||||
label: cms::lang.theme.theme_label
|
||||
disabled: true
|
||||
|
||||
uploaded_file:
|
||||
label: cms::lang.theme.import_uploaded_file
|
||||
type: fileupload
|
||||
mode: file
|
||||
fileTypes: zip
|
||||
|
||||
overwrite:
|
||||
label: cms::lang.theme.import_overwrite_label
|
||||
comment: cms::lang.theme.import_overwrite_comment
|
||||
type: checkbox
|
||||
|
||||
folders:
|
||||
label: cms::lang.theme.import_folders_label
|
||||
commentAbove: cms::lang.theme.import_folders_comment
|
||||
type: checkboxlist
|
||||
49
modules/cms/models/themelog/columns.yaml
Normal file
49
modules/cms/models/themelog/columns.yaml
Normal file
@@ -0,0 +1,49 @@
|
||||
# ===================================
|
||||
# Column Definitions
|
||||
# ===================================
|
||||
|
||||
columns:
|
||||
id:
|
||||
label: cms::lang.theme_log.id
|
||||
searchable: yes
|
||||
invisible: true
|
||||
width: 75px
|
||||
|
||||
created_at:
|
||||
label: cms::lang.theme_log.created_at
|
||||
searchable: yes
|
||||
width: 160px
|
||||
type: timetense
|
||||
|
||||
type:
|
||||
label: cms::lang.theme_log.type
|
||||
invisible: true
|
||||
|
||||
any_template:
|
||||
label: cms::lang.theme_log.template
|
||||
searchable: false
|
||||
sortable: false
|
||||
|
||||
template:
|
||||
label: cms::lang.theme_log.new_template
|
||||
searchable: true
|
||||
invisible: true
|
||||
|
||||
old_template:
|
||||
label: cms::lang.theme_log.old_template
|
||||
searchable: true
|
||||
invisible: true
|
||||
|
||||
user:
|
||||
label: cms::lang.theme_log.user
|
||||
relation: user
|
||||
select: concat(first_name, ' ', last_name)
|
||||
|
||||
theme_name:
|
||||
label: cms::lang.theme_log.theme_name
|
||||
sortable: false
|
||||
|
||||
theme:
|
||||
label: cms::lang.theme_log.theme_code
|
||||
searchable: true
|
||||
invisible: true
|
||||
36
modules/cms/models/themelog/fields.yaml
Normal file
36
modules/cms/models/themelog/fields.yaml
Normal file
@@ -0,0 +1,36 @@
|
||||
# ===================================
|
||||
# Field Definitions
|
||||
# ===================================
|
||||
|
||||
tabs:
|
||||
fields:
|
||||
|
||||
diff_template:
|
||||
tab: cms::lang.theme_log.diff
|
||||
type: partial
|
||||
path: field_diff_template
|
||||
|
||||
diff_content:
|
||||
tab: cms::lang.theme_log.diff
|
||||
type: partial
|
||||
path: field_diff_content
|
||||
|
||||
template:
|
||||
tab: cms::lang.theme_log.new_value
|
||||
type: partial
|
||||
path: field_template
|
||||
|
||||
content:
|
||||
tab: cms::lang.theme_log.new_value
|
||||
type: partial
|
||||
path: field_content
|
||||
|
||||
old_template:
|
||||
tab: cms::lang.theme_log.old_value
|
||||
type: partial
|
||||
path: field_template
|
||||
|
||||
old_content:
|
||||
tab: cms::lang.theme_log.old_value
|
||||
type: partial
|
||||
path: field_content
|
||||
Reference in New Issue
Block a user