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,35 @@
<?php
use Winter\Storm\Database\Schema\Blueprint;
use Winter\Storm\Database\Updates\Migration;
class DbBackendUsers extends Migration
{
public function up()
{
Schema::create('backend_users', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('first_name')->nullable();
$table->string('last_name')->nullable();
$table->string('login')->unique('login_unique')->index('login_index');
$table->string('email')->unique('email_unique');
$table->string('password');
$table->string('activation_code')->nullable()->index('act_code_index');
$table->string('persist_code')->nullable();
$table->string('reset_password_code')->nullable()->index('reset_code_index');
$table->text('permissions')->nullable();
$table->boolean('is_activated')->default(0);
$table->integer('role_id')->unsigned()->nullable()->index('admin_role_index');
$table->timestamp('activated_at')->nullable();
$table->timestamp('last_login')->nullable();
$table->timestamps();
$table->timestamp('deleted_at')->nullable();
});
}
public function down()
{
Schema::dropIfExists('backend_users');
}
}

View File

@@ -0,0 +1,22 @@
<?php
use Winter\Storm\Database\Schema\Blueprint;
use Winter\Storm\Database\Updates\Migration;
class DbBackendUserGroups extends Migration
{
public function up()
{
Schema::create('backend_user_groups', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('name')->unique('name_unique');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('backend_user_groups');
}
}

View File

@@ -0,0 +1,22 @@
<?php
use Winter\Storm\Database\Schema\Blueprint;
use Winter\Storm\Database\Updates\Migration;
class DbBackendUsersGroups extends Migration
{
public function up()
{
Schema::create('backend_users_groups', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->integer('user_id')->unsigned();
$table->integer('user_group_id')->unsigned();
$table->primary(['user_id', 'user_group_id'], 'user_group');
});
}
public function down()
{
Schema::dropIfExists('backend_users_groups');
}
}

View File

@@ -0,0 +1,28 @@
<?php
use Winter\Storm\Database\Schema\Blueprint;
use Winter\Storm\Database\Updates\Migration;
class DbBackendUserThrottle extends Migration
{
public function up()
{
Schema::create('backend_user_throttle', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->integer('user_id')->unsigned()->nullable()->index();
$table->string('ip_address')->nullable()->index();
$table->integer('attempts')->default(0);
$table->timestamp('last_attempt_at')->nullable();
$table->boolean('is_suspended')->default(0);
$table->timestamp('suspended_at')->nullable();
$table->boolean('is_banned')->default(0);
$table->timestamp('banned_at')->nullable();
});
}
public function down()
{
Schema::dropIfExists('backend_user_throttle');
}
}

View File

@@ -0,0 +1,26 @@
<?php
use Winter\Storm\Database\Schema\Blueprint;
use Winter\Storm\Database\Updates\Migration;
class DbBackendUserPreferences extends Migration
{
public function up()
{
Schema::create('backend_user_preferences', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->string('namespace', 100);
$table->string('group', 50);
$table->string('item', 150);
$table->text('value')->nullable();
$table->index(['user_id', 'namespace', 'group', 'item'], 'user_item_index');
});
}
public function down()
{
Schema::dropIfExists('backend_user_preferences');
}
}

View File

