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,42 @@
<?php
namespace {{ plugin_namespace }}\Console;
use Winter\Storm\Console\Command;
class {{studly_name}} extends Command
{
/**
* @var string The console command name.
*/
protected static $defaultName = '{{ lower_command }}';
/**
* @var string The name and signature of this command.
*/
protected $signature = '{{ lower_command }}
{myCustomArgument : Example argument. <info>Additional information</info>}
{--f|force : Force the operation to run and ignore production warnings and confirmation questions.}';
/**
* @var string The console command description.
*/
protected $description = '{{ description }}';
/**
* Execute the console command.
* @return void
*/
public function handle()
{
$this->output->writeln('Hello world!');
}
/**
* Provide autocomplete suggestions for the "myCustomArgument" argument
*/
// public function suggestMyCustomArgumentValues(): array
// {
// return ['value', 'another'];
// }
}

View File

@@ -0,0 +1,27 @@
<?php
namespace {{ plugin_namespace }}\Database\Factories;
use Winter\Storm\Database\Factories\Factory;
/**
* {{ name }} Factory
*
{% if model %}
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\{{ plugin_namespace }}\Models\{{ model }}>
{% endif %}
*/
class {{ studly_name }} extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
//
];
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace {{ plugin_namespace }}\Jobs;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\SkipIfBatchCancelled;
use Illuminate\Queue\SerializesModels;
class {{studly_name}} implements ShouldQueue
{
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*/
public function __construct()
{
//
}
/**
* Execute the job.
*/
public function handle(): void
{
//
}
/**
* Get the middleware the job should pass through.
*/
public function middleware()
{
return []; // Remove this line to activate
return [
// Instruct Laravel to not process the job if
// its corresponding batch has been cancelled
new SkipIfBatchCancelled()
];
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace {{ plugin_namespace }}\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class {{studly_name}} implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*/
public function __construct()
{
//
}
/**
* Execute the job.
*/
public function handle(): void
{
//
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace {{ plugin_namespace }}\Jobs;
use Illuminate\Foundation\Bus\Dispatchable;
class {{studly_name}}
{
use Dispatchable;
/**
* Create a new job instance.
*/
public function __construct()
{
//
}
/**
* Execute the job.
*/
public function handle(): void
{
//
}
}

View File

@@ -0,0 +1,46 @@
<?php
use Winter\Storm\Database\Schema\Blueprint;
use Winter\Storm\Database\Updates\Migration;
use Winter\Storm\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('{{ table }}', function (Blueprint $table) {
{% if primaryKey %}
$table->increments('{{ primaryKey }}');
{% else %}
$table->id();
{% endif %}
{% for field,config in fields %}
$table->{{ config.type }}('{{ field }}'){{ config.required == false ? '->nullable()' }}{{ config.index ? '->index()' }};
{% endfor %}
{% for field in jsonable %}
$table->mediumText('{{ field }}')->nullable();
{% endfor %}
{% for field in morphable %}
$table->nullableMorphs('{{ field }}', 'morphable_index');
{% endfor %}
{% if not model or timestamps %}
$table->timestamps();
{% endif %}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('{{ table }}');
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Winter\Storm\Database\Schema\Blueprint;
use Winter\Storm\Database\Updates\Migration;
use Winter\Storm\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
//
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
};

View File

@@ -0,0 +1,48 @@
<?php
use Winter\Storm\Database\Schema\Blueprint;
use Winter\Storm\Database\Updates\Migration;
use Winter\Storm\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('{{ table }}', function (Blueprint $table) {
{% for field,config in fields %}
$table->{{ config.type }}('{{ field }}'){{ config.required == false ? '->nullable()' }}{{ config.index ? '->index()' }};
{% endfor %}
{% for field in jsonable %}
$table->mediumText('{{ field }}')->nullable();
{% endfor %}
{% for field in morphable %}
$table->nullableMorphs('{{ field }}', 'morphable_index');
{% endfor %}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('{{ table }}', function (Blueprint $table) {
{% for field,config in fields %}
$table->dropColumn('{{ field }}');
{% endfor %}
{% for field in jsonable %}
$table->dropColumn('{{ field }}');
{% endfor %}
{% for field in morphable %}
$table->dropColumn('{{ field }}');
{% endfor %}
});
}
};

View File

@@ -0,0 +1,19 @@
# ===================================
# List Column Definitions
# ===================================
columns:
id:
label: '{{ plugin_id }}::lang.models.general.id'
searchable: true
created_at:
label: '{{ plugin_id }}::lang.models.general.created_at'
type: datetime
searchable: true
sortable: true
invisible: true
updated_at:
label: '{{ plugin_id }}::lang.models.general.updated_at'
type: datetime
searchable: true
sortable: true

View File

@@ -0,0 +1,21 @@
<?php
use Winter\Storm\Database\Schema\Blueprint;
use Winter\Storm\Database\Updates\Migration;
return new class extends Migration
{
public function up()
{
Schema::create('{{ table_name }}', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('{{ table_name }}');
}
};

View File

@@ -0,0 +1,8 @@
# ===================================
# Form Field Definitions
# ===================================
fields:
id:
label: '{{ plugin_id }}::lang.models.general.id'
disabled: true

View File

@@ -0,0 +1,76 @@
<?php
namespace {{ plugin_namespace }}\Models;
use Winter\Storm\Database\Model;
/**
* {{ name }} Model
*/
class {{ studly_name }} extends Model
{
use \Winter\Storm\Database\Traits\Validation;
/**
* @var string The database table used by the model.
*/
public $table = '{{ table_name }}';
/**
* @var array Guarded fields
*/
protected $guarded = ['*'];
/**
* @var array Fillable fields
*/
protected $fillable = [];
/**
* @var array Validation rules for attributes
*/
public $rules = [];
/**
* @var array Attributes to be cast to native types
*/
protected $casts = [];
/**
* @var array Attributes to be cast to JSON
*/
protected $jsonable = [];
/**
* @var array Attributes to be appended to the API representation of the model (ex. toArray())
*/
protected $appends = [];
/**
* @var array Attributes to be removed from the API representation of the model (ex. toArray())
*/
protected $hidden = [];
/**
* @var array Attributes to be cast to Argon (Carbon) instances
*/
protected $dates = [
'created_at',
'updated_at',
];
/**
* @var array Relations
*/
public $hasOne = [];
public $hasMany = [];
public $hasOneThrough = [];
public $hasManyThrough = [];
public $belongsTo = [];
public $belongsToMany = [];
public $morphTo = [];
public $morphOne = [];
public $morphMany = [];
public $attachOne = [];
public $attachMany = [];
}

View File

@@ -0,0 +1,88 @@
<?php
namespace {{ plugin_namespace }};
use Backend\Facades\Backend;
use Backend\Models\UserRole;
use System\Classes\PluginBase;
/**
* {{ name }} Plugin Information File
*/
class Plugin extends PluginBase
{
/**
* Returns information about this plugin.
*/
public function pluginDetails(): array
{
return [
'name' => '{{ plugin_id }}::lang.plugin.name',
'description' => '{{ plugin_id }}::lang.plugin.description',
'author' => '{{ author }}',
'icon' => 'icon-leaf'
];
}
/**
* Register method, called when the plugin is first registered.
*/
public function register(): void
{
}
/**
* Boot method, called right before the request route.
*/
public function boot(): void
{
}
/**
* Registers any frontend components implemented in this plugin.
*/
public function registerComponents(): array
{
return []; // Remove this line to activate
return [
\{{ plugin_namespace }}\Components\MyComponent::class => 'myComponent',
];
}
/**
* Registers any backend permissions used by this plugin.
*/
public function registerPermissions(): array
{
return []; // Remove this line to activate
return [
'{{ plugin_id }}.some_permission' => [
'tab' => '{{ plugin_id }}::lang.plugin.name',
'label' => '{{ plugin_id }}::lang.permissions.some_permission',
'roles' => [UserRole::CODE_DEVELOPER, UserRole::CODE_PUBLISHER],
],
];
}
/**
* Registers backend navigation items for this plugin.
*/
public function registerNavigation(): array
{
return []; // Remove this line to activate
return [
'{{ lower_name }}' => [
'label' => '{{ plugin_id }}::lang.plugin.name',
'url' => Backend::url('{{ plugin_url }}/mycontroller'),
'icon' => 'icon-leaf',
'permissions' => ['{{ plugin_id }}.*'],
'order' => 500,
],
];
}
}

View File

@@ -0,0 +1,2 @@
'1.0.0':
- 'First version of {{ name }}'

View File

@@ -0,0 +1,7 @@
# ===================================
# Form Field Definitions
# ===================================
fields:
settings_option:
label: This is a sample settings field used by {{author}}.{{plugin}}

View File

@@ -0,0 +1,33 @@
<?php
namespace {{ plugin_namespace }}\Models;
use Model;
/**
* {{name}} Model
*/
class {{studly_name}} 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 = '{{lower_author}}_{{lower_plugin}}_{{lower_name}}';
/**
* @var mixed Settings form field definitions
*/
public $settingsFields = 'fields.yaml';
/**
* @var array Validation rules
*/
public $rules = [];
}

View File

@@ -0,0 +1,13 @@
<?php
use {{ tested_class_full }};
beforeEach(function () {
$this->instance = $this->app->make({{ tested_class }}::class);
});
{% for method in public_methods %}
it('the {{ method }} method does something', function () {
// $this->assertTrue(method_exists($this->instance, '{{ method }}'));
});
{% endfor %}

View File

@@ -0,0 +1,45 @@
<?php
/*
|--------------------------------------------------------------------------
| Test Case
|--------------------------------------------------------------------------
|
| The closure you provide to your test functions is always bound to a specific PHPUnit test
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
| need to change it using the "uses()" function to bind a different classes or traits.
|
*/
uses(\System\Tests\Bootstrap\TestCase::class)->in('Feature');
/*
|--------------------------------------------------------------------------
| Expectations
|--------------------------------------------------------------------------
|
| When you're writing tests, you often need to check that values meet certain conditions. The
| "expect()" function gives you access to a set of "expectations" methods that you can use
| to assert different things. Of course, you may extend the Expectation API at any time.
|
*/
// expect()->extend('toBeOne', function () {
// return $this->toBe(1);
// });
/*
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
|
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
| project that you don't want to repeat in every file. Here you can also expose helpers as
| global functions to help you to reduce the number of lines of code in your test files.
|
*/
// function something()
// {
// // ..
// }

View File

@@ -0,0 +1,7 @@
<?php
test('example', function () {
$response = $this->get('/');
$response->assertStatus(200);
});

View File

@@ -0,0 +1,5 @@
<?php
test('example', function () {
expect(true)->toBeTrue();
});

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="../../../modules/system/tests/bootstrap/app.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
<testsuites>
<testsuite name="Unit">
<directory>./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>./tests/Feature</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_ENV" value="testing"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
</php>
</phpunit>

View File

@@ -0,0 +1,34 @@
<?php
namespace {{ test_namespace }};
use {{ tested_class_full }};
use System\Tests\Bootstrap\TestCase as WinterTestCase;
class {{ test_class }} extends WinterTestCase
{
/**
* An instance of the class being tested
*/
protected ?{{ tested_class }} $instance = null;
/**
* Initialize the {{ tested_class }}
*/
public function setUp(): void
{
parent::setUp();
$this->instance = $this->app->make({{ tested_class }}::class);
}
{% for method in public_methods %}
/**
* Test for the {{ method }} method
*/
public function test_{{ method }}()
{
$this->assertTrue(method_exists($this->instance, '{{ method }}'));
}
{% endfor %}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace {{ plugin_namespace }}\Tests\Unit;
use {{ plugin_namespace }}\Plugin;
use System\Classes\PluginBase;
use System\Tests\Bootstrap\PluginTestCase;
class PluginTest extends PluginTestCase
{
protected PluginBase $plugin;
public function setUp(): void
{
$this->plugin = new Plugin($this->createApplication());
}
public function testPluginDetails()
{
$details = $this->plugin->pluginDetails();
$this->assertIsArray($details);
$this->assertArrayHasKey('name', $details);
$this->assertArrayHasKey('description', $details);
$this->assertArrayHasKey('icon', $details);
$this->assertArrayHasKey('author', $details);
$this->assertEquals('{{ author }}', $details['author']);
}
public function testRegisterPermissions()
{
$permissions = $this->plugin->registerPermissions();
$this->assertIsArray($permissions);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace {{ test_namespace }};
use System\Tests\Bootstrap\PluginTestCase;
class {{ test_class }} extends PluginTestCase
{
/**
* A basic feature test example.
*/
public function test_example(): void
{
$response = $this->get('/');
$response->assertStatus(200);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace {{ test_namespace }};
use PHPUnit\Framework\TestCase;
class {{ test_class }} extends TestCase
{
/**
* A basic unit test example.
*/
public function test_example(): void
{
$this->assertTrue(true);
}
}