First Commit

This commit is contained in:
marco
2026-09-01 10:46:29 -03:00
commit bf96b77e9c
10000 changed files with 3508600 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
SysCMS Commercial License
Copyright (c) 2026 Marco Costa. All rights reserved.
This software is licensed, not sold. Permission is granted to use this software subject to the terms of the SysCMS Commercial License.
Redistribution, sublicensing, resale, or disclosure of this software, in whole or in part, is strictly prohibited without prior written permission from the author.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+22
View File
@@ -0,0 +1,22 @@
# System Config Module for SysCMS
System configuration management module.
## Features
- Global system settings
- Configuration profiles
- Environment-specific settings
- Configuration backup and restore
- Settings validation
## Installation
1. Upload the `systemconfig.zip` file through the module manager or extract it to the `modules/` directory.
2. Run the migrations to register the module.
3. Access system configuration in the admin panel.
## Routes
- Web routes: `routes/web.php`
## License
Copyright (c) 2026 Marco Costa. All rights reserved.
Licensed under the SysCMS Commercial License.
@@ -0,0 +1,83 @@
<?php
namespace Modules\SystemConfig\Controllers;
use App\Controllers\BaseAdminController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Modules\SystemConfig\Models\SystemConfig;
use Kernel\View;
class CustomizerController extends BaseAdminController
{
/**
* Show customizer for a specific theme
*/
public function index(Request $request, string $themeSlug): Response
{
$user = $this->getCurrentUser();
if (!$user) {
return new RedirectResponse(url('login'));
}
$baseDir = dirname(dirname(dirname(dirname(__DIR__))));
$themePath = $baseDir . '/themes/' . $themeSlug;
$customizerFile = $themePath . '/customizer.json';
if (!is_file($customizerFile)) {
return new Response("O tema " . ucfirst($themeSlug) . " não possui opções de personalização.", 404);
}
$config = json_decode(file_get_contents($customizerFile), true);
// Get current values from DB
$dbKey = 'theme_settings_' . $themeSlug;
$savedSettings = json_decode(SystemConfig::get($dbKey, '{}'), true);
// Merge defaults with saved values
foreach ($config['sections'] as &$section) {
foreach ($section['fields'] as &$field) {
$field['value'] = $savedSettings[$field['id']] ?? $field['default'];
}
}
return $this->renderAdminView('systemconfig/views/admin/customizer/index.twig', [
'title' => 'Personalizar Tema: ' . ucfirst($themeSlug),
'theme' => $themeSlug,
'config' => $config,
'saved' => $savedSettings
]);
}
/**
* Save customizer settings
*/
public function save(Request $request, string $themeSlug): Response
{
$user = $this->getCurrentUser();
if (!$user) {
return new RedirectResponse(url('login'));
}
$settings = $request->request->all();
// Remove CSRF or other non-setting fields if present
unset($settings['_token']);
$dbKey = 'theme_settings_' . $themeSlug;
$config = SystemConfig::where('system_config_key', $dbKey)->first();
if (!$config) {
$config = new SystemConfig();
$config->system_config_key = $dbKey;
$config->system_config_type = 'json';
$config->system_config_is_public = true;
}
$config->system_config_value = json_encode($settings, JSON_UNESCAPED_UNICODE);
$config->system_config_updated_by = $user->users_id ?? 1;
$config->save();
return new RedirectResponse(url('admin/themes/' . $themeSlug . '/customize?success=1'));
}
}
@@ -0,0 +1,60 @@
<?php
namespace Modules\SystemConfig\Controllers;
use App\Controllers\BaseAdminController;
use Modules\SystemConfig\Models\SystemConfig;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class SystemConfigController extends BaseAdminController
{
public function index(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new Response('Access Denied', 403);
}
$configs = SystemConfig::all();
return $this->renderAdminView('systemconfig/views/admin/index.twig', [
'configs' => $configs,
'title' => 'Configurações do Sistema'
]);
}
public function update(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new Response('Access Denied', 403);
}
$data = $request->request->all();
foreach ($data as $key => $value) {
if ($key === 'csrf_token') {
continue;
}
$config = SystemConfig::where('system_config_key', $key)->first();
if ($config) {
$config->system_config_value = $value;
$config->system_config_updated_by = $user->users_id;
$config->save();
} else {
SystemConfig::create([
'system_config_key' => $key,
'system_config_value' => $value,
'system_config_updated_by' => $user->users_id
]);
}
}
return new Response('', 302, ['Location' => url('admin/systemconfig')]);
}
}
@@ -0,0 +1,125 @@
<?php
namespace Modules\SystemConfig\Controllers;
use App\Controllers\BaseAdminController;
use Modules\SystemConfig\Models\SystemConfig;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
class ThemeController extends BaseAdminController
{
public function index(Request $request): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new Response('Access Denied', 403);
}
$themesDir = dirname(dirname(dirname(dirname(__DIR__)))) . '/themes';
$themes = [];
if (is_dir($themesDir)) {
$folders = array_diff(scandir($themesDir), ['.', '..']);
foreach ($folders as $folder) {
if (is_dir($themesDir . '/' . $folder)) {
$themes[$folder] = $this->getThemeInfo($themesDir . '/' . $folder, $folder);
}
}
}
$activeTheme = \get_active_theme();
return $this->renderAdminView('systemconfig/views/admin/themes/index.twig', [
'themes' => $themes,
'activeTheme' => $activeTheme,
'title' => 'Gerenciar Temas'
]);
}
public function activate(Request $request, string $theme): Response
{
$user = $this->getCurrentUser();
if (!$user || !$user->isSuperAdmin()) {
return new Response('Access Denied', 403);
}
$themesDir = dirname(dirname(dirname(dirname(__DIR__)))) . '/themes';
if (!is_dir($themesDir . '/' . $theme)) {
return new Response('Tema não encontrado', 404);
}
SystemConfig::updateOrCreate(
['system_config_key' => 'active_theme'],
[
'system_config_value' => $theme,
'system_config_updated_by' => $user->users_id,
'system_config_type' => 'string'
]
);
// Clear Twig cache
$cacheDir = dirname(dirname(dirname(dirname(__DIR__)))) . '/cache/twig';
if (is_dir($cacheDir)) {
$this->rrmdir($cacheDir);
}
return new RedirectResponse(url('admin/themes'));
}
protected function getThemeInfo(string $path, string $slug): array
{
$info = [
'slug' => $slug,
'name' => $slug,
'version' => '1.0.0',
'author' => 'Desconhecido',
'description' => '',
'screenshot' => null,
'is_customizable' => file_exists($path . '/customizer.json')
];
$stylePath = $path . '/style.css';
if (file_exists($stylePath)) {
$content = file_get_contents($stylePath);
if (preg_match('/Theme Name:\s*(.*)$/mi', $content, $matches)) {
$info['name'] = trim($matches[1]);
}
if (preg_match('/Version:\s*(.*)$/mi', $content, $matches)) {
$info['version'] = trim($matches[1]);
}
if (preg_match('/Author:\s*(.*)$/mi', $content, $matches)) {
$info['author'] = trim($matches[1]);
}
if (preg_match('/Description:\s*(.*)$/mi', $content, $matches)) {
$info['description'] = trim($matches[1]);
}
}
if (file_exists($path . '/screenshot.png')) {
$info['screenshot'] = '/themes/' . $slug . '/screenshot.png';
} elseif (file_exists($path . '/screenshot.jpg')) {
$info['screenshot'] = '/themes/' . $slug . '/screenshot.jpg';
}
return $info;
}
private function rrmdir($dir)
{
if (is_dir($dir)) {
$objects = scandir($dir);
foreach ($objects as $object) {
if ($object != "." && $object != "..") {
if (is_dir($dir . DIRECTORY_SEPARATOR . $object) && !is_link($dir . "/" . $object)) {
$this->rrmdir($dir . DIRECTORY_SEPARATOR . $object);
} else {
unlink($dir . DIRECTORY_SEPARATOR . $object);
}
}
}
rmdir($dir);
}
}
}
@@ -0,0 +1,91 @@
<?php
namespace Modules\SystemConfig\Middleware;
use Closure;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Modules\SystemConfig\Models\SystemConfig;
use Kernel\View;
class MaintenanceMiddleware
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
// Check if maintenance mode is enabled
$maintenanceMode = false;
try {
if (class_exists(SystemConfig::class)) {
$config = SystemConfig::where('system_config_key', 'maintenance_mode')->first();
if ($config && $config->system_config_value == '1') {
$maintenanceMode = true;
}
}
} catch (\Exception $e) {
// If DB fails, assume not in maintenance or handle gracefully
$maintenanceMode = false;
}
if (!$maintenanceMode) {
return $next($request);
}
// Inject maintenance mode variable into view
try {
$view = new View();
$view->getEngine()->addGlobal('is_maintenance_mode', true);
} catch (\Throwable $e) {
}
// Allow access to login route and assets
$path = $request->getPathInfo();
$allowedRoutes = ['/login', '/auth/login'];
$isAsset = preg_match('/\.(css|js|jpg|jpeg|png|gif|ico|woff|woff2|ttf|svg)$/', $path);
if (in_array($path, $allowedRoutes) || $isAsset) {
return $next($request);
}
// Allow Super Admin to bypass
if ($this->isSuperAdmin()) {
return $next($request);
}
// Render maintenance page
try {
$view = new View();
return new Response($view->render('errors/maintenance.twig'), 503);
} catch (\Throwable $e) {
return new Response('Maintenance Mode', 503);
}
}
/**
* Check if current user is Super Admin.
*
* @return bool
*/
protected function isSuperAdmin(): bool
{
if (!isset($_SESSION['user_id'])) {
return false;
}
try {
if (class_exists('\Modules\Auth\Models\User')) {
$user = \Modules\Auth\Models\User::find($_SESSION['user_id']);
return $user && $user->isSuperAdmin();
}
} catch (\Exception $e) {
return false;
}
return false;
}
}
+130
View File
@@ -0,0 +1,130 @@
<?php
namespace Modules\SystemConfig\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class SystemConfig extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'system_config';
/**
* The primary key associated with table.
*
* @var string
*/
protected $primaryKey = 'system_config_id';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'system_config_key',
'system_config_value',
'system_config_type',
'system_config_description',
'system_config_is_public',
'system_config_updated_by'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'system_config_is_public' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = [
'created_at',
'updated_at'
];
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
/**
* Get the user who updated the config.
*/
public function updatedByUser(): BelongsTo
{
// Check if Auth module exists before relating
if (class_exists('\Modules\Auth\Models\User')) {
return $this->belongsTo(\Modules\Auth\Models\User::class, 'system_config_updated_by');
}
// Fallback or return empty relation if Auth module is missing (unlikely but safe)
return $this->belongsTo(self::class, 'system_config_updated_by')->whereNull('system_config_id');
}
/**
* Get config value by key.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public static function get(string $key, $default = null)
{
$config = self::where('system_config_key', $key)->first();
if (!$config) {
return $default;
}
// Handle type casting if needed based on system_config_type
// For now, return value directly
return $config->system_config_value;
}
/**
* Set config value.
*
* @param string $key
* @param mixed $value
* @param string $type
* @param string|null $description
* @param bool $isPublic
* @return self
*/
public static function set(string $key, $value, string $type = 'string', ?string $description = null, bool $isPublic = false): self
{
$config = self::firstOrNew(['system_config_key' => $key]);
$config->system_config_value = $value;
$config->system_config_type = $type;
if ($description !== null) {
$config->system_config_description = $description;
}
$config->system_config_is_public = $isPublic;
if (isset($_SESSION['user_id'])) {
$config->system_config_updated_by = $_SESSION['user_id'];
}
$config->save();
return $config;
}
}
@@ -0,0 +1,23 @@
<?php
namespace Modules\SystemConfig\Providers;
use Kernel\Application;
use Modules\SystemConfig\Middleware\MaintenanceMiddleware;
class SystemConfigServiceProvider
{
public function register()
{
}
public function boot()
{
$app = Application::getInstance();
// Register Global Middleware (runs on all requests)
if (method_exists($app, 'addGlobalMiddleware')) {
$app->addGlobalMiddleware(new MaintenanceMiddleware());
}
}
}
@@ -0,0 +1,57 @@
<?php
use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Database\Schema\Blueprint;
class CreateSystemConfigTables
{
public function up()
{
// System Config
if (!Capsule::schema()->hasTable('system_config')) {
Capsule::schema()->create('system_config', function (Blueprint $table) {
$table->increments('system_config_id');
$table->string('system_config_key')->unique();
$table->text('system_config_value')->nullable();
$table->string('system_config_type')->default('string'); // string, boolean, integer, json
$table->text('system_config_description')->nullable();
$table->boolean('system_config_is_public')->default(false);
$table->integer('system_config_updated_by')->unsigned()->nullable();
$table->timestamps();
});
}
// Modules Table (Core table, placed here for system initialization)
if (!Capsule::schema()->hasTable('modules')) {
Capsule::schema()->create('modules', function (Blueprint $table) {
$table->increments('modules_id');
$table->string('modules_name')->unique(); // Folder name
$table->string('modules_display_name')->nullable();
$table->text('modules_description')->nullable();
$table->string('modules_version')->default('1.0.0');
$table->string('modules_author')->nullable();
$table->boolean('modules_ative')->default(false);
$table->boolean('modules_public')->default(false);
$table->boolean('modules_blocked')->default(false);
$table->json('modules_dependencies')->nullable();
$table->json('modules_permissions')->nullable();
$table->json('modules_settings')->nullable();
$table->string('modules_path')->nullable();
$table->string('modules_namespace')->nullable();
$table->string('modules_icon')->nullable();
$table->boolean('menu_side_bar')->default(false);
$table->boolean('menu_nav_bar')->default(false);
$table->integer('modules_order')->default(0);
$table->timestamp('modules_install_date')->nullable();
$table->timestamp('modules_update_date')->nullable();
$table->timestamps();
});
}
}
public function down()
{
Capsule::schema()->dropIfExists('modules');
Capsule::schema()->dropIfExists('system_config');
}
}
@@ -0,0 +1,76 @@
<?php
use Illuminate\Database\Capsule\Manager as Capsule;
use App\Models\Module;
use Modules\Company\Models\Company;
class InsertSystemConfigModule
{
public function up()
{
$moduleData = [
'modules_name' => 'systemconfig',
'modules_display_name' => 'Configurações do Sistema',
'modules_description' => 'Módulo de configuração geral do sistema SysCMS',
'modules_version' => '1.0.0',
'modules_author' => 'SysCMS Team',
'modules_ative' => true,
'modules_public' => false,
'modules_blocked' => false,
'modules_dependencies' => [],
'modules_permissions' => [
"systemconfig.view",
"systemconfig.edit"
],
'modules_settings' => [],
'modules_path' => 'modules/systemconfig',
'modules_namespace' => 'Modules\\SystemConfig',
'modules_icon' => 'bi bi-gear', // Inferred as none in json, but good practice
'menu_side_bar' => true,
'menu_nav_bar' => false,
'modules_order' => 99, // Usually high number for settings
'modules_install_date' => date('Y-m-d H:i:s'),
'modules_update_date' => date('Y-m-d H:i:s')
];
$existingModule = Module::where('modules_name', 'systemconfig')->first();
if (!$existingModule) {
$module = Module::create($moduleData);
if (Capsule::schema()->hasTable('bus')) {
$companies = Company::all();
foreach ($companies as $company) {
$exists = Capsule::table('bus_modules')
->where('bus_modules_bus_id', $company->bus_id)
->where('bus_modules_module_id', $module->modules_id)
->exists();
if (!$exists) {
Capsule::table('bus_modules')->insert([
'bus_modules_bus_id' => $company->bus_id,
'bus_modules_module_id' => $module->modules_id,
'bus_modules_is_enabled' => true,
'bus_modules_settings' => json_encode([]),
'bus_modules_enabled_by' => 1,
'bus_modules_enabled_at' => date('Y-m-d H:i:s'),
'bus_modules_show_public_nav' => true,
'bus_modules_show_dashboard_nav' => true,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
]);
}
}
}
}
}
public function down()
{
$module = Module::where('modules_name', 'systemconfig')->first();
if ($module) {
Capsule::table('bus_modules')->where('bus_modules_module_id', $module->modules_id)->delete();
$module->delete();
}
}
}
@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Capsule\Manager as Capsule;
class AddActiveThemeToConfig
{
public function up()
{
$exists = Capsule::table('system_config')
->where('system_config_key', 'active_theme')
->exists();
if (!$exists) {
Capsule::table('system_config')->insert([
'system_config_key' => 'active_theme',
'system_config_value' => 'default',
'system_config_type' => 'string',
'system_config_description' => 'O tema atualmente ativo no sistema.',
'system_config_is_public' => true,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
]);
}
}
public function down()
{
Capsule::table('system_config')
->where('system_config_key', 'active_theme')
->delete();
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"title": "Systemkonfigurationen",
"general_settings": "Allgemeine Einstellungen",
"yes": "Ja",
"no": "Nein",
"key": "Schlüssel",
"no_configs": "Keine Konfigurationen im System gefunden.",
"save_changes": "Änderungen speichern"
}
+9
View File
@@ -0,0 +1,9 @@
{
"title": "System Configurations",
"general_settings": "General Settings",
"yes": "Yes",
"no": "No",
"key": "Key",
"no_configs": "No configurations found in the system.",
"save_changes": "Save Changes"
}
+9
View File
@@ -0,0 +1,9 @@
{
"title": "Configuraciones del Sistema",
"general_settings": "Configuraciones Generales",
"yes": "Sí",
"no": "No",
"key": "Clave",
"no_configs": "No se encontraron configuraciones en el sistema.",
"save_changes": "Guardar Cambios"
}
+9
View File
@@ -0,0 +1,9 @@
{
"title": "Configurations du Système",
"general_settings": "Paramètres Généraux",
"yes": "Oui",
"no": "Non",
"key": "Clé",
"no_configs": "Aucune configuration trouvée dans le système.",
"save_changes": "Enregistrer les modifications"
}
+9
View File
@@ -0,0 +1,9 @@
{
"title": "Configurações do Sistema",
"general_settings": "Configurações Gerais",
"yes": "Sim",
"no": "Não",
"key": "Chave",
"no_configs": "Nenhuma configuração encontrada no sistema.",
"save_changes": "Salvar Alterações"
}
+28
View File
@@ -0,0 +1,28 @@
{
"name": "systemconfig",
"display_name": "Configurações do Sistema",
"description": "Módulo de configuração geral do sistema SysCMS",
"version": "1.0.0",
"author": "SysCMS Team",
"dependencies": [],
"permissions": [
"systemconfig.view",
"systemconfig.edit"
],
"settings": [],
"providers": [
"Modules\\SystemConfig\\Providers\\SystemConfigServiceProvider"
],
"routes": {
"web": "routes/web.php"
},
"views": "views",
"locales": "locales",
"module_active": true,
"module_public": false,
"module_blocked": false,
"ready": true,
"tables": [
"system_config"
]
}
+16
View File
@@ -0,0 +1,16 @@
<?php
use Kernel\Application;
return function (Application $app) {
$app->addRoute('GET', '/admin/systemconfig', [\Modules\SystemConfig\Controllers\SystemConfigController::class, 'index']);
$app->addRoute('POST', '/admin/systemconfig/update', [\Modules\SystemConfig\Controllers\SystemConfigController::class, 'update']);
// Theme Management
$app->addRoute('GET', '/admin/themes', [\Modules\SystemConfig\Controllers\ThemeController::class, 'index']);
$app->addRoute('POST', '/admin/themes/activate/{theme}', [\Modules\SystemConfig\Controllers\ThemeController::class, 'activate']);
// Theme Customizer
$app->addRoute('GET', '/admin/themes/{theme}/customize', [\Modules\SystemConfig\Controllers\CustomizerController::class, 'index']);
$app->addRoute('POST', '/admin/themes/{theme}/customize/save', [\Modules\SystemConfig\Controllers\CustomizerController::class, 'save']);
};
+92
View File
@@ -0,0 +1,92 @@
{% extends "layouts/dashboard.twig" %}
{% block title %}{{ title }} - {{ platformName }}{% endblock %}
{% block content %}
<div class="container-fluid px-4">
<h1 class="mt-4">{{ title }}</h1>
<ol class="breadcrumb mb-4">
<li class="breadcrumb-item"><a href="{{ url('dashboard') }}">{{ __('layout.menu.dashboard') }}</a></li>
<li class="breadcrumb-item"><a href="{{ url('admin/themes') }}">Temas</a></li>
<li class="breadcrumb-item active">Personalizar</li>
</ol>
{% if session_has('success') or current_uri matches '/success=1/' %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
Configurações salvas com sucesso!
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
<div class="row">
<div class="col-lg-8">
<form action="{{ url('admin/themes/' ~ theme ~ '/customize/save') }}" method="POST">
{% for section in config.sections %}
<div class="card mb-4">
<div class="card-header bg-light">
<i class="bi bi-gear-fill me-1"></i>
<strong>{{ section.title }}</strong>
</div>
<div class="card-body">
{% for field in section.fields %}
<div class="mb-3 row align-items-center">
<label for="{{ field.id }}" class="col-sm-4 col-form-label">{{ field.label }}</label>
<div class="col-sm-8">
{% if field.type == 'color' %}
<div class="d-flex align-items-center">
<input type="color" class="form-control form-control-color me-2" id="{{ field.id }}" name="{{ field.id }}" value="{{ field.value }}" title="Escolha a cor">
<code>{{ field.value }}</code>
</div>
{% elseif field.type == 'text' %}
<input type="text" class="form-control" id="{{ field.id }}" name="{{ field.id }}" value="{{ field.value }}">
{% elseif field.type == 'switch' %}
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="{{ field.id }}" name="{{ field.id }}" value="1" {{ field.value ? 'checked' : '' }}>
</div>
{% elseif field.type == 'select' %}
<select class="form-select" id="{{ field.id }}" name="{{ field.id }}">
{% for val, label in field.options %}
<option value="{{ val }}" {{ field.value == val ? 'selected' : '' }}>{{ label }}</option>
{% endfor %}
</select>
{% endif %}
{% if field.description %}
<div class="form-text">{{ field.description }}</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
<div class="d-grid gap-2 d-md-flex justify-content-md-end mb-5">
<a href="{{ url('admin/themes') }}" class="btn btn-secondary">Cancelar</a>
<button type="submit" class="btn btn-primary">Salvar Alterações</button>
</div>
</form>
</div>
<div class="col-lg-4">
<div class="card mb-4 shadow-sm">
<div class="card-header bg-primary text-white">
<i class="bi bi-info-circle me-1"></i>
Dica de Uso
</div>
<div class="card-body">
<p>Essas configurações são injetadas automaticamente no seu tema.</p>
<p>Você pode acessá-las no Twig usando:</p>
<pre class="bg-light p-2 rounded"><code>{{ '{{ theme_settings.campo_id }}' }}</code></pre>
<hr>
<h6>Exemplo de CSS dinâmico:</h6>
<pre class="bg-light p-2 rounded small"><code>&lt;style&gt;
:root {
--primary-color: {{ '{{ theme_settings.primary_color }}' }};
}
&lt;/style&gt;</code></pre>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
+53
View File
@@ -0,0 +1,53 @@
{% extends "layouts/dashboard.twig" %}
{% block title %}{{ __('systemconfig.title') }}{% endblock %}
{% block content %}
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">{{ __('systemconfig.title') }}</h1>
</div>
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">{{ __('systemconfig.general_settings') }}</h6>
</div>
<div class="card-body">
<form method="POST" action="{{ url('admin/systemconfig/update') }}">
{{ csrf_field()|raw }}
{% if configs|length > 0 %}
{% for config in configs %}
<div class="mb-3">
<label for="{{ config.system_config_key }}" class="form-label fw-bold">
{{ config.system_config_description ? config.system_config_description : config.system_config_key }}
</label>
{% if config.system_config_type == 'boolean' %}
<select class="form-select" id="{{ config.system_config_key }}" name="{{ config.system_config_key }}">
<option value="1" {% if config.system_config_value == '1' %}selected{% endif %}>{{ __('systemconfig.yes') }}</option>
<option value="0" {% if config.system_config_value == '0' %}selected{% endif %}>{{ __('systemconfig.no') }}</option>
</select>
{% elseif config.system_config_type == 'textarea' %}
<textarea class="form-control" id="{{ config.system_config_key }}" name="{{ config.system_config_key }}" rows="3">{{ config.system_config_value }}</textarea>
{% else %}
<input type="text" class="form-control" id="{{ config.system_config_key }}" name="{{ config.system_config_key }}" value="{{ config.system_config_value }}">
{% endif %}
<div class="form-text text-muted small">{{ __('systemconfig.key') }}: {{ config.system_config_key }}</div>
</div>
{% endfor %}
{% else %}
<div class="alert alert-info">
{{ __('systemconfig.no_configs') }}
</div>
{% endif %}
<div class="mt-4">
<button type="submit" class="btn btn-primary">
<i class="bi bi-save"></i> {{ __('systemconfig.save_changes') }}
</button>
</div>
</form>
</div>
</div>
{% endblock %}
+82
View File
@@ -0,0 +1,82 @@
{% extends 'layouts/dashboard.twig' %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid px-4">
<h1 class="mt-4">Temas</h1>
<ol class="breadcrumb mb-4">
<li class="breadcrumb-item"><a href="{{ url('admin') }}">Dashboard</a></li>
<li class="breadcrumb-item active">Temas</li>
</ol>
<div class="row">
{% for slug, theme in themes %}
<div class="col-xl-4 col-md-6 mb-4">
<div class="card h-100 theme-card {% if slug == activeTheme %}border-primary border-3{% endif %} shadow-sm">
{% if theme.screenshot %}
<img src="{{ theme.screenshot }}" class="card-img-top" alt="{{ theme.name }}" style="height: 200px; object-fit: cover;">
{% else %}
<div class="card-img-top bg-secondary d-flex align-items-center justify-content-center text-white" style="height: 200px;">
<i class="bi bi-image" style="font-size: 4rem;"></i>
</div>
{% endif %}
<div class="card-body">
<h5 class="card-title d-flex justify-content-between align-items-center">
{{ theme.name }}
{% if slug == activeTheme %}
<span class="badge bg-primary">Ativo</span>
{% endif %}
</h5>
<p class="card-text text-muted small">{{ theme.description|slice(0, 100) }}{% if theme.description|length > 100 %}...{% endif %}</p>
<div class="theme-meta mb-3">
<span class="badge bg-light text-dark">v{{ theme.version }}</span>
<span class="badge bg-light text-dark">Por: {{ theme.author }}</span>
</div>
</div>
<div class="card-footer bg-white border-top-0 pb-3">
<div class="d-grid gap-2">
{% if slug == activeTheme %}
{% if theme.is_customizable %}
<a href="{{ url('admin/themes/' ~ slug ~ '/customize') }}" class="btn btn-outline-primary">Personalizar</a>
{% else %}
<button class="btn btn-outline-secondary" disabled title="Este tema não suporta o Customizer">Personalizar</button>
{% endif %}
{% else %}
<form action="{{ url('admin/themes/activate/' ~ slug) }}" method="POST">
{{ csrf_field()|raw }}
<button type="submit" class="btn btn-primary w-100">Ativar</button>
</form>
{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
{# Card para Adicionar Novo Tema (Simulação) #}
<div class="col-xl-4 col-md-6 mb-4">
<div class="card h-100 border-dashed d-flex align-items-center justify-content-center bg-light" style="border: 2px dashed #dee2e6; min-height: 350px;">
<div class="text-center p-5">
<i class="bi bi-plus-circle text-muted" style="font-size: 3rem;"></i>
<h5 class="mt-3 text-muted">Adicionar Novo Tema</h5>
<p class="text-muted small">Faça upload de um arquivo .zip ou explore o repositório.</p>
<button class="btn btn-outline-primary btn-sm mt-2" disabled>Upload (Breve)</button>
</div>
</div>
</div>
</div>
</div>
<style>
.theme-card:hover {
transform: translateY(-5px);
transition: all 0.3s ease;
}
.border-dashed {
border-style: dashed !important;
}
</style>
{% endblock %}