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:
262
modules/system/models/EventLog.php
Normal file
262
modules/system/models/EventLog.php
Normal file
@@ -0,0 +1,262 @@
|
||||
<?php namespace System\Models;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Throwable;
|
||||
use ReflectionClass;
|
||||
use Winter\Storm\Database\Model;
|
||||
use Winter\Storm\Support\Str;
|
||||
|
||||
/**
|
||||
* Model for logging system errors and debug trace messages
|
||||
*
|
||||
* @package winter\wn-system-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class EventLog extends Model
|
||||
{
|
||||
protected const EXCEPTION_LOG_VERSION = 2;
|
||||
protected const EXCEPTION_SNIPPET_LINES = 12;
|
||||
|
||||
/**
|
||||
* @var string The database table used by the model.
|
||||
*/
|
||||
protected $table = 'system_event_logs';
|
||||
|
||||
/**
|
||||
* @var array List of attribute names which are json encoded and decoded from the database.
|
||||
*/
|
||||
protected $jsonable = ['details'];
|
||||
|
||||
/**
|
||||
* Returns true if this logger should be used.
|
||||
*/
|
||||
public static function useLogging(): bool
|
||||
{
|
||||
return (
|
||||
!defined('WINTER_NO_EVENT_LOGGING') &&
|
||||
class_exists('Model') &&
|
||||
Model::getConnectionResolver() &&
|
||||
static::hasDatabaseTable() &&
|
||||
LogSetting::get('log_events')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a log record
|
||||
*/
|
||||
public static function add(string $message, string $level = 'info', ?array $details = null): static
|
||||
{
|
||||
$record = new static;
|
||||
$record->message = $message;
|
||||
$record->level = $level;
|
||||
|
||||
if ($details !== null) {
|
||||
$record->details = (array) $details;
|
||||
}
|
||||
|
||||
try {
|
||||
$record->save();
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an exception log record
|
||||
*/
|
||||
public static function addException(Throwable $throwable, string $level = 'error'): static
|
||||
{
|
||||
$record = new static;
|
||||
$record->message = $throwable->getMessage();
|
||||
$record->level = $level;
|
||||
$record->details = $record->getDetails($throwable);
|
||||
|
||||
try {
|
||||
$record->save();
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Beautify level value.
|
||||
*/
|
||||
public function getLevelAttribute(string $level): string
|
||||
{
|
||||
return ucfirst($level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a shorter version of the message attribute,
|
||||
* extracts the exception message or limits by 100 characters.
|
||||
*/
|
||||
public function getSummaryAttribute(): string
|
||||
{
|
||||
if (preg_match("/with message '(.+)' in/", $this->message, $match)) {
|
||||
return $match[1];
|
||||
}
|
||||
|
||||
// Get first line of message
|
||||
preg_match('/^([^\n\r]+)/m', $this->message, $matches);
|
||||
|
||||
return Str::limit($matches[1] ?? '', 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the details array for logging
|
||||
*/
|
||||
public function getDetails(Throwable $throwable): array
|
||||
{
|
||||
return [
|
||||
'logVersion' => static::EXCEPTION_LOG_VERSION,
|
||||
'exception' => $this->exceptionToArray($throwable),
|
||||
'environment' => $this->getEnviromentInfo(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a throwable into an array of data for logging
|
||||
*/
|
||||
protected function exceptionToArray(Throwable $throwable): array
|
||||
{
|
||||
return [
|
||||
'type' => $throwable::class,
|
||||
'message' => $throwable->getMessage(),
|
||||
'file' => $throwable->getFile(),
|
||||
'line' => $throwable->getLine(),
|
||||
'snippet' => $this->getSnippet($throwable->getFile(), $throwable->getLine()),
|
||||
'trace' => $this->exceptionTraceToArray($throwable->getTrace()),
|
||||
'stringTrace' => $throwable->getTraceAsString(),
|
||||
'code' => $throwable->getCode(),
|
||||
'previous' => $throwable->getPrevious()
|
||||
? $this->exceptionToArray($throwable->getPrevious())
|
||||
: null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an array trace with extra data not provided by the default trace
|
||||
*
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected function exceptionTraceToArray(array $trace): array
|
||||
{
|
||||
foreach ($trace as $index => $frame) {
|
||||
if (!isset($frame['file']) && isset($frame['class'])) {
|
||||
$ref = new ReflectionClass($frame['class']);
|
||||
$frame['file'] = $ref->getFileName();
|
||||
|
||||
if (!isset($frame['line']) && isset($frame['function']) && !str_contains($frame['function'], '{')) {
|
||||
foreach (file($frame['file']) as $line => $text) {
|
||||
if (preg_match(sprintf('/function\s.*%s/', $frame['function']), $text)) {
|
||||
$frame['line'] = $line + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$trace[$index] = [
|
||||
'file' => $frame['file'] ?? null,
|
||||
'line' => $frame['line'] ?? null,
|
||||
'function' => $frame['function'] ?? null,
|
||||
'class' => $frame['class'] ?? null,
|
||||
'type' => $frame['type'] ?? null,
|
||||
'snippet' => !empty($frame['file']) && !empty($frame['line'])
|
||||
? $this->getSnippet($frame['file'], $frame['line'])
|
||||
: '',
|
||||
'in_app' => ($frame['file'] ?? null) ? $this->isInAppError($frame['file']) : false,
|
||||
'arguments' => array_map(function ($arg) {
|
||||
if (is_numeric($arg)) {
|
||||
return $arg;
|
||||
}
|
||||
if (is_string($arg)) {
|
||||
return "'$arg'";
|
||||
}
|
||||
if (is_null($arg)) {
|
||||
return 'null';
|
||||
}
|
||||
if (is_bool($arg)) {
|
||||
return $arg ? 'true' : 'false';
|
||||
}
|
||||
if (is_array($arg)) {
|
||||
return 'Array';
|
||||
}
|
||||
if (is_object($arg)) {
|
||||
return get_class($arg);
|
||||
}
|
||||
if (is_resource($arg)) {
|
||||
return 'Resource';
|
||||
}
|
||||
}, $frame['args'] ?? []),
|
||||
];
|
||||
}
|
||||
|
||||
return $trace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the code snippet referenced in a trace
|
||||
*/
|
||||
protected function getSnippet(string $file, int $line): array
|
||||
{
|
||||
if (str_contains($file, ': eval()\'d code')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$lines = file($file);
|
||||
|
||||
if (count($lines) < static::EXCEPTION_SNIPPET_LINES) {
|
||||
return $lines;
|
||||
}
|
||||
|
||||
return array_slice(
|
||||
$lines,
|
||||
$line - (static::EXCEPTION_SNIPPET_LINES / 2),
|
||||
static::EXCEPTION_SNIPPET_LINES,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get environment details to record with the exception
|
||||
*/
|
||||
protected function getEnviromentInfo(): array
|
||||
{
|
||||
if (app()->runningInConsole()) {
|
||||
return [
|
||||
'context' => 'CLI',
|
||||
'testing' => app()->runningUnitTests(),
|
||||
'env' => app()->environment(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'context' => 'Web',
|
||||
'backend' => method_exists(app(), 'runningInBackend') ? app()->runningInBackend() : false,
|
||||
'testing' => app()->runningUnitTests(),
|
||||
'url' => app('url')->current(),
|
||||
'method' => app('request')->method(),
|
||||
'env' => app()->environment(),
|
||||
'ip' => app('request')->ip(),
|
||||
'userAgent' => app('request')->header('User-Agent'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to work out if a file should be considered "In App" or not
|
||||
*/
|
||||
protected function isInAppError(string $file): bool
|
||||
{
|
||||
if (basename($file) === 'index.php' || basename($file) === 'artisan') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !Str::startsWith($file, base_path('vendor')) && !Str::startsWith($file, base_path('modules'));
|
||||
}
|
||||
}
|
||||
98
modules/system/models/File.php
Normal file
98
modules/system/models/File.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php namespace System\Models;
|
||||
|
||||
use Url;
|
||||
use Config;
|
||||
use Storage;
|
||||
use Winter\Storm\Database\Attach\File as FileBase;
|
||||
use Backend\Controllers\Files;
|
||||
|
||||
/**
|
||||
* File attachment model
|
||||
*
|
||||
* @package winter\wn-system-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class File extends FileBase
|
||||
{
|
||||
/**
|
||||
* @var string The database table used by the model.
|
||||
*/
|
||||
protected $table = 'system_files';
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getThumb($width, $height, $options = [])
|
||||
{
|
||||
$url = '';
|
||||
$width = !empty($width) ? $width : 0;
|
||||
$height = !empty($height) ? $height : 0;
|
||||
|
||||
if (!$this->isPublic() && class_exists(Files::class)) {
|
||||
$options = $this->getDefaultThumbOptions($options);
|
||||
// Ensure that the thumb exists first
|
||||
parent::getThumb($width, $height, $options);
|
||||
|
||||
// Return the Files controller handler for the URL
|
||||
$url = Files::getThumbUrl($this, $width, $height, $options);
|
||||
} else {
|
||||
$url = parent::getThumb($width, $height, $options);
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getPath(?string $fileName = null): string
|
||||
{
|
||||
$url = '';
|
||||
if (!$this->isPublic() && class_exists(Files::class)) {
|
||||
$url = Files::getDownloadUrl($this);
|
||||
} else {
|
||||
$url = parent::getPath($fileName);
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the public address for the storage path.
|
||||
*/
|
||||
public function getPublicPath(): string
|
||||
{
|
||||
$uploadsPath = Config::get('cms.storage.uploads.path', '/storage/app/uploads');
|
||||
|
||||
if ($this->isPublic()) {
|
||||
$uploadsPath .= '/public';
|
||||
}
|
||||
else {
|
||||
$uploadsPath .= '/protected';
|
||||
}
|
||||
|
||||
return Url::asset($uploadsPath) . '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the internal storage path.
|
||||
*/
|
||||
public function getStorageDirectory(): string
|
||||
{
|
||||
$uploadsFolder = Config::get('cms.storage.uploads.folder');
|
||||
|
||||
if ($this->isPublic()) {
|
||||
return $uploadsFolder . '/public/';
|
||||
}
|
||||
|
||||
return $uploadsFolder . '/protected/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the storage disk the file is stored on
|
||||
*/
|
||||
public function getDiskName(): string
|
||||
{
|
||||
return Config::get('cms.storage.uploads.disk');
|
||||
}
|
||||
}
|
||||
69
modules/system/models/LogSetting.php
Normal file
69
modules/system/models/LogSetting.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php namespace System\Models;
|
||||
|
||||
use Model;
|
||||
|
||||
/**
|
||||
* System log settings
|
||||
*
|
||||
* @package winter\wn-system-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class LogSetting 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 = 'system_log_settings';
|
||||
|
||||
/**
|
||||
* @var mixed Settings form field defitions
|
||||
*/
|
||||
public $settingsFields = 'fields.yaml';
|
||||
|
||||
/**
|
||||
* Validation rules
|
||||
*/
|
||||
public $rules = [];
|
||||
|
||||
public static function filterSettingItems($manager)
|
||||
{
|
||||
if (!self::isConfigured()) {
|
||||
$manager->removeSettingItem('Winter.System', 'request_logs');
|
||||
$manager->removeSettingItem('Winter.Cms', 'theme_logs');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self::get('log_events')) {
|
||||
$manager->removeSettingItem('Winter.System', 'event_logs');
|
||||
}
|
||||
|
||||
if (!self::get('log_requests')) {
|
||||
$manager->removeSettingItem('Winter.System', 'request_logs');
|
||||
}
|
||||
|
||||
if (!self::get('log_theme')) {
|
||||
$manager->removeSettingItem('Winter.Cms', 'theme_logs');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->log_events = true;
|
||||
$this->log_requests = false;
|
||||
$this->log_theme = false;
|
||||
}
|
||||
}
|
||||
179
modules/system/models/MailBrandSetting.php
Normal file
179
modules/system/models/MailBrandSetting.php
Normal file
@@ -0,0 +1,179 @@
|
||||
<?php namespace System\Models;
|
||||
|
||||
use App;
|
||||
use Str;
|
||||
use Model;
|
||||
use Cache;
|
||||
use Less_Parser;
|
||||
use Exception;
|
||||
use File as FileHelper;
|
||||
use Winter\Storm\Parse\Assetic\Filter\LessImportResolver;
|
||||
|
||||
/**
|
||||
* Mail brand settings
|
||||
*
|
||||
* @package winter\wn-system-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class MailBrandSetting extends Model
|
||||
{
|
||||
use \System\Traits\ViewMaker;
|
||||
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 = 'system_mail_brand_settings';
|
||||
|
||||
/**
|
||||
* @var mixed Settings form field defitions
|
||||
*/
|
||||
public $settingsFields = 'fields.yaml';
|
||||
|
||||
/**
|
||||
* @var string The key to store rendered CSS in the cache under
|
||||
*/
|
||||
public $cacheKey = 'system::mailbrand.custom_css';
|
||||
|
||||
const WHITE_COLOR = '#fff';
|
||||
const BODY_BG = '#f5f8fa';
|
||||
const PRIMARY_BG = '#d66829';
|
||||
const POSITIVE_BG = '#52a838';
|
||||
const NEGATIVE_BG = '#e01346';
|
||||
const HEADER_COLOR = '#bbbfc3';
|
||||
const HEADING_COLOR = '#2f3133';
|
||||
const TEXT_COLOR = '#74787e';
|
||||
const LINK_COLOR = '#2da7c7';
|
||||
const FOOTER_COLOR = '#aeaeae';
|
||||
const BORDER_COLOR = '#edeff2';
|
||||
const PROMOTION_BORDER_COLOR = '#9ba2ab';
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
$config = App::make('config');
|
||||
|
||||
$vars = static::getCssVars();
|
||||
|
||||
foreach ($vars as $var => $default) {
|
||||
$this->{$var} = $config->get('brand.mail.'.Str::studly($var), $default);
|
||||
}
|
||||
}
|
||||
|
||||
public function afterSave()
|
||||
{
|
||||
$this->resetCache();
|
||||
}
|
||||
|
||||
public function resetCache()
|
||||
{
|
||||
Cache::forget(self::instance()->cacheKey);
|
||||
}
|
||||
|
||||
public static function renderCss()
|
||||
{
|
||||
$cacheKey = self::instance()->cacheKey;
|
||||
if (Cache::has($cacheKey)) {
|
||||
return Cache::get($cacheKey);
|
||||
}
|
||||
|
||||
try {
|
||||
$customCss = self::compileCss();
|
||||
Cache::forever($cacheKey, $customCss);
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
$customCss = '/* ' . e($ex->getMessage()) . ' */';
|
||||
}
|
||||
|
||||
return $customCss;
|
||||
}
|
||||
|
||||
protected static function getCssVars()
|
||||
{
|
||||
$vars = [
|
||||
'body_bg' => self::BODY_BG,
|
||||
'content_bg' => self::WHITE_COLOR,
|
||||
'content_inner_bg' => self::WHITE_COLOR,
|
||||
'button_text_color' => self::WHITE_COLOR,
|
||||
'button_primary_bg' => self::PRIMARY_BG,
|
||||
'button_positive_bg' => self::POSITIVE_BG,
|
||||
'button_negative_bg' => self::NEGATIVE_BG,
|
||||
'header_color' => self::HEADER_COLOR,
|
||||
'heading_color' => self::HEADING_COLOR,
|
||||
'text_color' => self::TEXT_COLOR,
|
||||
'link_color' => self::LINK_COLOR,
|
||||
'footer_color' => self::FOOTER_COLOR,
|
||||
'body_border_color' => self::BORDER_COLOR,
|
||||
'subcopy_border_color' => self::BORDER_COLOR,
|
||||
'table_border_color' => self::BORDER_COLOR,
|
||||
'panel_bg' => self::BORDER_COLOR,
|
||||
'promotion_bg' => self::WHITE_COLOR,
|
||||
'promotion_border_color' => self::PROMOTION_BORDER_COLOR,
|
||||
];
|
||||
|
||||
return $vars;
|
||||
}
|
||||
|
||||
protected static function makeCssVars()
|
||||
{
|
||||
$vars = static::getCssVars();
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($vars as $var => $default) {
|
||||
// panel_bg -> panel-bg
|
||||
$cssVar = str_replace('_', '-', $var);
|
||||
|
||||
$result[$cssVar] = self::get($var, $default);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function compileCss()
|
||||
{
|
||||
$parser = new Less_Parser(['compress' => true]);
|
||||
|
||||
// Refuse every @import directive. The bundled custom.less ships no imports
|
||||
// and the only user-controlled input here is CSS variable values via
|
||||
// ModifyVars below — those values are concatenated into the LESS source
|
||||
// by Less_Parser::serializeVars() with no escaping, so a malicious value
|
||||
// like `red; @import (inline) "/etc/passwd"` would otherwise reach the
|
||||
// parser as a real @import directive. See GHSA-58fp-mcx6-7qf9.
|
||||
//
|
||||
// Note: unlike BrandSetting/EditorSetting, this model deliberately does
|
||||
// not strip_tags() its renderCss() output. User input flows in only via
|
||||
// ModifyVars (CSS variable values), not as a raw CSS string, and the
|
||||
// output is consumed by the mail rendering pipeline rather than rendered
|
||||
// inline on a backend page — so the threat model strip_tags() guards
|
||||
// against does not apply here. The @import injection vector that
|
||||
// ModifyVars opens up is closed structurally by the SetImportDirs
|
||||
// deny-all gate, not by strip_tags.
|
||||
$parser->SetImportDirs(['' => LessImportResolver::makeResolver([], null)]);
|
||||
|
||||
$basePath = base_path('modules/system/models/mailbrandsetting');
|
||||
|
||||
$parser->ModifyVars(static::makeCssVars());
|
||||
|
||||
$parser->parse(FileHelper::get($basePath . '/custom.less'));
|
||||
|
||||
return $parser->getCss();
|
||||
}
|
||||
}
|
||||
204
modules/system/models/MailLayout.php
Normal file
204
modules/system/models/MailLayout.php
Normal file
@@ -0,0 +1,204 @@
|
||||
<?php namespace System\Models;
|
||||
|
||||
use View;
|
||||
use Model;
|
||||
use System\Classes\MailManager;
|
||||
use Winter\Storm\Mail\MailParser;
|
||||
use ApplicationException;
|
||||
use File as FileHelper;
|
||||
|
||||
/**
|
||||
* Mail layout
|
||||
*
|
||||
* @package winter\wn-system-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class MailLayout extends Model
|
||||
{
|
||||
use \Winter\Storm\Database\Traits\Validation;
|
||||
|
||||
/**
|
||||
* @var string The database table used by the model.
|
||||
*/
|
||||
protected $table = 'system_mail_layouts';
|
||||
|
||||
/**
|
||||
* @var array Guarded fields
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* @var array Fillable fields
|
||||
*/
|
||||
protected $fillable = [];
|
||||
|
||||
/**
|
||||
* @var array Validation rules
|
||||
*/
|
||||
public $rules = [
|
||||
'code' => 'required|unique:system_mail_layouts',
|
||||
'name' => 'required',
|
||||
'content_html' => 'required',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array Options array
|
||||
*/
|
||||
protected $jsonable = [
|
||||
'options'
|
||||
];
|
||||
|
||||
public static $codeCache;
|
||||
|
||||
/**
|
||||
* Fired before the model is deleted.
|
||||
*
|
||||
* @return void
|
||||
* @throws ApplicationException if the template is locked
|
||||
*/
|
||||
public function beforeDelete()
|
||||
{
|
||||
if ($this->is_locked) {
|
||||
throw new ApplicationException('Cannot delete this template because it is locked');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List MailLayouts codes keyed by ID.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function listCodes()
|
||||
{
|
||||
if (self::$codeCache !== null) {
|
||||
return self::$codeCache;
|
||||
}
|
||||
|
||||
return self::$codeCache = self::lists('id', 'code');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ID of a MailLayout instance from a defined code.
|
||||
*
|
||||
* @param string $code
|
||||
* @return string
|
||||
*/
|
||||
public static function getIdFromCode($code)
|
||||
{
|
||||
return array_get(self::listCodes(), $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a MailLayout instance by its code or create a new instance from the view file.
|
||||
*
|
||||
* @param string $code
|
||||
* @return MailLayout
|
||||
*/
|
||||
public static function findOrMakeLayout($code)
|
||||
{
|
||||
$layout = self::whereCode($code)->first();
|
||||
|
||||
if (!$layout && View::exists($code)) {
|
||||
$layout = new self;
|
||||
$layout->code = $code;
|
||||
$layout->fillFromView($code);
|
||||
}
|
||||
|
||||
return $layout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops over each mail layout and ensures the system has a layout,
|
||||
* if the layout does not exist, it will create one.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function createLayouts()
|
||||
{
|
||||
$dbLayouts = self::lists('code', 'code');
|
||||
|
||||
$definitions = MailManager::instance()->listRegisteredLayouts();
|
||||
foreach ($definitions as $code => $path) {
|
||||
if (array_key_exists($code, $dbLayouts)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$layout = new static;
|
||||
$layout->code = $code;
|
||||
$layout->is_locked = true;
|
||||
$layout->fillFromView($path);
|
||||
$layout->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill model using a view file retrieved by code.
|
||||
*
|
||||
* @param string|null $code
|
||||
* @return void
|
||||
* @throws ApplicationException if a layout with the defined code is not registered.
|
||||
*/
|
||||
public function fillFromCode($code = null)
|
||||
{
|
||||
$definitions = MailManager::instance()->listRegisteredLayouts();
|
||||
|
||||
if ($code === null) {
|
||||
$code = $this->code;
|
||||
}
|
||||
|
||||
if (!$definition = array_get($definitions, $code)) {
|
||||
throw new ApplicationException('Unable to find a registered layout with code: '.$code);
|
||||
}
|
||||
|
||||
$this->fillFromView($definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill model using a view file retrieved by path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return void
|
||||
*/
|
||||
public function fillFromView($path)
|
||||
{
|
||||
$sections = self::getTemplateSections($path);
|
||||
|
||||
$css = '
|
||||
@media only screen and (max-width: 600px) {
|
||||
.inner-body {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
.button {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
';
|
||||
|
||||
$this->name = array_get($sections, 'settings.name', '???');
|
||||
$this->content_css = $css;
|
||||
$this->content_html = array_get($sections, 'html');
|
||||
$this->content_text = array_get($sections, 'text');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get section array from a view file retrieved by code.
|
||||
*
|
||||
* @param string $code
|
||||
* @return array|null
|
||||
*/
|
||||
protected static function getTemplateSections($code)
|
||||
{
|
||||
if (!View::exists($code)) {
|
||||
return null;
|
||||
}
|
||||
$view = View::make($code);
|
||||
return MailParser::parse(FileHelper::get($view->getPath()));
|
||||
}
|
||||
}
|
||||
163
modules/system/models/MailPartial.php
Normal file
163
modules/system/models/MailPartial.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php namespace System\Models;
|
||||
|
||||
use View;
|
||||
use Model;
|
||||
use System\Classes\MailManager;
|
||||
use Winter\Storm\Mail\MailParser;
|
||||
use ApplicationException;
|
||||
use Exception;
|
||||
use File as FileHelper;
|
||||
|
||||
/**
|
||||
* Mail partial
|
||||
*
|
||||
* @package winter\wn-system-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class MailPartial extends Model
|
||||
{
|
||||
use \Winter\Storm\Database\Traits\Validation;
|
||||
|
||||
/**
|
||||
* @var string The database table used by the model.
|
||||
*/
|
||||
protected $table = 'system_mail_partials';
|
||||
|
||||
/**
|
||||
* @var array Guarded fields
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* @var array Fillable fields
|
||||
*/
|
||||
protected $fillable = [];
|
||||
|
||||
/**
|
||||
* @var array Validation rules
|
||||
*/
|
||||
public $rules = [
|
||||
'code' => 'required|unique:system_mail_partials',
|
||||
'name' => 'required',
|
||||
'content_html' => 'required',
|
||||
];
|
||||
|
||||
/**
|
||||
* Fired after the model has been fetched.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function afterFetch()
|
||||
{
|
||||
if (!$this->is_custom) {
|
||||
$this->fillFromCode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a MailPartial instance by code or create a new instance from a view file.
|
||||
*
|
||||
* @param string $code
|
||||
* @return MailTemplate
|
||||
*/
|
||||
public static function findOrMakePartial($code)
|
||||
{
|
||||
try {
|
||||
if (!$template = self::whereCode($code)->first()) {
|
||||
$template = new self;
|
||||
$template->code = $code;
|
||||
$template->fillFromCode($code);
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops over each mail layout and ensures the system has a layout,
|
||||
* if the layout does not exist, it will create one.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function createPartials()
|
||||
{
|
||||
$partials = MailManager::instance()->listRegisteredPartials();
|
||||
$dbPartials = self::lists('is_custom', 'code');
|
||||
$newPartials = array_diff_key($partials, $dbPartials);
|
||||
|
||||
/*
|
||||
* Clean up non-customized partials
|
||||
*/
|
||||
foreach ($dbPartials as $code => $isCustom) {
|
||||
if ($isCustom) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!array_key_exists($code, $partials)) {
|
||||
self::whereCode($code)->delete();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($newPartials as $code => $path) {
|
||||
$partial = new static;
|
||||
$partial->code = $code;
|
||||
$partial->is_custom = 0;
|
||||
$partial->fillFromView($path);
|
||||
$partial->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill model using a view file retrieved by code.
|
||||
*
|
||||
* @param string|null $code
|
||||
* @return void
|
||||
*/
|
||||
public function fillFromCode($code = null)
|
||||
{
|
||||
$definitions = MailManager::instance()->listRegisteredPartials();
|
||||
|
||||
if ($code === null) {
|
||||
$code = $this->code;
|
||||
}
|
||||
|
||||
if (!$definition = array_get($definitions, $code)) {
|
||||
throw new ApplicationException('Unable to find a registered partial with code: '.$code);
|
||||
}
|
||||
|
||||
$this->fillFromView($definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill model using a view file retrieved by path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return void
|
||||
*/
|
||||
public function fillFromView($path)
|
||||
{
|
||||
$sections = self::getTemplateSections($path);
|
||||
|
||||
$this->name = array_get($sections, 'settings.name', '???');
|
||||
$this->content_html = array_get($sections, 'html');
|
||||
$this->content_text = array_get($sections, 'text');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get section array from a view file retrieved by code.
|
||||
*
|
||||
* @param string $code
|
||||
* @return array|null
|
||||
*/
|
||||
protected static function getTemplateSections($code)
|
||||
{
|
||||
if (!View::exists($code)) {
|
||||
return null;
|
||||
}
|
||||
$view = View::make($code);
|
||||
return MailParser::parse(FileHelper::get($view->getPath()));
|
||||
}
|
||||
}
|
||||
147
modules/system/models/MailSetting.php
Normal file
147
modules/system/models/MailSetting.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php namespace System\Models;
|
||||
|
||||
use App;
|
||||
use Model;
|
||||
|
||||
/**
|
||||
* Mail settings
|
||||
*
|
||||
* @package winter\wn-system-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class MailSetting extends Model
|
||||
{
|
||||
use \Winter\Storm\Database\Traits\Validation;
|
||||
|
||||
const MODE_FAILOVER = 'failover';
|
||||
const MODE_LOG = 'log';
|
||||
const MODE_MAIL = 'mail';
|
||||
const MODE_SENDMAIL = 'sendmail';
|
||||
const MODE_SMTP = 'smtp';
|
||||
|
||||
/**
|
||||
* @var array Behaviors implemented by this model.
|
||||
*/
|
||||
public $implement = [
|
||||
\System\Behaviors\SettingsModel::class
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string Unique code
|
||||
*/
|
||||
public $settingsCode = 'system_mail_settings';
|
||||
|
||||
/**
|
||||
* @var mixed Settings form field defitions
|
||||
*/
|
||||
public $settingsFields = 'fields.yaml';
|
||||
|
||||
/*
|
||||
* Validation rules
|
||||
*/
|
||||
public $rules = [
|
||||
'failover_mailers' => 'required_if:send_mode,'.self::MODE_FAILOVER,
|
||||
'sender_name' => 'required',
|
||||
'sender_email' => 'required|email'
|
||||
];
|
||||
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
$config = App::make('config');
|
||||
$mailers = $config->get('mail.mailers', [
|
||||
'sendmail' => ['path' => $config->get('mail.sendmail', '/usr/sbin/sendmail')],
|
||||
'smtp' => [
|
||||
'host' => $config->get('mail.host'),
|
||||
'port' => $config->get('mail.port', 587),
|
||||
'username' => $config->get('mail.username'),
|
||||
'password' => $config->get('mail.password'),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->send_mode = $config->get('mail.default', static::MODE_MAIL);
|
||||
$this->sender_name = $config->get('mail.from.name', 'Your Site');
|
||||
$this->sender_email = $config->get('mail.from.address', 'admin@example.com');
|
||||
$this->sendmail_path = array_get($mailers['sendmail'], 'path', '/usr/sbin/sendmail');
|
||||
$this->smtp_address = array_get($mailers['smtp'], 'host');
|
||||
$this->smtp_port = array_get($mailers['smtp'], 'port', 587);
|
||||
$this->smtp_user = array_get($mailers['smtp'], 'username');
|
||||
$this->smtp_password = array_get($mailers['smtp'], 'password');
|
||||
$this->smtp_authorization = !!strlen($this->smtp_user);
|
||||
$this->failover_mailers = implode(',', $config->get('mail.mailers.failover.mailers', []));
|
||||
}
|
||||
|
||||
public function getFailoverMailersOptions()
|
||||
{
|
||||
return collect(App::make('config')->get('mail.mailers'))->except('failover')->keys()->all();
|
||||
}
|
||||
|
||||
public function getSendModeOptions()
|
||||
{
|
||||
return [
|
||||
static::MODE_FAILOVER => 'system::lang.mail.failover',
|
||||
static::MODE_LOG => 'system::lang.mail.log_file',
|
||||
static::MODE_MAIL => 'system::lang.mail.php_mail',
|
||||
static::MODE_SENDMAIL => 'system::lang.mail.sendmail',
|
||||
static::MODE_SMTP => 'system::lang.mail.smtp',
|
||||
];
|
||||
}
|
||||
|
||||
public static function applyConfigValues()
|
||||
{
|
||||
$config = App::make('config');
|
||||
$settings = self::instance();
|
||||
$config->set('mail.default', $settings->send_mode);
|
||||
$config->set('mail.from.name', $settings->sender_name);
|
||||
$config->set('mail.from.address', $settings->sender_email);
|
||||
|
||||
switch ($settings->send_mode) {
|
||||
case self::MODE_FAILOVER:
|
||||
$config->set('mail.mailers.failover.mailers', explode(',', $settings->failover_mailers));
|
||||
break;
|
||||
|
||||
case self::MODE_SMTP:
|
||||
$config->set('mail.mailers.smtp.host', $settings->smtp_address);
|
||||
$config->set('mail.mailers.smtp.port', $settings->smtp_port);
|
||||
$config->set('mail.mailers.smtp.encryption', $settings->smtp_port === 465 ? 'tls' : null);
|
||||
if ($settings->smtp_authorization) {
|
||||
$config->set('mail.mailers.smtp.username', $settings->smtp_user);
|
||||
$config->set('mail.mailers.smtp.password', $settings->smtp_password);
|
||||
}
|
||||
else {
|
||||
$config->set('mail.mailers.smtp.username', null);
|
||||
$config->set('mail.mailers.smtp.password', null);
|
||||
}
|
||||
break;
|
||||
|
||||
case self::MODE_SENDMAIL:
|
||||
$config->set('mail.mailers.sendmail.path', $settings->sendmail_path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter fields callback.
|
||||
*
|
||||
* We use this to show smtp credential fields only for smtp mode and when smtp authorization is required.
|
||||
*
|
||||
* @param array $fields
|
||||
* @param string|null $context
|
||||
* @return void
|
||||
*/
|
||||
public function filterFields($fields, $context = null)
|
||||
{
|
||||
$hideAuth = $fields->send_mode->value !== 'smtp' || !$fields->smtp_authorization->value;
|
||||
|
||||
if (isset($fields->smtp_user)) {
|
||||
$fields->smtp_user->hidden = $hideAuth;
|
||||
}
|
||||
if (isset($fields->smtp_password)) {
|
||||
$fields->smtp_password->hidden = $hideAuth;
|
||||
}
|
||||
}
|
||||
}
|
||||
218
modules/system/models/MailTemplate.php
Normal file
218
modules/system/models/MailTemplate.php
Normal file
@@ -0,0 +1,218 @@
|
||||
<?php namespace System\Models;
|
||||
|
||||
use View;
|
||||
use Model;
|
||||
use System\Classes\MailManager;
|
||||
use Winter\Storm\Mail\MailParser;
|
||||
use File as FileHelper;
|
||||
|
||||
/**
|
||||
* Mail template
|
||||
*
|
||||
* @package winter\wn-system-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class MailTemplate extends Model
|
||||
{
|
||||
use \Winter\Storm\Database\Traits\Validation;
|
||||
|
||||
/**
|
||||
* @var string The database table used by the model.
|
||||
*/
|
||||
protected $table = 'system_mail_templates';
|
||||
|
||||
/**
|
||||
* @var array Guarded fields
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* @var array Fillable fields
|
||||
*/
|
||||
protected $fillable = [];
|
||||
|
||||
/**
|
||||
* @var array Validation rules
|
||||
*/
|
||||
public $rules = [
|
||||
'code' => 'required|unique:system_mail_templates',
|
||||
'subject' => 'required',
|
||||
'description' => 'required',
|
||||
'content_html' => 'required',
|
||||
];
|
||||
|
||||
public $belongsTo = [
|
||||
'layout' => MailLayout::class
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns an array of template codes and descriptions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function listAllTemplates()
|
||||
{
|
||||
$fileTemplates = (array) MailManager::instance()->listRegisteredTemplates();
|
||||
$dbTemplates = (array) self::lists('code', 'code');
|
||||
$templates = $fileTemplates + $dbTemplates;
|
||||
ksort($templates);
|
||||
return $templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all mail templates.
|
||||
*
|
||||
* @return array Returns an array of the MailTemplate objects.
|
||||
*/
|
||||
public static function allTemplates()
|
||||
{
|
||||
$result = [];
|
||||
$codes = array_keys(self::listAllTemplates());
|
||||
|
||||
foreach ($codes as $code) {
|
||||
if (View::exists($code)) {
|
||||
$result[] = self::findOrMakeTemplate($code);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncronise all file templates to the database.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function syncAll()
|
||||
{
|
||||
MailLayout::createLayouts();
|
||||
MailPartial::createPartials();
|
||||
|
||||
$templates = MailManager::instance()->listRegisteredTemplates();
|
||||
$dbTemplates = self::lists('is_custom', 'code');
|
||||
$newTemplates = array_diff_key($templates, $dbTemplates);
|
||||
|
||||
/*
|
||||
* Clean up non-customized templates
|
||||
*/
|
||||
foreach ($dbTemplates as $code => $isCustom) {
|
||||
if ($isCustom) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!array_key_exists($code, $templates)) {
|
||||
self::whereCode($code)->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create new templates
|
||||
*/
|
||||
foreach ($newTemplates as $code) {
|
||||
$sections = self::getTemplateSections($code);
|
||||
$layoutCode = array_get($sections, 'settings.layout', 'default');
|
||||
$description = array_get($sections, 'settings.description');
|
||||
|
||||
$template = self::make();
|
||||
$template->code = $code;
|
||||
$template->description = $description;
|
||||
$template->is_custom = 0;
|
||||
$template->layout_id = MailLayout::getIdFromCode($layoutCode);
|
||||
$template->forceSave();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired after the model has been fetched.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function afterFetch()
|
||||
{
|
||||
if (!$this->is_custom) {
|
||||
$this->fillFromView($this->code);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill model using provided content.
|
||||
*
|
||||
* @param string $content
|
||||
* @return void
|
||||
*/
|
||||
public function fillFromContent($content)
|
||||
{
|
||||
$this->fillFromSections(MailParser::parse($content));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill model using a view file path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return void
|
||||
*/
|
||||
public function fillFromView($path)
|
||||
{
|
||||
$this->fillFromSections(self::getTemplateSections($path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill model using provided section array.
|
||||
*
|
||||
* @param array $sections
|
||||
* @return void
|
||||
*/
|
||||
protected function fillFromSections($sections)
|
||||
{
|
||||
$this->content_html = array_get($sections, 'html');
|
||||
$this->content_text = array_get($sections, 'text');
|
||||
$this->subject = array_get($sections, 'settings.subject', 'No subject');
|
||||
|
||||
$layoutCode = array_get($sections, 'settings.layout', 'default');
|
||||
$this->layout = MailLayout::findOrMakeLayout($layoutCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get section array from a view file retrieved by code.
|
||||
*
|
||||
* @param string $code
|
||||
* @return array|null
|
||||
*/
|
||||
protected static function getTemplateSections($code)
|
||||
{
|
||||
if (!View::exists($code)) {
|
||||
return null;
|
||||
}
|
||||
$view = View::make($code);
|
||||
return MailParser::parse(FileHelper::get($view->getPath()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a MailTemplate record by code or create one from a view file.
|
||||
*
|
||||
* @param string $code
|
||||
* @return MailTemplate model
|
||||
*/
|
||||
public static function findOrMakeTemplate($code)
|
||||
{
|
||||
$template = self::whereCode($code)->first();
|
||||
|
||||
if (!$template && View::exists($code)) {
|
||||
$template = new self;
|
||||
$template->code = $code;
|
||||
$template->fillFromView($code);
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated see System\Classes\MailManager::registerCallback
|
||||
* Remove if year >= 2019
|
||||
*/
|
||||
public static function registerCallback(callable $callback)
|
||||
{
|
||||
traceLog('MailTemplate::registerCallback is deprecated, use ' . MailManager::class . '::registerCallback instead');
|
||||
MailManager::instance()->registerCallback($callback);
|
||||
}
|
||||
}
|
||||
164
modules/system/models/Parameter.php
Normal file
164
modules/system/models/Parameter.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php namespace System\Models;
|
||||
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Winter\Storm\Database\Model;
|
||||
|
||||
/**
|
||||
* Parameters model
|
||||
* Used for storing internal application parameters.
|
||||
*
|
||||
* @package winter\wn-system-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class Parameter extends Model
|
||||
{
|
||||
use \Winter\Storm\Support\Traits\KeyParser;
|
||||
|
||||
/**
|
||||
* @var string The database table used by the model.
|
||||
*/
|
||||
protected $table = 'system_parameters';
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected static $cache = [];
|
||||
|
||||
/**
|
||||
* @var array List of attribute names which are json encoded and decoded from the database.
|
||||
*/
|
||||
protected $jsonable = ['value'];
|
||||
|
||||
/**
|
||||
* Clear the cache after saving.
|
||||
*/
|
||||
public function afterSave()
|
||||
{
|
||||
Cache::forget(implode('-', [$this->table, $this->namespace, $this->group, $this->item]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a setting value by the module (or plugin) name and setting name.
|
||||
* @param string $key Specifies the setting key value, for example 'system:updates.check'
|
||||
* @param mixed $default The default value to return if the setting doesn't exist in the DB.
|
||||
* @return mixed Returns the setting value loaded from the database or the default value.
|
||||
*/
|
||||
public static function get($key, $default = null)
|
||||
{
|
||||
if (array_key_exists($key, static::$cache)) {
|
||||
return static::$cache[$key];
|
||||
}
|
||||
|
||||
$record = static::findRecord($key);
|
||||
if (!$record) {
|
||||
return static::$cache[$key] = $default;
|
||||
}
|
||||
|
||||
return static::$cache[$key] = $record->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a setting value to the database.
|
||||
* @param string|array $key Specifies the setting key value, for example 'system:updates.check'
|
||||
* @param mixed $value The setting value to store, serializable.
|
||||
* @return true
|
||||
*/
|
||||
public static function set($key, $value = null)
|
||||
{
|
||||
if (is_array($key)) {
|
||||
foreach ($key as $_key => $_value) {
|
||||
static::set($_key, $_value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
$record = static::findRecord($key);
|
||||
if (!$record) {
|
||||
$record = new static;
|
||||
list($namespace, $group, $item) = $record->parseKey($key);
|
||||
$record->namespace = $namespace;
|
||||
$record->group = $group;
|
||||
$record->item = $item;
|
||||
}
|
||||
|
||||
try {
|
||||
$record->value = $value;
|
||||
$record->save();
|
||||
} catch (QueryException $ex) {
|
||||
// SQLSTATE[42S02]: Base table or view not found - migrations haven't run yet
|
||||
if ($ex->getCode() !== '42S02') {
|
||||
Log::error($ex, ['skipDatabaseLog' => true]);
|
||||
}
|
||||
}
|
||||
|
||||
static::$cache[$key] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets a setting value by deleting the record.
|
||||
* @param string $key Specifies the setting key value.
|
||||
* @return bool
|
||||
*/
|
||||
public function reset($key)
|
||||
{
|
||||
$record = static::findRecord($key);
|
||||
if (!$record) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$record->delete();
|
||||
|
||||
unset(static::$cache[$key]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a record (cached)
|
||||
*/
|
||||
public static function findRecord($key): ?static
|
||||
{
|
||||
if (!App::hasDatabase()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$record = new static;
|
||||
|
||||
list($namespace, $group, $item) = $record->parseKey($key);
|
||||
|
||||
$result = null;
|
||||
try {
|
||||
$result = $record
|
||||
->applyKey($key)
|
||||
->remember(5, implode('-', [$record->getTable(), $namespace, $group, $item]))
|
||||
->first();
|
||||
} catch (QueryException $ex) {
|
||||
// SQLSTATE[42S02]: Base table or view not found - migrations haven't run yet
|
||||
if ($ex->getCode() !== '42S02') {
|
||||
Log::error($ex, ['skipDatabaseLog' => true]);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to find a setting record for the specified module (or plugin) name and setting name.
|
||||
* @param string $key Specifies the setting key value, for example 'system:updates.check'
|
||||
* @param mixed $default The default value to return if the setting doesn't exist in the DB.
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function scopeApplyKey($query, $key)
|
||||
{
|
||||
list($namespace, $group, $item) = $this->parseKey($key);
|
||||
|
||||
$query = $query
|
||||
->where('namespace', $namespace)
|
||||
->where('group', $group)
|
||||
->where('item', $item);
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
168
modules/system/models/PluginVersion.php
Normal file
168
modules/system/models/PluginVersion.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php namespace System\Models;
|
||||
|
||||
use Lang;
|
||||
use Model;
|
||||
use System\Classes\PluginManager;
|
||||
|
||||
/**
|
||||
* Stores information about current plugin versions.
|
||||
*
|
||||
* @package winter\wn-system-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class PluginVersion extends Model
|
||||
{
|
||||
public $table = 'system_plugin_versions';
|
||||
|
||||
/**
|
||||
* @var array Guarded fields
|
||||
*/
|
||||
protected $guarded = ['*'];
|
||||
|
||||
/**
|
||||
* @var bool Disable model timestamps.
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* @var array Cache store for version information
|
||||
*/
|
||||
protected static $versionCache;
|
||||
|
||||
/**
|
||||
* @var bool Plugin has been disabled by a missing dependency.
|
||||
*/
|
||||
public $disabledBySystem = false;
|
||||
|
||||
/**
|
||||
* @var bool Plugin has been disabled by the user or configuration.
|
||||
*/
|
||||
public $disabledByConfig = false;
|
||||
|
||||
/**
|
||||
* @var bool If true, plugin exists in the database but not the filesystem.
|
||||
*/
|
||||
public $orphaned = false;
|
||||
|
||||
/**
|
||||
* @var string Plugin name, sourced from plugin details
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var string Plugin description, sourced from plugin details
|
||||
*/
|
||||
public $description;
|
||||
|
||||
/**
|
||||
* @var string Plugin author, sourced from plugin details
|
||||
*/
|
||||
public $author;
|
||||
|
||||
/**
|
||||
* @var string Plugin icon, sourced from plugin details
|
||||
*/
|
||||
public $icon;
|
||||
|
||||
/**
|
||||
* @var string Plugin homepage, sourced from plugin details
|
||||
*/
|
||||
public $homepage;
|
||||
|
||||
/**
|
||||
* The accessors to append to the model's array form.
|
||||
* @var array
|
||||
*/
|
||||
protected $appends = ['slug'];
|
||||
|
||||
/**
|
||||
* After the model is populated
|
||||
*/
|
||||
public function afterFetch()
|
||||
{
|
||||
/*
|
||||
* Override the database columns with the plugin details
|
||||
* found in the plugin registration file.
|
||||
*/
|
||||
$manager = PluginManager::instance();
|
||||
$pluginObj = $manager->findByIdentifier($this->code);
|
||||
|
||||
if ($pluginObj) {
|
||||
$pluginInfo = $pluginObj->pluginDetails();
|
||||
foreach ($pluginInfo as $attribute => $info) {
|
||||
if (property_exists($this, $attribute)) {
|
||||
$this->{$attribute} = Lang::get($info);
|
||||
}
|
||||
}
|
||||
|
||||
$activeFlags = $manager->getPluginFlags($pluginObj);
|
||||
if (!empty($activeFlags)) {
|
||||
foreach ($activeFlags as $flag => $enabled) {
|
||||
if (in_array($flag, [
|
||||
PluginManager::DISABLED_MISSING,
|
||||
PluginManager::DISABLED_REPLACED,
|
||||
PluginManager::DISABLED_REPLACEMENT_FAILED,
|
||||
PluginManager::DISABLED_MISSING_DEPENDENCIES,
|
||||
])) {
|
||||
$this->disabledBySystem = true;
|
||||
}
|
||||
|
||||
if ($flag === PluginManager::DISABLED_BY_CONFIG) {
|
||||
$this->disabledByConfig = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->name = $this->code;
|
||||
$this->description = Lang::get('system::lang.plugins.unknown_plugin');
|
||||
$this->orphaned = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the plugin should be updated by the system.
|
||||
*/
|
||||
public function getIsUpdatableAttribute(): bool
|
||||
{
|
||||
return !$this->is_disabled && !$this->disabledBySystem && !$this->disabledByConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only include enabled plugins
|
||||
* @param $query
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function scopeApplyEnabled($query)
|
||||
{
|
||||
return $query->where('is_disabled', '!=', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current version for a plugin
|
||||
*/
|
||||
public static function getVersion(string $pluginCode): ?string
|
||||
{
|
||||
if (self::$versionCache === null) {
|
||||
self::$versionCache = self::lists('version', 'code');
|
||||
}
|
||||
|
||||
return self::$versionCache[$pluginCode] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the slug attribute.
|
||||
*/
|
||||
public function getSlugAttribute(): string
|
||||
{
|
||||
return self::makeSlug($this->code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a slug for the plugin.
|
||||
*/
|
||||
public static function makeSlug(string $code): string
|
||||
{
|
||||
return strtolower(str_replace('.', '-', $code));
|
||||
}
|
||||
}
|
||||
65
modules/system/models/RequestLog.php
Normal file
65
modules/system/models/RequestLog.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php namespace System\Models;
|
||||
|
||||
use App;
|
||||
use Model;
|
||||
use Request;
|
||||
|
||||
/**
|
||||
* Model for logging 404 errors
|
||||
*
|
||||
* @package winter\wn-system-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class RequestLog extends Model
|
||||
{
|
||||
/**
|
||||
* @var string The database table used by the model.
|
||||
*/
|
||||
protected $table = 'system_request_logs';
|
||||
|
||||
/**
|
||||
* @var array The attributes that aren't mass assignable.
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* @var array List of attribute names which are json encoded and decoded from the database.
|
||||
*/
|
||||
protected $jsonable = ['referer'];
|
||||
|
||||
/**
|
||||
* Creates a log record
|
||||
* @return self
|
||||
*/
|
||||
public static function add($statusCode = 404)
|
||||
{
|
||||
if (!App::hasDatabase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LogSetting::get('log_requests')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$record = static::firstOrNew([
|
||||
'url' => substr(Request::fullUrl(), 0, 191),
|
||||
'status_code' => $statusCode,
|
||||
]);
|
||||
|
||||
if ($referer = Request::header('referer')) {
|
||||
$referers = (array) $record->referer ?: [];
|
||||
$referers[] = $referer;
|
||||
$record->referer = $referers;
|
||||
}
|
||||
|
||||
if (!$record->exists) {
|
||||
$record->count = 1;
|
||||
$record->save();
|
||||
}
|
||||
else {
|
||||
$record->increment('count');
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
}
|
||||
17
modules/system/models/Revision.php
Normal file
17
modules/system/models/Revision.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php namespace System\Models;
|
||||
|
||||
use Winter\Storm\Database\Models\Revision as RevisionBase;
|
||||
|
||||
/**
|
||||
* Revision history model
|
||||
*
|
||||
* @package winter\wn-system-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class Revision extends RevisionBase
|
||||
{
|
||||
/**
|
||||
* @var string The database table used by the model.
|
||||
*/
|
||||
public $table = 'system_revisions';
|
||||
}
|
||||
22
modules/system/models/eventlog/columns.yaml
Normal file
22
modules/system/models/eventlog/columns.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
# ===================================
|
||||
# Column Definitions
|
||||
# ===================================
|
||||
|
||||
columns:
|
||||
id:
|
||||
label: system::lang.event_log.id
|
||||
searchable: yes
|
||||
width: 75px
|
||||
|
||||
created_at:
|
||||
label: system::lang.event_log.created_at
|
||||
searchable: yes
|
||||
width: 160px
|
||||
type: timetense
|
||||
|
||||
message:
|
||||
label: system::lang.event_log.message
|
||||
searchable: yes
|
||||
type: partial
|
||||
path: message_column
|
||||
cssClass: column-break-word
|
||||
15
modules/system/models/eventlog/fields.yaml
Normal file
15
modules/system/models/eventlog/fields.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
# ===================================
|
||||
# Field Definitions
|
||||
# ===================================
|
||||
|
||||
fields:
|
||||
|
||||
message:
|
||||
type: partial
|
||||
path: field_message
|
||||
containerAttributes:
|
||||
data-plugin: exception-beautifier
|
||||
|
||||
details:
|
||||
type: partial
|
||||
path: field_details
|
||||
14
modules/system/models/file/fields.yaml
Normal file
14
modules/system/models/file/fields.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
# ===================================
|
||||
# Field Definitions
|
||||
# ===================================
|
||||
|
||||
fields:
|
||||
title:
|
||||
type: text
|
||||
placeholder: backend::lang.fileupload.title_label
|
||||
span: full
|
||||
description:
|
||||
type: textarea
|
||||
placeholder: backend::lang.fileupload.description_label
|
||||
span: full
|
||||
size: tiny
|
||||
25
modules/system/models/logsetting/fields.yaml
Normal file
25
modules/system/models/logsetting/fields.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
# ===================================
|
||||
# Field Definitions
|
||||
# ===================================
|
||||
|
||||
tabs:
|
||||
defaultTab: system::lang.log.default_tab
|
||||
fields:
|
||||
|
||||
log_requests:
|
||||
label: system::lang.log.log_requests
|
||||
span: auto
|
||||
type: switch
|
||||
comment: system::lang.log.log_requests_comment
|
||||
|
||||
log_theme:
|
||||
label: system::lang.log.log_theme
|
||||
span: auto
|
||||
type: switch
|
||||
comment: system::lang.log.log_theme_comment
|
||||
|
||||
log_events:
|
||||
label: system::lang.log.log_events
|
||||
span: auto
|
||||
type: switch
|
||||
comment: system::lang.log.log_events_comment
|
||||
304
modules/system/models/mailbrandsetting/custom.less
Normal file
304
modules/system/models/mailbrandsetting/custom.less
Normal file
@@ -0,0 +1,304 @@
|
||||
/* Base */
|
||||
|
||||
body, body *:not(html):not(style):not(br):not(tr):not(code) {
|
||||
font-family: Avenir, Helvetica, sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: @body-bg;
|
||||
color: @text-color;
|
||||
height: 100%;
|
||||
hyphens: auto;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
-moz-hyphens: auto;
|
||||
-ms-word-break: break-word;
|
||||
width: 100% !important;
|
||||
-webkit-hyphens: auto;
|
||||
-webkit-text-size-adjust: none;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
p,
|
||||
ul,
|
||||
ol,
|
||||
blockquote {
|
||||
line-height: 1.4;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
a {
|
||||
color: @link-color;
|
||||
}
|
||||
|
||||
a img {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
|
||||
h1 {
|
||||
color: @heading-color;
|
||||
font-size: 19px;
|
||||
font-weight: bold;
|
||||
margin-top: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: @heading-color;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-top: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: @heading-color;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-top: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
p {
|
||||
color: @text-color;
|
||||
font-size: 16px;
|
||||
line-height: 1.5em;
|
||||
margin-top: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
code {
|
||||
color: @text-color;
|
||||
font-size: 16px;
|
||||
line-height: 1.5em;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
p.sub {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.break-all, .break-all * {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
|
||||
.wrapper {
|
||||
background-color: @body-bg;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 100%;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
|
||||
.header {
|
||||
padding: 25px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header a, .header span {
|
||||
color: @header-color;
|
||||
font-size: 19px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Body */
|
||||
|
||||
.body {
|
||||
background-color: @content-bg;
|
||||
border-bottom: 1px solid @body-border-color;
|
||||
border-top: 1px solid @body-border-color;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 100%;
|
||||
}
|
||||
|
||||
.inner-body {
|
||||
background-color: @content-inner-bg;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
width: 570px;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 570px;
|
||||
}
|
||||
|
||||
/* Subcopy */
|
||||
|
||||
.subcopy {
|
||||
border-top: 1px solid @subcopy-border-color;
|
||||
margin-top: 25px;
|
||||
padding-top: 25px;
|
||||
}
|
||||
|
||||
.subcopy p {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
|
||||
.footer {
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
width: 570px;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 570px;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
color: @footer-color;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
|
||||
.table table {
|
||||
border-collapse: collapse;
|
||||
margin: 30px auto;
|
||||
width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 100%;
|
||||
}
|
||||
|
||||
.table th {
|
||||
border-bottom: 1px solid @table-border-color;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.table td {
|
||||
color: @text-color;
|
||||
font-size: 15px;
|
||||
line-height: 18px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.content-cell {
|
||||
padding: 35px;
|
||||
}
|
||||
|
||||
.wrapper.layout-system .content-cell {
|
||||
padding: 35px 0;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
|
||||
.action {
|
||||
margin: 30px auto;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 100%;
|
||||
}
|
||||
|
||||
.button {
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16);
|
||||
color: @button-text-color;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
|
||||
.button-primary {
|
||||
background-color: @button-primary-bg;
|
||||
border-top: 10px solid @button-primary-bg;
|
||||
border-right: 18px solid @button-primary-bg;
|
||||
border-bottom: 10px solid @button-primary-bg;
|
||||
border-left: 18px solid @button-primary-bg;
|
||||
}
|
||||
|
||||
.button-positive {
|
||||
background-color: @button-positive-bg;
|
||||
border-top: 10px solid @button-positive-bg;
|
||||
border-right: 18px solid @button-positive-bg;
|
||||
border-bottom: 10px solid @button-positive-bg;
|
||||
border-left: 18px solid @button-positive-bg;
|
||||
}
|
||||
|
||||
.button-negative {
|
||||
background-color: @button-negative-bg;
|
||||
border-top: 10px solid @button-negative-bg;
|
||||
border-right: 18px solid @button-negative-bg;
|
||||
border-bottom: 10px solid @button-negative-bg;
|
||||
border-left: 18px solid @button-negative-bg;
|
||||
}
|
||||
|
||||
/* Panels */
|
||||
|
||||
.panel {
|
||||
margin: 0 0 21px;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
background-color: @panel-bg;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.panel-item {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.panel-item p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/* Promotions */
|
||||
|
||||
.promotion {
|
||||
background-color: @promotion-bg;
|
||||
border: 2px dashed @promotion-border-color;
|
||||
margin: 0;
|
||||
margin-bottom: 25px;
|
||||
margin-top: 25px;
|
||||
padding: 24px;
|
||||
width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 100%;
|
||||
}
|
||||
|
||||
.promotion h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.promotion p {
|
||||
font-size: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.promotion p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
122
modules/system/models/mailbrandsetting/fields.yaml
Normal file
122
modules/system/models/mailbrandsetting/fields.yaml
Normal file
@@ -0,0 +1,122 @@
|
||||
# ===================================
|
||||
# Field Definitions
|
||||
# ===================================
|
||||
|
||||
fields:
|
||||
|
||||
_mail_preview:
|
||||
type: partial
|
||||
path: field_mail_preview
|
||||
|
||||
secondaryTabs:
|
||||
fields:
|
||||
|
||||
_section_background:
|
||||
label: system::lang.mail_brand.fields._section_background
|
||||
type: section
|
||||
|
||||
body_bg:
|
||||
label: system::lang.mail_brand.fields.body_bg
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
content_bg:
|
||||
label: system::lang.mail_brand.fields.content_bg
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
content_inner_bg:
|
||||
label: system::lang.mail_brand.fields.content_inner_bg
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
_section_buttons:
|
||||
label: system::lang.mail_brand.fields._section_buttons
|
||||
type: section
|
||||
|
||||
button_text_color:
|
||||
label: system::lang.mail_brand.fields.button_text_color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
button_primary_bg:
|
||||
label: system::lang.mail_brand.fields.button_primary_bg
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
button_positive_bg:
|
||||
label: system::lang.mail_brand.fields.button_positive_bg
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
button_negative_bg:
|
||||
label: system::lang.mail_brand.fields.button_negative_bg
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
_section_type:
|
||||
label: system::lang.mail_brand.fields._section_type
|
||||
type: section
|
||||
|
||||
header_color:
|
||||
label: system::lang.mail_brand.fields.header_color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
heading_color:
|
||||
label: system::lang.mail_brand.fields.heading_color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
text_color:
|
||||
label: system::lang.mail_brand.fields.text_color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
link_color:
|
||||
label: system::lang.mail_brand.fields.link_color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
footer_color:
|
||||
label: system::lang.mail_brand.fields.footer_color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
_section_borders:
|
||||
label: system::lang.mail_brand.fields._section_borders
|
||||
type: section
|
||||
|
||||
body_border_color:
|
||||
label: system::lang.mail_brand.fields.body_border_color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
subcopy_border_color:
|
||||
label: system::lang.mail_brand.fields.subcopy_border_color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
table_border_color:
|
||||
label: system::lang.mail_brand.fields.table_border_color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
_section_components:
|
||||
label: system::lang.mail_brand.fields._section_components
|
||||
type: section
|
||||
|
||||
panel_bg:
|
||||
label: system::lang.mail_brand.fields.panel_bg
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
promotion_bg:
|
||||
label: system::lang.mail_brand.fields.promotion_bg
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
promotion_border_color:
|
||||
label: system::lang.mail_brand.fields.promotion_border_color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
45
modules/system/models/mailbrandsetting/sample_template.php
Normal file
45
modules/system/models/mailbrandsetting/sample_template.php
Normal file
@@ -0,0 +1,45 @@
|
||||
# {{texts.heading}} 1
|
||||
|
||||
{{texts.paragraph|raw}}
|
||||
|
||||
## {{texts.heading}} 2
|
||||
|
||||
{% partial 'table' body %}
|
||||
| {{texts.table.item}} | {{texts.table.description}} | {{texts.table.price}} |
|
||||
|:------------- |:-------------:| --------:|
|
||||
| {{texts.table.item}} 1 | {{texts.table.centered}} | $10 |
|
||||
| {{texts.table.item}} 2 | {{texts.table.right_aligned}} | $20 |
|
||||
{% endpartial %}
|
||||
|
||||
### {{texts.heading}} 3
|
||||
|
||||
{{texts.paragraph|raw}}
|
||||
|
||||
{% partial 'button' url='javascript:;' body %}
|
||||
{{texts.buttons.primary}}
|
||||
{% endpartial %}
|
||||
|
||||
{% partial 'button' type='positive' url='javascript:;' body %}
|
||||
{{texts.buttons.positive}}
|
||||
{% endpartial %}
|
||||
|
||||
{% partial 'button' type='negative' url='javascript:;' body %}
|
||||
{{texts.buttons.negative}}
|
||||
{% endpartial %}
|
||||
|
||||
{% partial 'panel' body %}
|
||||
{{texts.panel}}
|
||||
{% endpartial %}
|
||||
|
||||
{{texts.more}}
|
||||
|
||||
{% partial 'promotion' body %}
|
||||
{{texts.promotion}}
|
||||
{% endpartial %}
|
||||
|
||||
{{texts.thanks}},
|
||||
{{ appName }}
|
||||
|
||||
{% partial 'subcopy' body %}
|
||||
{{texts.subcopy}}
|
||||
{% endpartial %}
|
||||
13
modules/system/models/maillayout/columns.yaml
Normal file
13
modules/system/models/maillayout/columns.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
# ===================================
|
||||
# Column Definitions
|
||||
# ===================================
|
||||
|
||||
columns:
|
||||
|
||||
name:
|
||||
label: system::lang.mail_templates.name
|
||||
searchable: true
|
||||
|
||||
code:
|
||||
label: system::lang.mail_templates.code
|
||||
searchable: true
|
||||
47
modules/system/models/maillayout/fields.yaml
Normal file
47
modules/system/models/maillayout/fields.yaml
Normal file
@@ -0,0 +1,47 @@
|
||||
# ===================================
|
||||
# Field Definitions
|
||||
# ===================================
|
||||
|
||||
fields:
|
||||
|
||||
code:
|
||||
label: system::lang.mail_templates.code
|
||||
comment: system::lang.mail_templates.code_comment
|
||||
span: left
|
||||
context: create
|
||||
|
||||
name@create:
|
||||
label: system::lang.mail_templates.name
|
||||
span: right
|
||||
|
||||
name@update:
|
||||
label: system::lang.mail_templates.name
|
||||
|
||||
secondaryTabs:
|
||||
fields:
|
||||
|
||||
content_html:
|
||||
type: codeeditor
|
||||
size: giant
|
||||
tab: system::lang.mail_templates.content_html
|
||||
language: html
|
||||
stretch: true
|
||||
|
||||
content_css:
|
||||
type: codeeditor
|
||||
size: giant
|
||||
tab: system::lang.mail_templates.content_css
|
||||
language: css
|
||||
stretch: true
|
||||
|
||||
content_text:
|
||||
type: textarea
|
||||
size: giant
|
||||
tab: system::lang.mail_templates.content_text
|
||||
stretch: true
|
||||
|
||||
options[disable_auto_inline_css]:
|
||||
label: system::lang.mail_templates.disable_auto_inline_css
|
||||
type: checkbox
|
||||
tab: system::lang.mail_templates.options
|
||||
default: false
|
||||
13
modules/system/models/mailpartial/columns.yaml
Normal file
13
modules/system/models/mailpartial/columns.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
# ===================================
|
||||
# Column Definitions
|
||||
# ===================================
|
||||
|
||||
columns:
|
||||
|
||||
name:
|
||||
label: system::lang.mail_templates.name
|
||||
searchable: true
|
||||
|
||||
code:
|
||||
label: system::lang.mail_templates.code
|
||||
searchable: true
|
||||
34
modules/system/models/mailpartial/fields.yaml
Normal file
34
modules/system/models/mailpartial/fields.yaml
Normal file
@@ -0,0 +1,34 @@
|
||||
# ===================================
|
||||
# Field Definitions
|
||||
# ===================================
|
||||
|
||||
fields:
|
||||
|
||||
code:
|
||||
label: system::lang.mail_templates.code
|
||||
comment: system::lang.mail_templates.code_comment
|
||||
span: left
|
||||
context: create
|
||||
|
||||
name@create:
|
||||
label: system::lang.mail_templates.name
|
||||
span: right
|
||||
|
||||
name@update:
|
||||
label: system::lang.mail_templates.name
|
||||
|
||||
secondaryTabs:
|
||||
fields:
|
||||
|
||||
content_html:
|
||||
type: codeeditor
|
||||
size: giant
|
||||
tab: system::lang.mail_templates.content_html
|
||||
language: html
|
||||
stretch: true
|
||||
|
||||
content_text:
|
||||
type: textarea
|
||||
size: giant
|
||||
tab: system::lang.mail_templates.content_text
|
||||
stretch: true
|
||||
1
modules/system/models/mailsetting/_drivers_hint.php
Normal file
1
modules/system/models/mailsetting/_drivers_hint.php
Normal file
@@ -0,0 +1 @@
|
||||
<?= trans('system::lang.mail.drivers_hint_content', ['url'=> 'https://wintercms.com/docs/services/mail#drivers']); ?>
|
||||
9
modules/system/models/mailsetting/_send_test_button.php
Normal file
9
modules/system/models/mailsetting/_send_test_button.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-success"
|
||||
data-request="onTest"
|
||||
data-request-data="redirect:0"
|
||||
data-load-indicator="<?= e(trans('system::lang.mail_templates.sending')) ?>"
|
||||
data-request-confirm="<?= e(trans('system::lang.settings.test_confirm', [ 'email' => e(BackendAuth::getUser()->email)])) ?>">
|
||||
<?= e(trans('system::lang.mail_templates.test_send')) ?>
|
||||
</button>
|
||||
98
modules/system/models/mailsetting/fields.yaml
Normal file
98
modules/system/models/mailsetting/fields.yaml
Normal file
@@ -0,0 +1,98 @@
|
||||
# ===================================
|
||||
# Field Definitions
|
||||
# ===================================
|
||||
|
||||
tabs:
|
||||
fields:
|
||||
sender_name:
|
||||
label: system::lang.mail.sender_name
|
||||
span: auto
|
||||
tab: system::lang.mail.general
|
||||
|
||||
sender_email:
|
||||
label: system::lang.mail.sender_email
|
||||
span: auto
|
||||
type: email
|
||||
tab: system::lang.mail.general
|
||||
|
||||
driver_hint:
|
||||
type: hint
|
||||
path: ~/modules/system/models/mailsetting/_drivers_hint.php
|
||||
tab: system::lang.mail.general
|
||||
|
||||
send_mode:
|
||||
label: system::lang.mail.method
|
||||
type: balloon-selector
|
||||
tab: system::lang.mail.general
|
||||
|
||||
smtp_address:
|
||||
label: system::lang.mail.smtp_address
|
||||
tab: system::lang.mail.general
|
||||
span: left
|
||||
trigger:
|
||||
action: show
|
||||
field: send_mode
|
||||
condition: value[smtp]
|
||||
|
||||
smtp_port:
|
||||
label: system::lang.mail.smtp_port
|
||||
type: number
|
||||
tab: system::lang.mail.general
|
||||
span: auto
|
||||
trigger:
|
||||
action: show
|
||||
field: send_mode
|
||||
condition: value[smtp]
|
||||
|
||||
smtp_authorization:
|
||||
type: checkbox
|
||||
label: system::lang.mail.smtp_authorization
|
||||
tab: system::lang.mail.general
|
||||
comment: system::lang.mail.smtp_authorization_comment
|
||||
trigger:
|
||||
action: show
|
||||
field: send_mode
|
||||
condition: value[smtp]
|
||||
|
||||
smtp_user:
|
||||
label: system::lang.mail.smtp_username
|
||||
tab: system::lang.mail.general
|
||||
span: left
|
||||
dependsOn:
|
||||
- send_mode
|
||||
- smtp_authorization
|
||||
|
||||
smtp_password:
|
||||
label: system::lang.mail.smtp_password
|
||||
tab: system::lang.mail.general
|
||||
type: sensitive
|
||||
span: right
|
||||
dependsOn:
|
||||
- send_mode
|
||||
- smtp_authorization
|
||||
|
||||
sendmail_path:
|
||||
label: system::lang.mail.sendmail_path
|
||||
commentAbove: system::lang.mail.sendmail_path_comment
|
||||
tab: system::lang.mail.general
|
||||
trigger:
|
||||
action: show
|
||||
field: send_mode
|
||||
condition: value[sendmail]
|
||||
|
||||
failover_mailers:
|
||||
label: system::lang.mail.failover_mailers
|
||||
placeholder: system::lang.mail.failover_mailers_placeholer
|
||||
type: taglist
|
||||
options: getFailoverMailersOptions
|
||||
customTags: false
|
||||
tab: system::lang.mail.general
|
||||
trigger:
|
||||
action: show
|
||||
field: send_mode
|
||||
condition: value[failover]
|
||||
|
||||
_send_test:
|
||||
type: partial
|
||||
path: ~/modules/system/models/mailsetting/_send_test_button.php
|
||||
tab: system::lang.mail.general
|
||||
23
modules/system/models/mailtemplate/columns.yaml
Normal file
23
modules/system/models/mailtemplate/columns.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
# ===================================
|
||||
# Column Definitions
|
||||
# ===================================
|
||||
|
||||
columns:
|
||||
|
||||
code:
|
||||
label: system::lang.mail_templates.code
|
||||
searchable: true
|
||||
|
||||
subject:
|
||||
label: system::lang.mail_templates.subject
|
||||
searchable: true
|
||||
|
||||
description:
|
||||
label: system::lang.mail_templates.description
|
||||
searchable: true
|
||||
|
||||
layout:
|
||||
label: system::lang.mail_templates.layout
|
||||
relation: layout
|
||||
select: name
|
||||
sortable: false
|
||||
45
modules/system/models/mailtemplate/fields.yaml
Normal file
45
modules/system/models/mailtemplate/fields.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
# ===================================
|
||||
# Field Definitions
|
||||
# ===================================
|
||||
|
||||
fields:
|
||||
|
||||
layout:
|
||||
label: system::lang.mail_templates.layout
|
||||
type: relation
|
||||
emptyOption: system::lang.mail_templates.no_layout
|
||||
|
||||
code:
|
||||
label: system::lang.mail_templates.code
|
||||
comment: system::lang.mail_templates.code_comment
|
||||
span: left
|
||||
context: create
|
||||
|
||||
subject@create:
|
||||
label: system::lang.mail_templates.subject
|
||||
comment: system::lang.mail_templates.subject_comment
|
||||
span: right
|
||||
|
||||
subject@update:
|
||||
label: system::lang.mail_templates.subject
|
||||
|
||||
description:
|
||||
label: system::lang.mail_templates.description
|
||||
type: textarea
|
||||
size: tiny
|
||||
|
||||
secondaryTabs:
|
||||
fields:
|
||||
|
||||
content_html:
|
||||
type: markdown
|
||||
size: giant
|
||||
tab: system::lang.mail_templates.content_html
|
||||
safe: true
|
||||
stretch: true
|
||||
|
||||
content_text:
|
||||
type: textarea
|
||||
size: giant
|
||||
tab: system::lang.mail_templates.content_text
|
||||
stretch: true
|
||||
21
modules/system/models/pluginversion/columns.yaml
Normal file
21
modules/system/models/pluginversion/columns.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
# ===================================
|
||||
# Column Definitions
|
||||
# ===================================
|
||||
|
||||
columns:
|
||||
|
||||
name:
|
||||
label: system::lang.updates.plugin_name
|
||||
sortable: false
|
||||
|
||||
description:
|
||||
label: system::lang.updates.plugin_description
|
||||
sortable: false
|
||||
|
||||
version:
|
||||
label: system::lang.updates.plugin_version
|
||||
sortable: false
|
||||
|
||||
author:
|
||||
label: system::lang.updates.plugin_author
|
||||
sortable: false
|
||||
27
modules/system/models/pluginversion/columns_manage.yaml
Normal file
27
modules/system/models/pluginversion/columns_manage.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
# ===================================
|
||||
# Column Definitions
|
||||
# ===================================
|
||||
|
||||
columns:
|
||||
|
||||
code:
|
||||
label: system::lang.updates.plugin_code
|
||||
sortable: false
|
||||
type: partial
|
||||
path: column_code
|
||||
|
||||
version:
|
||||
label: system::lang.updates.plugin_version
|
||||
sortable: false
|
||||
|
||||
is_unfrozen:
|
||||
label: system::lang.plugins.unfrozen
|
||||
type: partial
|
||||
path: is_unfrozen
|
||||
sortable: false
|
||||
|
||||
is_enabled:
|
||||
label: system::lang.plugins.enabled
|
||||
type: partial
|
||||
path: is_enabled
|
||||
sortable: false
|
||||
18
modules/system/models/requestlog/columns.yaml
Normal file
18
modules/system/models/requestlog/columns.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
# ===================================
|
||||
# Column Definitions
|
||||
# ===================================
|
||||
|
||||
columns:
|
||||
status_code:
|
||||
label: system::lang.request_log.status_code
|
||||
searchable: yes
|
||||
width: 100px
|
||||
|
||||
url:
|
||||
label: system::lang.request_log.url
|
||||
searchable: yes
|
||||
cssClass: column-break-word
|
||||
|
||||
count:
|
||||
label: system::lang.request_log.count
|
||||
width: 150px
|
||||
13
modules/system/models/requestlog/fields.yaml
Normal file
13
modules/system/models/requestlog/fields.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
# ===================================
|
||||
# Field Definitions
|
||||
# ===================================
|
||||
|
||||
fields:
|
||||
|
||||
url:
|
||||
label: system::lang.request_log.url
|
||||
|
||||
referer:
|
||||
label: system::lang.request_log.referer
|
||||
type: partial
|
||||
path: referer_field
|
||||
Reference in New Issue
Block a user