@@ -0,0 +1,23 @@
<?php
use Winter\Storm\Database\Schema\Blueprint;
use Winter\Storm\Database\Updates\Migration;
class DbBackendAccessLog extends Migration
{
public function up()
{
Schema::create('backend_access_log', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->string('ip_address')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('backend_access_log');
}
}

View File

@@ -0,0 +1,25 @@
<?php
use Winter\Storm\Database\Schema\Blueprint;
use Winter\Storm\Database\Updates\Migration;
class DbBackendAddDescriptionField extends Migration
{
public function up()
{
Schema::table('backend_user_groups', function (Blueprint $table) {
$table->string('code')->nullable()->index('code_index');
$table->text('description')->nullable();
$table->boolean('is_new_user_default')->default(false);
});
}
public function down()
{
// Schema::table('backend_user_groups', function (Blueprint $table) {
// $table->dropColumn('code');
// $table->dropColumn('description');
// $table->dropColumn('is_new_user_default');
// });
}
}

View File

@@ -0,0 +1,29 @@
<?php
use Winter\Storm\Database\Schema\Blueprint;
use Winter\Storm\Database\Updates\Migration;
use Backend\Models\User as AdminModel;
class DbBackendAddSuperuserFlag extends Migration
{
public function up()
{
Schema::table('backend_users', function (Blueprint $table) {
$table->boolean('is_superuser')->default(false);
});
AdminModel::all()->each(function ($user) {
if ($user->hasPermission('superuser')) {
$user->is_superuser = true;
$user->save();
}
});
}
public function down()
{
// Schema::table('backend_users', function (Blueprint $table) {
// $table->dropColumn('is_superuser');
// });
}
}

View File

@@ -0,0 +1,39 @@
<?php
use Winter\Storm\Database\Updates\Migration;
/**
* This migration addresses a MySQL specific issue around STRICT MODE.
* In past versions, Laravel would give timestamps a bad default value
* of "0" considered invalid by MySQL. Strict mode is disabled and the
* the timestamps are patched up. Credit for this work: Dave Shoreman.
*/
class DbBackendTimestampFix extends Migration
{
protected $backendTables = [
'backend_users',
'backend_user_groups',
'backend_access_log',
];
public function up()
{
DbDongle::disableStrictMode();
foreach ($this->backendTables as $table) {
DbDongle::convertTimestamps($table);
}
// Use this opportunity to reset backend preferences for stable
Db::table('backend_user_preferences')
->where('namespace', 'backend')
->where('group', 'backend')
->where('item', 'preferences')
->delete();
}
public function down()
{
// ...
}
}

View File

@@ -0,0 +1,165 @@
<?php
use Backend\Models\UserRole;
use Winter\Storm\Database\Schema\Blueprint;
use Winter\Storm\Database\Updates\Migration;
class DbBackendUserRoles extends Migration
{
public function up()
{
Schema::create('backend_user_roles', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('name')->unique('role_unique');
$table->string('code')->nullable()->index('role_code_index');
$table->text('description')->nullable();
$table->text('permissions')->nullable();
$table->boolean('is_system')->default(0);
$table->timestamps();
});
// This detects older builds and performs a migration to include
// the new role system. This column will exist for new installs
// so this heavy migration process does not need to execute.
$this->migratePreviousBuild();
}
public function down()
{
Schema::dropIfExists('backend_user_roles');
}
protected function migratePreviousBuild()
{
// Role not found in the users table, perform a complete migration.
// Merging group permissions with the user and assigning the user
// with the first available role.
if (!Schema::hasColumn('backend_users', 'role_id')) {
Schema::table('backend_users', function (Blueprint $table) {
$table->integer('role_id')->unsigned()->nullable()->index('admin_role_index');
});
$this->createSystemUserRoles();
$this->migratePermissionsFromGroupsToRoles();
}
// Drop permissions column on groups table as it is no longer needed.
if (Schema::hasColumn('backend_user_groups', 'permissions')) {
Schema::table('backend_user_groups', function (Blueprint $table) {
$table->dropColumn('permissions');
});
}
}
protected function createSystemUserRoles()
{
Db::table('backend_user_roles')->insert([
'name' => 'Publisher',
'code' => UserRole::CODE_PUBLISHER,
'description' => 'Site editor with access to publishing tools.',
]);
Db::table('backend_user_roles')->insert([
'name' => 'Developer',
'code' => UserRole::CODE_DEVELOPER,
'description' => 'Site administrator with access to developer tools.',
]);
}
protected function migratePermissionsFromGroupsToRoles()
{
$groups = Db::table('backend_user_groups')->get();
$roles = [];
$permissions = [];
/*
* Carbon copy groups to roles
*/
foreach ($groups as $group) {
if (!isset($group->name) || !$group->name) {
continue;
}
try {
$roles[$group->id] = Db::table('backend_user_roles')->insertGetId([
'name' => $group->name,
'description' => $group->description,
'permissions' => $group->permissions ?? null
]);
}
catch (Exception $ex) {
}
$permissions[$group->id] = $group->permissions ?? null;
}
/*
* Assign a user with the first available role
*/
$found = [];
$joins = Db::table('backend_users_groups')->get();
foreach ($joins as $join) {
if (!$roleId = array_get($roles, $join->user_group_id)) {
continue;
}
$userId = $join->user_id;
if (!isset($found[$userId])) {
Db::table('backend_users')->where('id', $userId)->update([
'role_id' => $roleId
]);
}
$found[$userId][] = $join->user_group_id;
}
/*
* Merge group permissions in to user
*/
foreach ($found as $userId => $groups) {
$userPerms = [];
foreach ($groups as $groupId) {
if (!$permString = array_get($permissions, $groupId)) {
continue;
}
try {
$perms = json_decode($permString, true);
$userPerms = array_merge($userPerms, $perms);
}
catch (Exception $ex) {
}
}
if (count($userPerms) > 0) {
$this->splicePermissionsForUser($userId, $userPerms);
}
}
}
protected function splicePermissionsForUser($userId, $permissions)
{
/*
* Look up user and splice the provided permissions in
*/
$user = Db::table('backend_users')->where('id', $userId)->first();
if (!$user) {
return;
}
try {
$currentPerms = $user->permissions ? json_decode($user->permissions, true) : [];
$newPerms = array_merge($permissions, $currentPerms);
Db::table('backend_users')->where('id', $userId)->update([
'permissions' => json_encode($newPerms)
]);
}
catch (Exception $ex) {
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
use Winter\Storm\Database\Schema\Blueprint;
use Winter\Storm\Database\Updates\Migration;
class DbBackendAddDeletedAt extends Migration
{
public function up()
{
if (!Schema::hasColumn('backend_users', 'deleted_at')) {
Schema::table('backend_users', function (Blueprint $table) {
$table->timestamp('deleted_at')->nullable()->after('updated_at');
});
}
}
public function down()
{
if (Schema::hasColumn('backend_users', 'deleted_at')) {
Schema::table('backend_users', function (Blueprint $table) {
$table->dropColumn('deleted_at');
});
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
use Winter\Storm\Database\Schema\Blueprint;
use Winter\Storm\Database\Updates\Migration;
class DbBackendAddUserMetadata extends Migration
{
public function up()
{
if (!Schema::hasColumn('backend_users', 'metadata')) {
Schema::table('backend_users', function (Blueprint $table) {
$table->mediumText('metadata')->nullable()->after('permissions');
});
}
}
public function down()
{
if (Schema::hasColumn('backend_users', 'metadata')) {
Schema::table('backend_users', function (Blueprint $table) {
$table->dropColumn('metadata');
});
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
use Winter\Storm\Database\Schema\Blueprint;
use Winter\Storm\Database\Updates\Migration;
class DbBackendAddUsersGroupsDeleteAt extends Migration
{
public function up()
{
if (!Schema::hasColumn('backend_users_groups', 'deleted_at')) {
Schema::table('backend_users_groups', function (Blueprint $table) {
$table->timestamp('deleted_at')->nullable()->after('user_group_id');
});
}
}
public function down()
{
if (Schema::hasColumn('backend_users_groups', 'deleted_at')) {
Schema::table('backend_users_groups', function (Blueprint $table) {
$table->dropColumn('deleted_at');
});
}
}
}

View File

@@ -0,0 +1,33 @@
<?php namespace Backend\Database\Seeds;
use Str;
use Seeder;
use Eloquent;
use Backend\Database\Seeds\SeedSetupAdmin;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return string
*/
public function run()
{
$shouldRandomizePassword = SeedSetupAdmin::$password === '';
$adminPassword = $shouldRandomizePassword ? Str::random(22) : SeedSetupAdmin::$password;
Eloquent::unguarded(function () use ($adminPassword) {
// Generate a random password for the seeded admin account
$adminSeeder = new \Backend\Database\Seeds\SeedSetupAdmin;
$adminSeeder->setDefaults([
'password' => $adminPassword
]);
$this->call($adminSeeder);
});
return ($shouldRandomizePassword)
? 'The following password has been automatically generated for the "admin" account: <fg=yellow;options=bold>' . $adminPassword . '</>'
: '';
}
}

View File

@@ -0,0 +1,62 @@
<?php namespace Backend\Database\Seeds;
use Seeder;
use Backend\Models\User;
use Backend\Models\UserRole;
use Backend\Models\UserGroup;
class SeedSetupAdmin extends Seeder
{
public static $email = 'admin@example.com';
public static $login = 'admin';
public static $password = '';
public static $firstName = 'Admin';
public static $lastName = 'Person';
public function setDefaults($values)
{
if (!is_array($values)) {
return;
}
foreach ($values as $attribute => $value) {
static::$$attribute = $value;
}
}
public function run()
{
UserRole::create([
'name' => 'Publisher',
'code' => UserRole::CODE_PUBLISHER,
'description' => 'Site editor with access to publishing tools.',
]);
$role = UserRole::create([
'name' => 'Developer',
'code' => UserRole::CODE_DEVELOPER,
'description' => 'Site administrator with access to developer tools.',
]);
$group = UserGroup::create([
'name' => 'Owners',
'code' => UserGroup::CODE_OWNERS,
'description' => 'Default group for website owners.',
'is_new_user_default' => false
]);
$user = User::create([
'email' => static::$email,
'login' => static::$login,
'password' => static::$password,
'password_confirmation' => static::$password,
'first_name' => static::$firstName,
'last_name' => static::$lastName,
'permissions' => [],
'is_superuser' => true,
'is_activated' => true,
'role_id' => $role->id
]);
$user->addGroup($group);
}
}