feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
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:
2026-08-21 19:29:00 -06:00
commit 1f72193a64
3266 changed files with 531480 additions and 0 deletions

View 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);
}
}