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:
281
modules/backend/models/EditorSetting.php
Normal file
281
modules/backend/models/EditorSetting.php
Normal file
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
|
||||
namespace Backend\Models;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Less_Parser;
|
||||
use Winter\Storm\Database\Model;
|
||||
use Winter\Storm\Parse\Assetic\Filter\LessImportResolver;
|
||||
use Winter\Storm\Support\Facades\File;
|
||||
|
||||
/**
|
||||
* Editor settings that affect all users
|
||||
*
|
||||
* @package winter\wn-backend-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class EditorSetting 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 = 'backend_editor_settings';
|
||||
|
||||
/**
|
||||
* @var mixed Settings form field definitions
|
||||
*/
|
||||
public $settingsFields = 'fields.yaml';
|
||||
|
||||
/**
|
||||
* @var string The key to store rendered CSS in the cache under
|
||||
*/
|
||||
public $cacheKey = 'backend::editor.custom_css';
|
||||
|
||||
protected $defaultHtmlAllowEmptyTags = 'textarea, a, iframe, object, video, style, script, .fa, .fr-emoticon, .fr-inner, path, line, hr, i';
|
||||
|
||||
protected $defaultHtmlAllowTags = 'a, abbr, address, area, article, aside, audio, b, bdi, bdo, blockquote, br, button, canvas, caption, cite, code, col, colgroup, datalist, dd, del, details, dfn, dialog, div, dl, dt, em, embed, fieldset, figcaption, figure, footer, form, h1, h2, h3, h4, h5, h6, header, hgroup, hr, i, iframe, img, input, ins, kbd, keygen, label, legend, li, link, main, map, mark, menu, menuitem, meter, nav, noscript, object, ol, optgroup, option, output, p, param, pre, progress, queue, rp, rt, ruby, s, samp, script, style, section, select, small, source, span, strike, strong, sub, summary, sup, table, tbody, td, textarea, tfoot, th, thead, time, title, tr, track, u, ul, var, video, wbr';
|
||||
|
||||
protected $defaultHtmlAllowAttributes = 'accept, accept-charset, accesskey, action, align, allowfullscreen, allowtransparency, alt, aria-.*, async, autocomplete, autofocus, autoplay, autosave, background, bgcolor, border, charset, cellpadding, cellspacing, checked, cite, class, color, cols, colspan, content, contenteditable, contextmenu, controls, coords, data, data-.*, datetime, default, defer, dir, dirname, disabled, download, draggable, dropzone, enctype, for, form, formaction, frameborder, headers, height, hidden, high, href, hreflang, http-equiv, icon, id, ismap, itemprop, keytype, kind, label, lang, language, list, loop, low, max, maxlength, media, method, min, mozallowfullscreen, multiple, muted, name, novalidate, open, optimum, pattern, ping, placeholder, playsinline, poster, preload, pubdate, radiogroup, readonly, rel, required, reversed, rows, rowspan, sandbox, scope, scoped, scrolling, seamless, selected, shape, size, sizes, span, src, srcdoc, srclang, srcset, start, step, summary, spellcheck, style, tabindex, target, title, type, translate, usemap, value, valign, webkitallowfullscreen, width, wrap';
|
||||
|
||||
protected $defaultHtmlNoWrapTags = 'figure, script, style';
|
||||
|
||||
protected $defaultHtmlRemoveTags = 'script, style, base';
|
||||
|
||||
protected $defaultHtmlLineBreakerTags = 'figure, table, hr, iframe, form, dl';
|
||||
|
||||
protected $defaultHtmlStyleImage = [
|
||||
'oc-img-rounded' => 'Rounded',
|
||||
'oc-img-bordered' => 'Bordered',
|
||||
];
|
||||
|
||||
protected $defaultHtmlStyleLink = [
|
||||
'oc-link-green' => 'Green',
|
||||
'oc-link-strong' => 'Strong',
|
||||
];
|
||||
|
||||
protected $defaultHtmlStyleParagraph = [
|
||||
'oc-text-bordered' => 'Bordered',
|
||||
'oc-text-gray' => 'Gray',
|
||||
'oc-text-spaced' => 'Spaced',
|
||||
'oc-text-uppercase' => 'Uppercase',
|
||||
];
|
||||
|
||||
protected $defaultHtmlStyleTable = [
|
||||
'oc-dashed-borders' => 'Dashed Borders',
|
||||
'oc-alternate-rows' => 'Alternate Rows',
|
||||
];
|
||||
|
||||
protected $defaultHtmlStyleTableCell = [
|
||||
'oc-cell-highlighted' => 'Highlighted',
|
||||
'oc-cell-thick-border' => 'Thick Border',
|
||||
];
|
||||
|
||||
protected $defaultHtmlParagraphFormats = [
|
||||
'N' => 'Normal',
|
||||
'H1' => 'Heading 1',
|
||||
'H2' => 'Heading 2',
|
||||
'H3' => 'Heading 3',
|
||||
'H4' => 'Heading 4',
|
||||
'PRE' => 'Code',
|
||||
];
|
||||
|
||||
/**
|
||||
* Editor toolbar presets for Froala.
|
||||
*/
|
||||
protected $editorToolbarPresets = [
|
||||
'default' => 'paragraphFormat, paragraphStyle, quote, bold, italic, align, formatOL, formatUL, insertTable,
|
||||
insertLink, insertImage, insertVideo, insertAudio, insertFile, insertHR, html',
|
||||
'minimal' => 'paragraphFormat, bold, italic, underline, |, insertLink, insertImage, |, html',
|
||||
'full' => 'undo, redo, |, bold, italic, underline, |, paragraphFormat, paragraphStyle, inlineStyle, |,
|
||||
strikeThrough, subscript, superscript, clearFormatting, |, fontFamily, fontSize, |, color,
|
||||
emoticons, -, selectAll, |, align, formatOL, formatUL, outdent, indent, quote, |, insertHR,
|
||||
insertLink, insertImage, insertVideo, insertAudio, insertFile, insertTable, |, selectAll,
|
||||
html, fullscreen',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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->html_allow_empty_tags = $this->defaultHtmlAllowEmptyTags;
|
||||
$this->html_allow_tags = $this->defaultHtmlAllowTags;
|
||||
$this->html_allow_attributes = $this->defaultHtmlAllowAttributes;
|
||||
$this->html_no_wrap_tags = $this->defaultHtmlNoWrapTags;
|
||||
$this->html_remove_tags = $this->defaultHtmlRemoveTags;
|
||||
$this->html_line_breaker_tags = $this->defaultHtmlLineBreakerTags;
|
||||
$this->html_custom_styles = File::get(base_path().'/modules/backend/models/editorsetting/default_styles.less');
|
||||
$this->html_style_image = $this->makeStylesForTable($this->defaultHtmlStyleImage);
|
||||
$this->html_style_link = $this->makeStylesForTable($this->defaultHtmlStyleLink);
|
||||
$this->html_style_paragraph = $this->makeStylesForTable($this->defaultHtmlStyleParagraph);
|
||||
$this->html_style_table = $this->makeStylesForTable($this->defaultHtmlStyleTable);
|
||||
$this->html_style_table_cell = $this->makeStylesForTable($this->defaultHtmlStyleTableCell);
|
||||
$this->html_paragraph_formats = $this->makeFormatsForTable($this->defaultHtmlParagraphFormats);
|
||||
}
|
||||
|
||||
public function afterFetch()
|
||||
{
|
||||
if (!isset($this->value['html_paragraph_formats'])) {
|
||||
$this->html_paragraph_formats = $this->makeFormatsForTable($this->defaultHtmlParagraphFormats);
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function afterSave()
|
||||
{
|
||||
Cache::forget(self::instance()->cacheKey);
|
||||
}
|
||||
|
||||
protected function makeStylesForTable($arr)
|
||||
{
|
||||
$count = 0;
|
||||
|
||||
return array_build($arr, function ($key, $value) use (&$count) {
|
||||
return [$count++, ['class_label' => $value, 'class_name' => $key]];
|
||||
});
|
||||
}
|
||||
|
||||
protected function makeFormatsForTable($arr)
|
||||
{
|
||||
$count = 0;
|
||||
|
||||
return array_build($arr, function ($key, $value) use (&$count) {
|
||||
return [$count++, ['format_label' => $value, 'format_tag' => $key]];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as getConfigured but uses a special structure for styles.
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getConfiguredStyles($key, $default = null)
|
||||
{
|
||||
return static::getConfiguredArray($key, $default, function ($key, $value) {
|
||||
if (array_has($value, ['class_name', 'class_label'])) {
|
||||
return [
|
||||
array_get($value, 'class_name'),
|
||||
array_get($value, 'class_label')
|
||||
];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as getConfigured but uses a special structure for paragraph formats.
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getConfiguredFormats($key, $default = null)
|
||||
{
|
||||
return static::getConfiguredArray($key, $default, function ($key, $value) {
|
||||
if (array_has($value, ['format_tag', 'format_label'])) {
|
||||
return [
|
||||
array_get($value, 'format_tag'),
|
||||
array_get($value, 'format_label')
|
||||
];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected static function getConfiguredArray($key, $default = null, $callback = null)
|
||||
{
|
||||
$instance = static::instance();
|
||||
|
||||
$value = $instance->get($key);
|
||||
|
||||
$defaultValue = $instance->getDefaultValue($key);
|
||||
|
||||
if (is_array($value) && is_callable($callback)) {
|
||||
$value = array_filter(array_build($value, $callback));
|
||||
}
|
||||
|
||||
return $value != $defaultValue ? $value : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value only if it differs from the default value.
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getConfigured($key, $default = null)
|
||||
{
|
||||
$instance = static::instance();
|
||||
|
||||
$value = $instance->get($key);
|
||||
|
||||
$defaultValue = $instance->getDefaultValue($key);
|
||||
|
||||
return $value != $defaultValue ? $value : $default;
|
||||
}
|
||||
|
||||
public function getDefaultValue($attribute)
|
||||
{
|
||||
$property = 'default'.studly_case($attribute);
|
||||
|
||||
return $this->$property;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the editor toolbar presets without line breaks.
|
||||
* @return array
|
||||
*/
|
||||
public function getEditorToolbarPresets()
|
||||
{
|
||||
return array_map(function ($value) {
|
||||
return preg_replace('/\s+/', ' ', $value);
|
||||
}, $this->editorToolbarPresets);
|
||||
}
|
||||
|
||||
public static function renderCss()
|
||||
{
|
||||
$cacheKey = self::instance()->cacheKey;
|
||||
if (Cache::has($cacheKey)) {
|
||||
return strip_tags(Cache::get($cacheKey));
|
||||
}
|
||||
|
||||
try {
|
||||
$customCss = self::compileCss();
|
||||
Cache::forever($cacheKey, $customCss);
|
||||
} catch (Exception $ex) {
|
||||
$customCss = '/* ' . e($ex->getMessage()) . ' */';
|
||||
}
|
||||
|
||||
return strip_tags($customCss);
|
||||
}
|
||||
|
||||
public static function compileCss()
|
||||
{
|
||||
$parser = new Less_Parser(['compress' => true]);
|
||||
|
||||
// Refuse every @import directive. There is no bundled .less file to
|
||||
// import here, and the admin-supplied html_custom_styles field has no
|
||||
// legitimate use for @import. Without this gate, an @import (inline)
|
||||
// directive in user CSS would disclose server files via the
|
||||
// wikimedia/less.php raw-path fallback. See GHSA-58fp-mcx6-7qf9.
|
||||
$parser->SetImportDirs(['' => LessImportResolver::makeResolver([], null)]);
|
||||
|
||||
$customStyles = '.fr-view {';
|
||||
$customStyles .= self::get('html_custom_styles');
|
||||
$customStyles .= '}';
|
||||
|
||||
$parser->parse($customStyles);
|
||||
|
||||
return $parser->getCss();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user