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:
54
modules/cms/console/CreateComponent.php
Normal file
54
modules/cms/console/CreateComponent.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php namespace Cms\Console;
|
||||
|
||||
use System\Console\BaseScaffoldCommand;
|
||||
|
||||
class CreateComponent extends BaseScaffoldCommand
|
||||
{
|
||||
/**
|
||||
* The default command name for lazy loading.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected static $defaultName = 'create:component';
|
||||
|
||||
/**
|
||||
* The name and signature of this command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'create:component
|
||||
{plugin : The name of the plugin. <info>(eg: Winter.Blog)</info>}
|
||||
{component : The name of the component to generate. <info>(eg: Posts)</info>}
|
||||
{--force : Overwrite existing files with generated files.}
|
||||
{--uninspiring : Disable inspirational quotes}
|
||||
';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Creates a new plugin component.';
|
||||
|
||||
/**
|
||||
* The type of class being generated.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'Component';
|
||||
|
||||
/**
|
||||
* @var string The argument that the generated class name comes from
|
||||
*/
|
||||
protected $nameFrom = 'component';
|
||||
|
||||
/**
|
||||
* A mapping of stub to generated file.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $stubs = [
|
||||
'scaffold/component/component.stub' => 'components/{{studly_name}}.php',
|
||||
'scaffold/component/default.stub' => 'components/{{lower_name}}/default.htm',
|
||||
];
|
||||
}
|
||||
217
modules/cms/console/CreateTheme.php
Normal file
217
modules/cms/console/CreateTheme.php
Normal file
@@ -0,0 +1,217 @@
|
||||
<?php namespace Cms\Console;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use System\Classes\Asset\PackageManager;
|
||||
use Winter\Storm\Exception\SystemException;
|
||||
use Winter\Storm\Scaffold\GeneratorCommand;
|
||||
|
||||
class CreateTheme extends GeneratorCommand
|
||||
{
|
||||
/**
|
||||
* @var string|null The default command name for lazy loading.
|
||||
*/
|
||||
protected static $defaultName = 'create:theme';
|
||||
|
||||
/**
|
||||
* @var string The name and signature of this command.
|
||||
*/
|
||||
protected $signature = 'create:theme
|
||||
{theme : The name of the theme to create. <info>(eg: MyTheme)</info>}
|
||||
{scaffold? : The base theme scaffold to use <info>(eg: less, tailwind)</info>}
|
||||
{--f|force : Overwrite existing files with generated files.}
|
||||
{--uninspiring : Disable inspirational quotes}
|
||||
';
|
||||
|
||||
/**
|
||||
* @var string The console command description.
|
||||
*/
|
||||
protected $description = 'Creates a new theme.';
|
||||
|
||||
/**
|
||||
* @var string The type of class being generated.
|
||||
*/
|
||||
protected $type = 'Theme';
|
||||
|
||||
/**
|
||||
* @var string The argument that the generated class name comes from
|
||||
*/
|
||||
protected $nameFrom = 'theme';
|
||||
|
||||
/**
|
||||
* @var string The scaffold that we are building
|
||||
*/
|
||||
protected string $scaffold;
|
||||
|
||||
/**
|
||||
* @var array Available theme scaffolds and their types
|
||||
*/
|
||||
protected $themeScaffolds = [
|
||||
'less' => [
|
||||
'scaffold/theme/less/assets/js/app.stub' => 'assets/js/app.js',
|
||||
'scaffold/theme/less/assets/less/theme.stub' => 'assets/less/theme.less',
|
||||
'scaffold/theme/less/layouts/default.stub' => 'layouts/default.htm',
|
||||
'scaffold/theme/less/pages/404.stub' => 'pages/404.htm',
|
||||
'scaffold/theme/less/pages/error.stub' => 'pages/error.htm',
|
||||
'scaffold/theme/less/pages/home.stub' => 'pages/home.htm',
|
||||
'scaffold/theme/less/partials/meta/seo.stub' => 'partials/meta/seo.htm',
|
||||
'scaffold/theme/less/partials/meta/styles.stub' => 'partials/meta/styles.htm',
|
||||
'scaffold/theme/less/partials/site/header.stub' => 'partials/site/header.htm',
|
||||
'scaffold/theme/less/partials/site/footer.stub' => 'partials/site/footer.htm',
|
||||
'scaffold/theme/less/theme.stub' => 'theme.yaml',
|
||||
'scaffold/theme/less/version.stub' => 'version.yaml',
|
||||
],
|
||||
'tailwind' => [
|
||||
'scaffold/theme/tailwind/lang/en/lang.stub' => 'lang/en/lang.php',
|
||||
'scaffold/theme/tailwind/layouts/default.stub' => 'layouts/default.htm',
|
||||
'scaffold/theme/tailwind/pages/404.stub' => 'pages/404.htm',
|
||||
'scaffold/theme/tailwind/pages/error.stub' => 'pages/error.htm',
|
||||
'scaffold/theme/tailwind/pages/home.stub' => 'pages/home.htm',
|
||||
'scaffold/theme/tailwind/partials/meta/seo.stub' => 'partials/meta/seo.htm',
|
||||
'scaffold/theme/tailwind/partials/meta/styles.stub' => 'partials/meta/styles.htm',
|
||||
'scaffold/theme/tailwind/partials/site/header.stub' => 'partials/site/header.htm',
|
||||
'scaffold/theme/tailwind/partials/site/footer.stub' => 'partials/site/footer.htm',
|
||||
'scaffold/theme/tailwind/.gitignore.stub' => '.gitignore',
|
||||
'scaffold/theme/tailwind/README.stub' => 'README.md',
|
||||
'scaffold/theme/tailwind/theme.stub' => 'theme.yaml',
|
||||
'scaffold/theme/tailwind/version.stub' => 'version.yaml',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the desired class name from the input.
|
||||
*/
|
||||
protected function getNameInput(): string
|
||||
{
|
||||
return str_slug(parent::getNameInput());
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare variables for stubs.
|
||||
*/
|
||||
protected function prepareVars(): array
|
||||
{
|
||||
$this->scaffold = $this->argument('scaffold') ?? 'tailwind';
|
||||
|
||||
$validOptions = $this->suggestScaffoldValues();
|
||||
if (!in_array($this->scaffold, $validOptions)) {
|
||||
throw new InvalidArgumentException("$this->scaffold is not an available theme scaffold type (Available types: " . implode(', ', $validOptions) . ')');
|
||||
}
|
||||
$this->stubs = $this->themeScaffolds[$this->scaffold];
|
||||
|
||||
return [
|
||||
'code' => $this->getNameInput(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto suggest valid theme scaffold values
|
||||
*/
|
||||
public function suggestScaffoldValues(): array
|
||||
{
|
||||
return array_keys($this->themeScaffolds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plugin path from the input.
|
||||
*/
|
||||
protected function getDestinationPath(): string
|
||||
{
|
||||
return themes_path($this->getNameInput());
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a single stub.
|
||||
*
|
||||
* @param string $stubName The source filename for the stub.
|
||||
*/
|
||||
public function makeStub($stubName)
|
||||
{
|
||||
if (!isset($this->stubs[$stubName])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sourceFile = $this->getSourcePath() . '/' . $stubName;
|
||||
$destinationFile = $this->getDestinationForStub($stubName);
|
||||
$destinationContent = $this->files->get($sourceFile);
|
||||
|
||||
/*
|
||||
* Parse each variable in to the destination content and path
|
||||
* @NOTE: CANNOT USE TWIG AS IT WOULD CONFLICT WITH THE TWIG TEMPLATES THEMSELVES
|
||||
*/
|
||||
foreach ($this->vars as $key => $var) {
|
||||
$destinationContent = str_replace('{{' . $key . '}}', $var, $destinationContent);
|
||||
$destinationFile = str_replace('{{' . $key . '}}', $var, $destinationFile);
|
||||
}
|
||||
|
||||
$this->makeDirectory($destinationFile);
|
||||
|
||||
$this->files->put($destinationFile, $destinationContent);
|
||||
}
|
||||
|
||||
public function makeStubs(): void
|
||||
{
|
||||
parent::makeStubs();
|
||||
|
||||
if ($this->scaffold === 'tailwind') {
|
||||
// @TODO: allow support for mix here
|
||||
$this->tailwindPostCreate('vite');
|
||||
}
|
||||
}
|
||||
|
||||
protected function tailwindPostCreate(string $processor): void
|
||||
{
|
||||
if ($this->call('npm:version', ['--silent' => true, '--compatible' => true]) !== 0) {
|
||||
throw new SystemException(sprintf(
|
||||
'NPM is not installed or is outdated, please ensure NPM >= v7.0 is available and then manually set up %s.',
|
||||
$processor
|
||||
));
|
||||
}
|
||||
|
||||
$commands = [
|
||||
// Set up the vite config files
|
||||
$processor . ':create' => [
|
||||
'message' => 'Generating ' . $processor . ' + tailwind config...',
|
||||
'args' => [
|
||||
'packageName' => 'theme-' . $this->getNameInput(),
|
||||
'--no-interaction' => true,
|
||||
'--force' => true,
|
||||
'--silent' => true,
|
||||
'--tailwind' => true
|
||||
]
|
||||
],
|
||||
// Ensure all require packages are available for the new theme and add the new theme to our npm workspaces
|
||||
$processor . ':install' => [
|
||||
'message' => 'Installing NPM dependencies...',
|
||||
'args' => [
|
||||
'assetPackage' => ['theme-' . $this->getNameInput()],
|
||||
'--no-interaction' => true,
|
||||
'--silent' => false,
|
||||
'--disable-tty' => true
|
||||
]
|
||||
],
|
||||
// Run an initial compile to ensure styles are available for first load
|
||||
$processor . ':compile' => [
|
||||
'message' => 'Compiling your theme...',
|
||||
'args' => [
|
||||
'--package' => ['theme-' . $this->getNameInput()],
|
||||
'--no-interaction' => true,
|
||||
'--silent' => true,
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($commands as $command => $data) {
|
||||
$this->info($data['message']);
|
||||
|
||||
// Handle commands throwing errors
|
||||
if ($this->call($command, $data['args']) !== 0) {
|
||||
throw new SystemException(sprintf('Post create command `%s` failed, please review manually.', $command));
|
||||
}
|
||||
|
||||
// Force PackageManger to reset available packages
|
||||
if ($command === $processor . ':create') {
|
||||
PackageManager::forgetInstance();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
130
modules/cms/console/ThemeInstall.php
Normal file
130
modules/cms/console/ThemeInstall.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php namespace Cms\Console;
|
||||
|
||||
use Cms\Classes\Theme;
|
||||
use Cms\Classes\ThemeManager;
|
||||
use File;
|
||||
use System\Classes\UpdateManager;
|
||||
use Winter\Storm\Console\Command;
|
||||
|
||||
/**
|
||||
* Console command to install a new theme.
|
||||
*
|
||||
* This adds a new theme by requesting it from the Winter marketplace.
|
||||
*
|
||||
* @package winter\wn-cms-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class ThemeInstall extends Command
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'theme:install';
|
||||
|
||||
/**
|
||||
* @var string The name and signature of this command.
|
||||
*/
|
||||
protected $signature = 'theme:install
|
||||
{name : The name of the theme. <info>(eg: AuthorName.ThemeName)</info>}
|
||||
{dirName? : Destination directory name for the theme installation.}
|
||||
';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Install a theme from the Winter marketplace.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$themeName = $this->argument('name');
|
||||
$argDirName = $this->argument('dirName');
|
||||
|
||||
if ($argDirName && $themeName == $argDirName) {
|
||||
$argDirName = null;
|
||||
}
|
||||
|
||||
if ($argDirName) {
|
||||
if (!Theme::isValidDirName($argDirName)) {
|
||||
return $this->error('Invalid destination directory name.');
|
||||
}
|
||||
|
||||
if (Theme::exists($argDirName)) {
|
||||
return $this->error(sprintf('A theme named %s already exists.', $argDirName));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$themeManager = ThemeManager::instance();
|
||||
$updateManager = UpdateManager::instance();
|
||||
|
||||
$themeDetails = $updateManager->requestThemeDetails($themeName);
|
||||
|
||||
if ($themeManager->isInstalled($themeDetails['code'])) {
|
||||
return $this->error(sprintf('The theme %s is already installed.', $themeDetails['code']));
|
||||
}
|
||||
|
||||
if (Theme::exists($themeDetails['code'])) {
|
||||
return $this->error(sprintf('A theme named %s already exists.', $themeDetails['code']));
|
||||
}
|
||||
|
||||
$fields = ['Name', 'Description', 'Author', 'URL', ''];
|
||||
|
||||
$this->info(sprintf(
|
||||
implode(': %s'.PHP_EOL, $fields),
|
||||
$themeDetails['code'],
|
||||
$themeDetails['description'],
|
||||
$themeDetails['author'],
|
||||
$themeDetails['product_url']
|
||||
));
|
||||
|
||||
if (!$this->confirm('Do you wish to continue? [Y|n]', true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info('Downloading theme...');
|
||||
$updateManager->downloadTheme($themeDetails['code'], $themeDetails['hash']);
|
||||
|
||||
$this->info('Extracting theme...');
|
||||
$updateManager->extractTheme($themeDetails['code'], $themeDetails['hash']);
|
||||
|
||||
$dirName = $this->themeCodeToDir($themeDetails['code']);
|
||||
|
||||
if ($argDirName) {
|
||||
/*
|
||||
* Move downloaded theme to a new directory.
|
||||
* Basically we're renaming it.
|
||||
*/
|
||||
File::move(themes_path().'/'.$dirName, themes_path().'/'.$argDirName);
|
||||
|
||||
/*
|
||||
* Let's make sure to unflag the 'old' theme as
|
||||
* installed so it can be re-installed later.
|
||||
*/
|
||||
$themeManager->setUninstalled($themeDetails['code']);
|
||||
|
||||
$dirName = $argDirName;
|
||||
}
|
||||
|
||||
$this->info(sprintf('The theme %s has been installed. (now %s)', $themeDetails['code'], $dirName));
|
||||
} catch (\Throwable $ex) {
|
||||
$this->error($ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme code to dir.
|
||||
*
|
||||
* @param string $themeCode
|
||||
* @return string
|
||||
*/
|
||||
protected function themeCodeToDir($themeCode)
|
||||
{
|
||||
return strtolower(str_replace('.', '-', $themeCode));
|
||||
}
|
||||
}
|
||||
66
modules/cms/console/ThemeList.php
Normal file
66
modules/cms/console/ThemeList.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php namespace Cms\Console;
|
||||
|
||||
use Cms\Classes\Theme;
|
||||
use Cms\Classes\ThemeManager;
|
||||
use System\Classes\UpdateManager;
|
||||
use Winter\Storm\Console\Command;
|
||||
|
||||
/**
|
||||
* Console command to list themes.
|
||||
*
|
||||
* This lists all the available themes in the system. It also shows the active theme.
|
||||
*
|
||||
* @package winter\wn-cms-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class ThemeList extends Command
|
||||
{
|
||||
/**
|
||||
* The console command name.
|
||||
*/
|
||||
protected $name = 'theme:list';
|
||||
|
||||
/**
|
||||
* @var string The name and signature of this command.
|
||||
*/
|
||||
protected $signature = 'theme:list
|
||||
{--m|include-marketplace : Include downloadable themes from the Winter marketplace.}
|
||||
';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*/
|
||||
protected $description = 'List available themes.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$themeManager = ThemeManager::instance();
|
||||
$updateManager = UpdateManager::instance();
|
||||
$results = [];
|
||||
|
||||
foreach (Theme::all() as $theme) {
|
||||
$results[] = [
|
||||
'code' => $theme->getId(),
|
||||
'is_active' => $theme->isActiveTheme() ? '<info>Yes</info>': '<fg=red>No</>',
|
||||
'is_installed' => '<info>Yes</info>',
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->option('include-marketplace')) {
|
||||
// @TODO List everything in the marketplace - not just popular.
|
||||
$popularThemes = $updateManager->requestPopularProducts('theme');
|
||||
foreach ($popularThemes as $popularTheme) {
|
||||
$results[] = [
|
||||
'code' => $popularTheme['code'],
|
||||
'is_active' => '<fg=red>No</>',
|
||||
'is_installed' => $themeManager->isInstalled($popularTheme['code']) ? '<info>Yes</info>': '<fg=red>No</>',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->table(['Theme', 'Active', 'Installed'], $results);
|
||||
}
|
||||
}
|
||||
70
modules/cms/console/ThemeRemove.php
Normal file
70
modules/cms/console/ThemeRemove.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php namespace Cms\Console;
|
||||
|
||||
use Cms\Classes\Theme;
|
||||
use Cms\Classes\ThemeManager;
|
||||
use Exception;
|
||||
use Winter\Storm\Console\Command;
|
||||
|
||||
/**
|
||||
* Console command to remove a theme.
|
||||
*
|
||||
* This completely deletes an existing theme, including all files and directories.
|
||||
*
|
||||
* @package winter\wn-cms-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class ThemeRemove extends Command
|
||||
{
|
||||
use \Illuminate\Console\ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'theme:remove';
|
||||
|
||||
/**
|
||||
* @var string The name and signature of this command.
|
||||
*/
|
||||
protected $signature = 'theme:remove
|
||||
{name : The name of the theme to delete. <info>(eg: mytheme)</info>}
|
||||
{--f|force : Force the operation to run.}
|
||||
';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Delete an existing theme.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$themeManager = ThemeManager::instance();
|
||||
$themeName = $this->argument('name');
|
||||
$themeExists = Theme::exists($themeName);
|
||||
|
||||
if (!$themeExists) {
|
||||
$themeName = strtolower(str_replace('.', '-', $themeName));
|
||||
$themeExists = Theme::exists($themeName);
|
||||
}
|
||||
|
||||
if (!$themeExists) {
|
||||
return $this->error(sprintf('The theme %s does not exist.', $themeName));
|
||||
}
|
||||
|
||||
if (!$this->confirmToProceed(sprintf('This will DELETE theme "%s" from the filesystem and database.', $themeName))) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$themeManager->deleteTheme($themeName);
|
||||
$this->info(sprintf('The theme %s has been deleted.', $themeName));
|
||||
} catch (Exception $ex) {
|
||||
$this->error($ex->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
234
modules/cms/console/ThemeSync.php
Normal file
234
modules/cms/console/ThemeSync.php
Normal file
@@ -0,0 +1,234 @@
|
||||
<?php namespace Cms\Console;
|
||||
|
||||
use Cms\Classes\Theme;
|
||||
use Event;
|
||||
use Exception;
|
||||
use Winter\Storm\Console\Command;
|
||||
|
||||
/**
|
||||
* Console command to sync a theme between the DB and Filesystem layers.
|
||||
*
|
||||
* theme:sync name --paths=file/to/sync.md,other/file/to/sync.md --target=filesystem --force
|
||||
*
|
||||
* - name defaults to the currently active theme
|
||||
* - --paths defaults to all paths within the theme, otherwise comma-separated list of paths relative to the theme directory
|
||||
* - --target defaults to "filesystem", the source will whichever of filesystem vs database is not the target
|
||||
* - --force bypasses the confirmation request
|
||||
*
|
||||
* @package winter\wn-cms-module
|
||||
* @author Luke Towers
|
||||
*/
|
||||
class ThemeSync extends Command
|
||||
{
|
||||
use \Illuminate\Console\ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'theme:sync';
|
||||
|
||||
/**
|
||||
* @var string The name and signature of this command.
|
||||
*/
|
||||
protected $signature = 'theme:sync
|
||||
{name? : The name of the theme (directory name). Defaults to currently active theme.}
|
||||
{--paths= : Comma-separated specific paths (relative to provided theme directory) to specificaly sync. Default is all paths. You may use regular expressions.}
|
||||
{--target= : The target of the sync, the other will be used as the source. Defaults to "filesystem", can be "database"}
|
||||
{--f|force : Force the operation to run.}
|
||||
';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Sync an existing theme between the DB and Filesystem layers';
|
||||
|
||||
/**
|
||||
* @var \Cms\Classes\AutoDatasource The theme's AutoDatasource instance
|
||||
*/
|
||||
protected $datasource;
|
||||
|
||||
/**
|
||||
* @var string The datasource key that the sync is targeting
|
||||
*/
|
||||
protected $target;
|
||||
|
||||
/**
|
||||
* @var string The datasource key that the sync is sourcing from
|
||||
*/
|
||||
protected $source;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// Check to see if the application even uses a database
|
||||
if (!$this->laravel->hasDatabase()) {
|
||||
return $this->error("The application is not using a database.");
|
||||
}
|
||||
|
||||
// Check to see if the DB layer is enabled
|
||||
if (!Theme::databaseLayerEnabled()) {
|
||||
return $this->error("cms.databaseTemplates is not enabled, enable it first and try again.");
|
||||
}
|
||||
|
||||
// Check to see if the provided theme exists
|
||||
$themeName = $this->argument('name') ?: Theme::getActiveThemeCode();
|
||||
$themeExists = Theme::exists($themeName);
|
||||
if (!$themeExists) {
|
||||
$themeName = strtolower(str_replace('.', '-', $themeName));
|
||||
$themeExists = Theme::exists($themeName);
|
||||
}
|
||||
if (!$themeExists) {
|
||||
return $this->error(sprintf('The theme %s does not exist.', $themeName));
|
||||
}
|
||||
$theme = Theme::load($themeName);
|
||||
$this->datasource = $theme->getDatasource();
|
||||
|
||||
// Get the target and source datasources
|
||||
$availableSources = ['filesystem', 'database'];
|
||||
$target = $this->option('target') ?: 'filesystem';
|
||||
$source = ($target === 'filesystem') ? 'database' : 'filesystem';
|
||||
|
||||
if (!in_array($target, $availableSources)) {
|
||||
return $this->error(sprintf("Provided --target of %s is invalid. Allowed: filesystem, database", $target));
|
||||
}
|
||||
|
||||
$this->source = $source;
|
||||
$this->target = $target;
|
||||
|
||||
// Get the theme paths, taking into account if the user has specified paths
|
||||
$userPaths = $this->option('paths') ?: null;
|
||||
$themePaths = array_keys($this->datasource->getSourcePaths($source));
|
||||
|
||||
if (!isset($userPaths)) {
|
||||
$paths = $themePaths;
|
||||
} else {
|
||||
$paths = [];
|
||||
$userPaths = array_map('trim', explode(',', $userPaths));
|
||||
|
||||
foreach ($userPaths as $userPath) {
|
||||
foreach ($themePaths as $themePath) {
|
||||
$pregMatch = '/^' . str_replace('/', '\/', $userPath) . '/i';
|
||||
|
||||
if ($userPath === $themePath || preg_match($pregMatch, $themePath)) {
|
||||
$paths[] = $themePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine valid paths based on the models made available for syncing
|
||||
$validPaths = [];
|
||||
|
||||
/**
|
||||
* @event system.console.theme.sync.getAvailableModelClasses
|
||||
* Defines the Halcyon models to be made available to the `theme:sync` tool.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* Event::listen('system.console.theme.sync.getAvailableModelClasses', function () {
|
||||
* return [
|
||||
* Meta::class,
|
||||
* Page::class,
|
||||
* Layout::class,
|
||||
* Content::class,
|
||||
* Partial::class,
|
||||
* ];
|
||||
* });
|
||||
*
|
||||
*/
|
||||
$eventResults = Event::fire('system.console.theme.sync.getAvailableModelClasses');
|
||||
$validModels = [];
|
||||
|
||||
foreach ($eventResults as $result) {
|
||||
if (!is_array($result)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($result as $modelClass) {
|
||||
$modelObj = new $modelClass;
|
||||
|
||||
if ($modelObj instanceof \Winter\Storm\Halcyon\Model) {
|
||||
$validModels[] = $modelObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check each path and map it to a corresponding model
|
||||
foreach ($paths as $path) {
|
||||
foreach ($validModels as $model) {
|
||||
if (
|
||||
starts_with($path, $model->getObjectTypeDirName() . '/')
|
||||
&& in_array(pathinfo($path, PATHINFO_EXTENSION), $model->getAllowedExtensions())
|
||||
) {
|
||||
$validPaths[$path] = get_class($model);
|
||||
|
||||
// Skip to the next path
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($validPaths) === 0) {
|
||||
return $this->error(sprintf('No applicable paths found for %s.', $source));
|
||||
}
|
||||
|
||||
// Confirm with the user
|
||||
if (!$this->confirmToProceed(sprintf('This will OVERWRITE the %s provided paths in "themes/%s" on the %s with content from the %s', count($validPaths), $themeName, $target, $source), true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->info('Syncing files, please wait...');
|
||||
$progress = $this->output->createProgressBar(count($validPaths));
|
||||
|
||||
foreach ($validPaths as $path => $model) {
|
||||
$entity = $this->getModelForPath($path, $model, $theme);
|
||||
if (!isset($entity)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->datasource->pushToSource($entity, $target);
|
||||
$progress->advance();
|
||||
}
|
||||
|
||||
$progress->finish();
|
||||
$this->info('');
|
||||
$this->info(sprintf('The theme %s has been successfully synced from the %s to the %s.', $themeName, $source, $target));
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
$this->error($ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the correct Halcyon model for the provided path from the source datasource and load the requested path data.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $model
|
||||
* @param \Cms\Classes\Theme $theme
|
||||
* @return \Winter\Storm\Halcyon\Model
|
||||
*/
|
||||
protected function getModelForPath($path, $modelClass, $theme)
|
||||
{
|
||||
return $this->datasource->usingSource($this->source, function () use ($path, $modelClass, $theme) {
|
||||
$modelObj = new $modelClass;
|
||||
|
||||
$entity = $modelClass::load(
|
||||
$theme,
|
||||
str_replace($modelObj->getObjectTypeDirName() . '/', '', $path)
|
||||
);
|
||||
|
||||
if (!isset($entity)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $entity;
|
||||
});
|
||||
}
|
||||
}
|
||||
66
modules/cms/console/ThemeUse.php
Normal file
66
modules/cms/console/ThemeUse.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php namespace Cms\Console;
|
||||
|
||||
use Cms\Classes\Theme;
|
||||
use Winter\Storm\Console\Command;
|
||||
|
||||
/**
|
||||
* Console command to switch themes.
|
||||
*
|
||||
* This switches the active theme to another one, saved to the database.
|
||||
*
|
||||
* @package winter\wn-cms-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class ThemeUse extends Command
|
||||
{
|
||||
use \Illuminate\Console\ConfirmableTrait;
|
||||
|
||||
/**
|
||||
* The console command name.
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'theme:use';
|
||||
|
||||
/**
|
||||
* @var string The name and signature of this command.
|
||||
*/
|
||||
protected $signature = 'theme:use
|
||||
{name : The name of the theme. (directory name).}
|
||||
{--f|force : Force the operation to run.}
|
||||
';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Switch the active theme.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (!$this->confirmToProceed('Change the active theme?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$newThemeName = $this->argument('name');
|
||||
$newTheme = Theme::load($newThemeName);
|
||||
|
||||
if (!$newTheme->exists($newThemeName)) {
|
||||
return $this->error(sprintf('The theme %s does not exist.', $newThemeName));
|
||||
}
|
||||
|
||||
if ($newTheme->isActiveTheme()) {
|
||||
return $this->error(sprintf('%s is already the active theme.', $newTheme->getId()));
|
||||
}
|
||||
|
||||
$activeTheme = Theme::getActiveTheme();
|
||||
$from = $activeTheme ? $activeTheme->getId() : 'nothing';
|
||||
|
||||
$this->info(sprintf('Switching theme from %s to %s', $from, $newTheme->getId()));
|
||||
|
||||
Theme::setActiveTheme($newThemeName);
|
||||
}
|
||||
}
|
||||
25
modules/cms/console/scaffold/component/component.stub
Normal file
25
modules/cms/console/scaffold/component/component.stub
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php namespace {{studly_author}}\{{studly_plugin}}\Components;
|
||||
|
||||
use Cms\Classes\ComponentBase;
|
||||
|
||||
class {{studly_name}} extends ComponentBase
|
||||
{
|
||||
/**
|
||||
* Gets the details for the component
|
||||
*/
|
||||
public function componentDetails()
|
||||
{
|
||||
return [
|
||||
'name' => '{{name}} Component',
|
||||
'description' => 'No description provided yet...'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the properties provided by the component
|
||||
*/
|
||||
public function defineProperties()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
3
modules/cms/console/scaffold/component/default.stub
Normal file
3
modules/cms/console/scaffold/component/default.stub
Normal file
@@ -0,0 +1,3 @@
|
||||
<p>This is the default markup for component {{name}}</p>
|
||||
|
||||
<small>You can delete this file if you want</small>
|
||||
33
modules/cms/console/scaffold/theme/less/assets/js/app.stub
Normal file
33
modules/cms/console/scaffold/theme/less/assets/js/app.stub
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Application
|
||||
*/
|
||||
(function($) {
|
||||
"use strict";
|
||||
|
||||
jQuery(document).ready(function($) {
|
||||
/*-------------------------------
|
||||
WINTER CMS FLASH MESSAGE HANDLING
|
||||
---------------------------------*/
|
||||
$(document).on('ajaxSetup', function(event, context) {
|
||||
// Enable AJAX handling of Flash messages on all AJAX requests
|
||||
context.options.flash = true;
|
||||
|
||||
// Enable the StripeLoadIndicator on all AJAX requests
|
||||
context.options.loading = $.oc.stripeLoadIndicator;
|
||||
|
||||
// Handle Flash Messages
|
||||
context.options.handleFlashMessage = function(message, type) {
|
||||
$.oc.flashMsg({ text: message, class: type });
|
||||
};
|
||||
|
||||
// Handle Error Messages
|
||||
context.options.handleErrorMessage = function(message) {
|
||||
$.oc.flashMsg({ text: message, class: 'error' });
|
||||
};
|
||||
});
|
||||
});
|
||||
}(jQuery));
|
||||
|
||||
if (typeof(gtag) !== 'function') {
|
||||
gtag = function() { console.log('GoogleAnalytics not present.'); }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
.content {
|
||||
margin: 2em auto;
|
||||
max-width: 1080px;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
description = "Default layout"
|
||||
==
|
||||
{% partial "site/header" %}
|
||||
|
||||
{% page %}
|
||||
|
||||
{% partial "site/footer" %}
|
||||
8
modules/cms/console/scaffold/theme/less/pages/404.stub
Normal file
8
modules/cms/console/scaffold/theme/less/pages/404.stub
Normal file
@@ -0,0 +1,8 @@
|
||||
title = "Page not found (404)"
|
||||
url = "/404"
|
||||
layout = "default"
|
||||
==
|
||||
<div class="content">
|
||||
<h1>Page not found</h1>
|
||||
<p>We're sorry, but the page you requested cannot be found.</p>
|
||||
</div>
|
||||
8
modules/cms/console/scaffold/theme/less/pages/error.stub
Normal file
8
modules/cms/console/scaffold/theme/less/pages/error.stub
Normal file
@@ -0,0 +1,8 @@
|
||||
title = "Error page (500)"
|
||||
url = "/error"
|
||||
layout = "default"
|
||||
==
|
||||
<div class="content">
|
||||
<h1>Error</h1>
|
||||
<p>We're sorry, but something went wrong and the page cannot be displayed.</p>
|
||||
</div>
|
||||
7
modules/cms/console/scaffold/theme/less/pages/home.stub
Normal file
7
modules/cms/console/scaffold/theme/less/pages/home.stub
Normal file
@@ -0,0 +1,7 @@
|
||||
title = "Home"
|
||||
url = "/"
|
||||
layout = "default"
|
||||
==
|
||||
<div class="content">
|
||||
<h1>Home Page</h1>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
{% if this.theme.googleanalytics_id is not empty %}
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id={{ this.theme.googleanalytics_id }}"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', '{{ this.theme.googleanalytics_id }}');
|
||||
</script>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,4 @@
|
||||
<link rel="stylesheet" href="{{ ['assets/less/theme.less'] | theme }}">
|
||||
|
||||
{% styles %}
|
||||
{% placeholder head %}
|
||||
@@ -0,0 +1,17 @@
|
||||
<!-- Scripts -->
|
||||
<script src="{{ [
|
||||
'@jquery',
|
||||
'@framework',
|
||||
'@framework.extras',
|
||||
|
||||
'assets/js/app.js',
|
||||
] | theme }}"></script>
|
||||
{% scripts %}
|
||||
|
||||
{% flash %}
|
||||
<p data-control="flash-message" data-interval="7" class="flashmessage {{ type }}">
|
||||
{{ message }}
|
||||
</p>
|
||||
{% endflash %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% placeholder page_title default %}{{ this.page.title }}{% endplaceholder %}</title>
|
||||
{% partial "meta/styles" %}
|
||||
{% partial "meta/seo" %}
|
||||
<meta name="generator" content="Winter CMS">
|
||||
</head>
|
||||
{% set pageId = this.page.id %}
|
||||
{% set pageTitle = this.page.title %}
|
||||
{% if pageId is empty %}
|
||||
{% set pageId = page.id %}
|
||||
{% endif %}
|
||||
{% if pageTitle is empty %}
|
||||
{% set pageTitle = page.title %}
|
||||
{% endif %}
|
||||
<body class="page-{{ pageId }} layout-{{ this.layout.id }}">
|
||||
9
modules/cms/console/scaffold/theme/less/theme.stub
Normal file
9
modules/cms/console/scaffold/theme/less/theme.stub
Normal file
@@ -0,0 +1,9 @@
|
||||
name: "{{code}}"
|
||||
description: "No description provided yet..."
|
||||
author: "Winter CMS Scaffold"
|
||||
homepage: "https://example.com"
|
||||
code: "{{code}}"
|
||||
form:
|
||||
fields:
|
||||
googleanalytics_id:
|
||||
label: 'Google Analytics ID'
|
||||
1
modules/cms/console/scaffold/theme/less/version.stub
Normal file
1
modules/cms/console/scaffold/theme/less/version.stub
Normal file
@@ -0,0 +1 @@
|
||||
1.0.1: 'Initial version'
|
||||
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
dist/
|
||||
9
modules/cms/console/scaffold/theme/tailwind/README.stub
Normal file
9
modules/cms/console/scaffold/theme/tailwind/README.stub
Normal file
@@ -0,0 +1,9 @@
|
||||
# {{code}} Winter CMS Theme
|
||||
|
||||
This theme uses [Vite](https://wintercms.com/docs/develop/docs/console/asset-compilation-vite) for asset compilation. It also uses [Tailwind CSS](https://tailwindcss.com/).
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Run [`artisan vite:install`](https://wintercms.com/docs/console/asset-compilation#mix-install) and agree when asked to modify the `package.json` file for your project in order to register & install this theme's dependencies.
|
||||
2. Run [`artisan vite:compile -p theme-{{code}} --production`](https://wintercms.com/docs/develop/docs/console/asset-compilation-vite#compile-a-vite-packages) to compile the asset files for this theme.
|
||||
3. Optionally, run [`artisan vite:watch theme-{{code}}`](https://wintercms.com/docs/develop/docs/console/asset-compilation-vite#watch-a-vite-package) while actively working on the theme to have the assets automatically recompiled in the background for you every time you make a change.
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'options' => [
|
||||
'googleanalytics_id' => 'Google Analytics ID',
|
||||
'color_primary' => 'Primary Color',
|
||||
'color_secondary' => 'secondary Color',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,8 @@
|
||||
description = "Default layout"
|
||||
default = true
|
||||
==
|
||||
{% partial "site/header" %}
|
||||
|
||||
{% page %}
|
||||
|
||||
{% partial "site/footer" %}
|
||||
@@ -0,0 +1,8 @@
|
||||
title = "Page not found (404)"
|
||||
url = "/404"
|
||||
layout = "default"
|
||||
==
|
||||
<div>
|
||||
<h1>Page not found</h1>
|
||||
<p>We're sorry, but the page you requested cannot be found.</p>
|
||||
</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
title = "Error page (500)"
|
||||
url = "/error"
|
||||
layout = "default"
|
||||
==
|
||||
<div>
|
||||
<h1>Error</h1>
|
||||
<p>We're sorry, but something went wrong and the page cannot be displayed.</p>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
title = "Home"
|
||||
url = "/"
|
||||
layout = "default"
|
||||
==
|
||||
<div class="container mx-auto">
|
||||
<h1><span class="text-primary">Home</span> <span class="text-secondary">Page</span></h1>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
{% if this.theme.googleanalytics_id is not empty %}
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id={{ this.theme.googleanalytics_id }}"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', '{{ this.theme.googleanalytics_id }}');
|
||||
</script>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,12 @@
|
||||
==
|
||||
{{ vite(['assets/src/css/theme-{{code}}.css'], 'theme-{{code}}') }}
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--primary: {{ this.theme.color_primary }};
|
||||
--secondary: {{ this.theme.color_secondary }};
|
||||
}
|
||||
</style>
|
||||
|
||||
{% styles %}
|
||||
{% placeholder head %}
|
||||
@@ -0,0 +1,21 @@
|
||||
<!-- Scripts -->
|
||||
|
||||
{# jQuery AJAX Framework #}
|
||||
<script src="{{ [
|
||||
'@jquery',
|
||||
'@framework',
|
||||
'@framework.extras',
|
||||
] | theme }}"></script>
|
||||
|
||||
{# Vite extracted assets #}
|
||||
{{ vite(['assets/src/js/theme-{{code}}.js'], 'theme-{{code}}') }}
|
||||
|
||||
{% scripts %}
|
||||
|
||||
{% flash %}
|
||||
<p data-control="flash-message" data-interval="7" class="flashmessage {{ type }}">
|
||||
{{ message }}
|
||||
</p>
|
||||
{% endflash %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% placeholder page_title default %}{{ this.page.title }}{% endplaceholder %}</title>
|
||||
{% partial "meta/styles" %}
|
||||
{% partial "meta/seo" %}
|
||||
<meta name="generator" content="Winter CMS">
|
||||
</head>
|
||||
{% set pageId = this.page.id %}
|
||||
{% set pageTitle = this.page.title %}
|
||||
{% if pageId is empty %}
|
||||
{% set pageId = page.id %}
|
||||
{% endif %}
|
||||
{% if pageTitle is empty %}
|
||||
{% set pageTitle = page.title %}
|
||||
{% endif %}
|
||||
<body class="page-{{ pageId }} layout-{{ this.layout.id }}">
|
||||
21
modules/cms/console/scaffold/theme/tailwind/theme.stub
Normal file
21
modules/cms/console/scaffold/theme/tailwind/theme.stub
Normal file
@@ -0,0 +1,21 @@
|
||||
name: "{{code}}"
|
||||
description: "No description provided yet..."
|
||||
author: "Winter CMS Scaffold"
|
||||
homepage: "https://example.com"
|
||||
code: "{{code}}"
|
||||
form:
|
||||
fields:
|
||||
googleanalytics_id:
|
||||
label: themes.{{code}}::lang.options.googleanalytics_id
|
||||
type: text
|
||||
span: full
|
||||
color_primary:
|
||||
label: themes.{{code}}::lang.options.color_primary
|
||||
type: colorpicker
|
||||
span: left
|
||||
default: "#103141"
|
||||
color_secondary:
|
||||
label: themes.{{code}}::lang.options.color_secondary
|
||||
type: colorpicker
|
||||
span: right
|
||||
default: "#2DA7C7"
|
||||
1
modules/cms/console/scaffold/theme/tailwind/version.stub
Normal file
1
modules/cms/console/scaffold/theme/tailwind/version.stub
Normal file
@@ -0,0 +1 @@
|
||||
1.0.0: 'Initial version'
|
||||
Reference in New Issue
Block a user