Files
vivespos-landing/modules/system/console/CreateMigration.php
avives 1f72193a64
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
- 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
2026-08-21 19:29:00 -06:00

359 lines
12 KiB
PHP

<?php namespace System\Console;
use File;
use InvalidArgumentException;
use System\Classes\VersionManager;
use System\Console\BaseScaffoldCommand;
use Winter\Storm\Database\Model;
use Winter\Storm\Support\Str;
use Yaml;
/**
* Scaffolds a new migration file
*
* @TODO:
* - Add flag to either create a new version automatically in version.yaml or
* add the migration to a specific version, would also put the migration in
* a version specific folder
*/
class CreateMigration extends BaseScaffoldCommand
{
/**
* @var string|null The default command name for lazy loading.
*/
protected static $defaultName = 'create:migration';
/**
* @var string The name and signature of this command.
*/
protected $signature = 'create:migration
{plugin : The name of the plugin. <info>(eg: Winter.Blog)</info>}
{--name= : The name of the migration}
{--model= : The model to create a migration for. <info>(eg: Post)</info>}
{--table= : The table to migrate, defaults to autogenerated from the provided model. <info>(eg: winter_blog_posts)</info>}
{--for-version= : Generate a migration for a specific version}
{--c|create : Generate a migration that creates the specified table}
{--u|update : Generate a migration that updates the specified table}
{--f|force : Overwrite existing files with generated files.}
{--uninspiring : Disable inspirational quotes}
';
/**
* @var string The console command description.
*/
protected $description = 'Creates a new migration.';
/**
* @var array List of commands that this command replaces (aliases)
*/
protected $replaces = [
'make:migration',
];
/**
* @var string The type of class being generated.
*/
protected $type = 'Migration';
/**
* @var bool Validate the provided plugin input against the PluginManager.
*/
protected $validatePluginInput = true;
/**
* @var array Available migration scaffolds and their types
*/
protected $migrationScaffolds = [
'create' => [
'scaffold/migration/migration.create.stub' => 'updates/{{version}}/{{snake_name}}.php',
],
'update' => [
'scaffold/migration/migration.update.stub' => 'updates/{{version}}/{{snake_name}}.php',
],
'migrate' => [
'scaffold/migration/migration.stub' => 'updates/{{version}}/{{snake_name}}.php',
],
];
/**
* Make all stubs.
*/
public function makeStubs(): void
{
parent::makeStubs();
$plugin = $this->getPlugin();
// Identify the changes to insert into the version.yaml file
$changes = [$this->vars['name']];
$stubs = array_keys($this->stubs);
foreach ($stubs as $stub) {
$changes[] = Str::after($this->getDestinationForStub($stub), $plugin->getPluginPath() . '/updates/');
}
// Identify the version to insert these changes into
$versions = $plugin->getPluginVersions();
$firstVersion = array_keys($versions)[0];
$usesV = Str::startsWith($firstVersion, 'v');
$version = $usesV ? $this->vars['version'] : Str::after($this->vars['version'], 'v');
// Insert these changes into the identified version
$changes = array_merge($versions[$version] ?? [], $changes);
$versions[$version] = $changes;
// Render and save the updated version.yaml file
$destinationFile = $plugin->getPluginPath() . '/updates/version.yaml';
$this->files->put($destinationFile, Yaml::render($versions));
$this->comment('File updated: ' . str_replace(base_path(), '', $destinationFile));
}
/**
* Get the desired class name from the input.
*/
protected function getNameInput(): string
{
$name = trim($this->option($this->nameFrom));
if (empty($name)) {
if ($this->option('create')) {
$template = 'Create {ResourceName} Table';
} elseif ($this->option('update')) {
$template = 'Update {ResourceName} Table';
} else {
$template = now()->format('Y_m_d_His');
}
$resourceName = (
$this->option('model')
? Str::plural($this->option('model'))
: null
)
?? (
$this->option('table')
? Str::replace('_', ' ', $this->option('table'))
: ''
);
$name = Str::replace('{ResourceName}', $resourceName, $template);
}
return $name;
}
/**
* Prepare variables for stubs.
*/
protected function prepareVars(): array
{
$parts = explode('.', $this->getPluginIdentifier());
$plugin = array_pop($parts);
$author = array_pop($parts);
$name = $this->getNameInput();
$table = $this->option('table');
$model = $this->option('model');
if (empty($table) && !empty($model)) {
$modelClass = "\\{$author}\\{$plugin}\Models\\{$model}";
if (class_exists($modelClass)) {
$table = (new $modelClass)->getTable();
} else {
throw new InvalidArgumentException("The model [{$modelClass}] does not exist.");
}
}
if ($this->option('create') && $this->option('update')) {
throw new InvalidArgumentException('The create & update options cannot both be set at the same time');
}
if ($this->option('create')) {
$scaffold = 'create';
} elseif ($this->option('update')) {
$scaffold = 'update';
} else {
$scaffold = 'migrate';
}
if (in_array($scaffold, ['create', 'update']) && empty($table)) {
throw new InvalidArgumentException('The table or model options are required when using the create or update options');
}
if (($table || $model) && !in_array($scaffold, ['create', 'update'])) {
throw new InvalidArgumentException('One of create or update option is required when using the model or table options');
}
$this->stubs = $this->migrationScaffolds[$scaffold];
if (!empty($this->option('for-version'))) {
$version = $this->option('for-version');
} else {
$currentVersion = $this->getPlugin()->getPluginVersion();
if ($currentVersion === VersionManager::NO_VERSION_VALUE) {
throw new InvalidArgumentException('The plugin [' . $this->getPluginIdentifier() . '] does not have a version set and no --version option was provided. Please set a version in the plugin\'s updates/version.yaml file.');
}
$version = $this->getNextVersion($currentVersion);
}
$vars = [
'name' => $name,
'author' => $author,
'plugin' => $plugin,
'version' => $version,
];
if (!empty($model)) {
$vars['model'] = $model;
}
if (!empty($table)) {
$vars['table'] = $table;
}
return $vars;
}
/**
* Create vars for model fields mappings so they can be used in update/create stubs
*/
protected function processVars(array $vars): array
{
$vars = parent::processVars($vars);
// --model option needed below
if (empty($vars['model'])) {
return $vars;
}
$vars['fields'] = [];
$fields_path = plugins_path($vars['plugin_url'] . '/models/' . $vars['lower_model'] . '/fields.yaml');
$fields = [];
if (file_exists($fields_path)) {
$fields = Yaml::parseFile(($fields_path));
}
$modelName = $vars['plugin_namespace'] . '\\Models\\' . $vars['model'];
$vars['model'] = $model = new $modelName();
foreach (['fields', 'tabs', 'secondaryTabs'] as $type) {
if (!isset($fields[$type])) {
continue;
}
if ($type === 'fields') {
$fieldList = $fields[$type];
} else {
$fieldList = $fields[$type]['fields'];
}
foreach ($fieldList as $field => $config) {
if (str_contains($field, '@')) {
list($field, $context) = explode('@', $field);
}
$type = $config['type'] ?? 'text';
if (str_starts_with($field, '_')
|| $field === $model->getKeyName()
|| str_contains($field, '[')
|| in_array($type, ['fileupload', 'relation', 'relationmanager', 'repeater', 'section', 'hint'])
|| in_array($field, $model->purgeable ?? [])
|| $model->getRelationType($field)
) {
continue;
}
$vars['fields'][$field] = $this->mapFieldType($field, $config, $model);
}
}
foreach ($model->getRelationDefinitions() as $relationType => $definitions) {
if (in_array($relationType, ['belongsTo', 'hasOne'])) {
foreach (array_keys($definitions) as $relation) {
$vars['fields'][$relation . '_id'] = [
'type' => 'foreignId',
'index' => true,
'required' => true,
];
}
}
}
if ($model->methodExists('getSortOrderColumn')) {
$field = $model->getSortOrderColumn();
$vars['fields'][$field] = [
'type' => 'unsignedinteger',
'required' => false,
'index' => true,
];
}
$vars['primaryKey'] = $model->getKeyName();
$vars['jsonable'] = $model->getJsonable();
$vars['timestamps'] = $model->timestamps;
if ($morphable = $model->morphTo) {
$vars['morphable'] = array_keys($morphable);
}
return $vars;
}
/**
* Get the next version number based on the current number.
*/
protected function getNextVersion($currentVersion): string
{
$currentVersion = ltrim($currentVersion, 'v');
$parts = explode('.', $currentVersion);
$parts[count($parts) - 1] = (int) $parts[count($parts) - 1] + 1;
return 'v' . implode('.', $parts);
}
/**
* Maps model fields config to DB Schema column types.
*/
protected function mapFieldType(string $fieldName, array $fieldConfig, ?Model $model = null) : array
{
switch ($fieldConfig['type'] ?? 'text') {
case 'checkbox':
case 'switch':
$dbType = 'boolean';
break;
case 'number':
$dbType = 'double';
if (isset($fieldConfig['step']) && is_int($fieldConfig['step'])) {
$dbType = 'integer';
}
if ($dbType === 'integer' && isset($fieldConfig['min']) && $fieldConfig['min'] >= 0) {
$dbType = 'unsignedInteger';
}
break;
case 'range':
$dbType = 'unsignedInteger';
break;
case 'datepicker':
$dbType = $fieldConfig['mode'] ?? 'datetime';
break;
case 'markdown':
$dbType = 'mediumText';
break;
case 'textarea':
$dbType = 'text';
break;
default:
$dbType = 'string';
}
if ($model) {
$rule = array_get($model->rules ?? [], $fieldName, '');
$rule = is_array($rule) ? implode(',', $rule) : $rule;
$required = str_contains($rule, 'required') ? true : $fieldConfig['required'] ?? false;
} else {
$required = $fieldConfig['required'] ?? false;
}
return [
'type' => $dbType,
'required' => $required,
'index' => in_array($fieldName, ["slug"]) or str_ends_with($fieldName, "_id"),
];
}
}