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:
57
modules/system/tests/models/MailBrandSettingTest.php
Normal file
57
modules/system/tests/models/MailBrandSettingTest.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace System\Tests\Models;
|
||||
|
||||
use System\Models\MailBrandSetting;
|
||||
use System\Tests\Bootstrap\PluginTestCase;
|
||||
|
||||
class MailBrandSettingTest extends PluginTestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
\System\Behaviors\SettingsModel::clearInternalCache();
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
MailBrandSetting::instance()->resetDefault();
|
||||
\System\Behaviors\SettingsModel::clearInternalCache();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Regression for GHSA-58fp-mcx6-7qf9. MailBrandSetting takes no raw user CSS
|
||||
* string but flows user input through Less_Parser::ModifyVars(), whose
|
||||
* serializeVars() helper concatenates raw values into LESS source without
|
||||
* escaping. A CSS variable value of `red; @import (inline) "/path/to/secret"`
|
||||
* therefore reaches the parser as a working @import directive. The
|
||||
* SetImportDirs deny-all gate in compileCss() must close this vector
|
||||
* regardless of form-field validation strictness.
|
||||
*/
|
||||
public function testCompileCssBlocksModifyVarsImportInjection()
|
||||
{
|
||||
$tmpSecret = tempnam(sys_get_temp_dir(), 'mailbrandsetting-leak-canary-');
|
||||
file_put_contents($tmpSecret, "APP_KEY=do-not-leak-via-modifyvars\n");
|
||||
|
||||
try {
|
||||
// Bypass form-field validation by writing the malicious value directly
|
||||
// onto the model. This simulates a model-layer bypass or a future
|
||||
// weakening of the field validator.
|
||||
$setting = MailBrandSetting::instance();
|
||||
$setting->body_bg = 'red; @import (inline) "' . $tmpSecret . '"';
|
||||
$setting->save();
|
||||
|
||||
\System\Behaviors\SettingsModel::clearInternalCache();
|
||||
|
||||
$css = MailBrandSetting::compileCss();
|
||||
|
||||
$this->assertStringNotContainsString('APP_KEY', $css);
|
||||
$this->assertStringNotContainsString('do-not-leak-via-modifyvars', $css);
|
||||
} finally {
|
||||
@unlink($tmpSecret);
|
||||
}
|
||||
}
|
||||
}
|
||||
91
modules/system/tests/models/RequestLogTest.php
Normal file
91
modules/system/tests/models/RequestLogTest.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace System\Tests\Models;
|
||||
|
||||
use System\Models\LogSetting;
|
||||
use System\Models\RequestLog;
|
||||
use System\Tests\Bootstrap\PluginTestCase;
|
||||
use Winter\Storm\Support\Facades\DB;
|
||||
|
||||
class RequestLogTest extends PluginTestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
LogSetting::set('log_requests', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the names of the indexes defined on the provided table.
|
||||
*/
|
||||
protected function getIndexNames(string $table): array
|
||||
{
|
||||
$connection = DB::connection();
|
||||
|
||||
switch ($connection->getDriverName()) {
|
||||
case 'sqlite':
|
||||
$indexes = $connection->select('PRAGMA index_list(' . $connection->getTablePrefix() . $table . ')');
|
||||
return array_column(array_map(fn ($row) => (array) $row, $indexes), 'name');
|
||||
|
||||
case 'mysql':
|
||||
$indexes = $connection->select('SHOW INDEX FROM ' . $connection->getTablePrefix() . $table);
|
||||
return array_column(array_map(fn ($row) => (array) $row, $indexes), 'Key_name');
|
||||
|
||||
case 'pgsql':
|
||||
$indexes = $connection->select(
|
||||
'SELECT indexname AS name FROM pg_indexes WHERE tablename = ?',
|
||||
[$connection->getTablePrefix() . $table]
|
||||
);
|
||||
return array_column(array_map(fn ($row) => (array) $row, $indexes), 'name');
|
||||
}
|
||||
|
||||
$this->markTestSkipped('Unsupported database driver: ' . $connection->getDriverName());
|
||||
}
|
||||
|
||||
public function testUrlAndStatusCodeAreIndexed()
|
||||
{
|
||||
// Without this index every logged request performs a full table scan of a table
|
||||
// that only ever grows
|
||||
$this->assertContains(
|
||||
'system_request_logs_url_status_code_index',
|
||||
$this->getIndexNames('system_request_logs')
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddCreatesRecord()
|
||||
{
|
||||
$record = RequestLog::add(404);
|
||||
|
||||
$this->assertNotNull($record);
|
||||
$this->assertEquals(1, $record->count);
|
||||
$this->assertEquals(404, $record->status_code);
|
||||
$this->assertEquals(1, RequestLog::count());
|
||||
}
|
||||
|
||||
public function testAddIncrementsExistingRecord()
|
||||
{
|
||||
RequestLog::add(404);
|
||||
RequestLog::add(404);
|
||||
RequestLog::add(404);
|
||||
|
||||
$this->assertEquals(1, RequestLog::count());
|
||||
$this->assertEquals(3, RequestLog::first()->count);
|
||||
}
|
||||
|
||||
public function testAddSeparatesStatusCodes()
|
||||
{
|
||||
RequestLog::add(404);
|
||||
RequestLog::add(500);
|
||||
|
||||
$this->assertEquals(2, RequestLog::count());
|
||||
}
|
||||
|
||||
public function testAddRespectsLogRequestsSetting()
|
||||
{
|
||||
LogSetting::set('log_requests', false);
|
||||
|
||||
$this->assertNull(RequestLog::add(404));
|
||||
$this->assertEquals(0, RequestLog::count());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user