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
+265
View File
@@ -0,0 +1,265 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2025 Marco Costa
* All rights reserved.
*
* Author: Marco Costa
* Email: contato@syspanel.com.br
* Project: SysCMS
*
* 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.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
*/
namespace Modules\PageBuilder\Classes;
/**
* Simple pagination class for PageBuilder module
*/
class Paginator
{
/**
* Current page
*
* @var int
*/
protected $currentPage;
/**
* Items per page
*
* @var int
*/
protected $perPage;
/**
* Total items
*
* @var int
*/
protected $total;
/**
* Path for pagination links
*
* @var string
*/
protected $path;
/**
* Query parameters
*
* @var array
*/
protected $query;
/**
* Constructor
*
* @param int $total
* @param int $perPage
* @param int $currentPage
* @param string $path
* @param array $query
*/
public function __construct(int $total, int $perPage, int $currentPage = 1, string $path = '', array $query = [])
{
$this->total = $total;
$this->perPage = $perPage;
$this->currentPage = max(1, $currentPage);
$this->path = $path ?: $_SERVER['REQUEST_URI'] ?? '';
$this->query = $query;
}
/**
* Get current page
*
* @return int
*/
public function getCurrentPage(): int
{
return $this->currentPage;
}
/**
* Get items per page
*
* @return int
*/
public function getPerPage(): int
{
return $this->perPage;
}
/**
* Get total items
*
* @return int
*/
public function getTotal(): int
{
return $this->total;
}
/**
* Get last page
*
* @return int
*/
public function getLastPage(): int
{
return max(1, (int) ceil($this->total / $this->perPage));
}
/**
* Get offset for database query
*
* @return int
*/
public function getOffset(): int
{
return ($this->currentPage - 1) * $this->perPage;
}
/**
* Check if there are more items
*
* @return bool
*/
public function hasMorePages(): bool
{
return $this->currentPage < $this->getLastPage();
}
/**
* Check if there are previous pages
*
* @return bool
*/
public function hasPreviousPages(): bool
{
return $this->currentPage > 1;
}
/**
* Get URL for a specific page
*
* @param int $page
* @return string
*/
public function getUrl(int $page): string
{
$query = $this->query;
$query['page'] = $page;
$queryString = http_build_query($query);
// Remove the existing query string from the path
$path = strtok($this->path, '?');
return $path . ($queryString ? '?' . $queryString : '');
}
/**
* Get previous page URL
*
* @return string|null
*/
public function getPreviousPageUrl(): ?string
{
if (!$this->hasPreviousPages()) {
return null;
}
return $this->getUrl($this->currentPage - 1);
}
/**
* Get next page URL
*
* @return string|null
*/
public function getNextPageUrl(): ?string
{
if (!$this->hasMorePages()) {
return null;
}
return $this->getUrl($this->currentPage + 1);
}
/**
* Get array of page numbers to display
*
* @param int $onEachSide
* @return array
*/
public function getPageNumbers(int $onEachSide = 3): array
{
$lastPage = $this->getLastPage();
$currentPage = $this->currentPage;
if ($lastPage <= ($onEachSide * 2) + 1) {
return range(1, $lastPage);
}
$pages = [];
// First page
$pages[] = 1;
// Pages before current
$start = max(2, $currentPage - $onEachSide);
$end = min($lastPage - 1, $currentPage + $onEachSide);
if ($start > 2) {
$pages[] = '...';
}
for ($i = $start; $i <= $end; $i++) {
$pages[] = $i;
}
if ($end < $lastPage - 1) {
$pages[] = '...';
}
// Last page
if ($lastPage > 1) {
$pages[] = $lastPage;
}
return $pages;
}
/**
* Convert paginator to array for template rendering
*
* @return array
*/
public function toArray(): array
{
return [
'current_page' => $this->getCurrentPage(),
'per_page' => $this->getPerPage(),
'total' => $this->getTotal(),
'last_page' => $this->getLastPage(),
'offset' => $this->getOffset(),
'has_more_pages' => $this->hasMorePages(),
'has_previous_pages' => $this->hasPreviousPages(),
'previous_page_url' => $this->getPreviousPageUrl(),
'next_page_url' => $this->getNextPageUrl(),
'page_numbers' => $this->getPageNumbers(),
];
}
}
+169
View File
@@ -0,0 +1,169 @@
<?php
namespace Modules\PageBuilder\Controllers;
use App\Controllers\BaseAdminController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Modules\PageBuilder\Models\Page;
use Modules\PageBuilder\Classes\Paginator;
use Illuminate\Support\Str;
class AdminController extends BaseAdminController
{
/**
* Lists all pages in the admin panel.
*/
public function index(Request $request): Response
{
$perPage = 15;
$currentPage = (int) $request->query->get('page', 1);
$total = Page::count();
$paginator = new Paginator($total, $perPage, $currentPage);
$pages = Page::with(['creator', 'updater'])
->orderBy('created_at', 'desc')
->skip($paginator->getOffset())
->take($perPage)
->get();
return $this->renderAdminView('@pagebuilder/admin/index.twig', [
'pages' => $pages,
'paginator' => $paginator->toArray(),
'title' => 'Gerenciar Páginas'
]);
}
/**
* Displays the page creation form.
*/
public function create(Request $request): Response
{
return $this->renderAdminView('@pagebuilder/admin/create.twig', [
'title' => 'Criar Nova Página'
]);
}
/**
* Stores a new page.
*/
public function store(Request $request): Response
{
$data = $request->request->all();
// Basic validation
if (empty($data['title']) || empty($data['slug']) || empty($data['content'])) {
$_SESSION['error'] = 'Preencha todos os campos obrigatórios (Título, Slug, Conteúdo).';
return new RedirectResponse(url('/admin/pages/create'));
}
// Check unique slug
if (Page::where('pages_slug', $data['slug'])->exists()) {
$_SESSION['error'] = 'Este slug já está sendo utilizado.';
return new RedirectResponse(url('/admin/pages/create'));
}
$data['slug'] = Str::slug($data['slug']);
$data['created_by'] = $_SESSION['user_id'] ?? null;
if (isset($data['is_published']) && $data['is_published']) {
$data['published_at'] = date('Y-m-d H:i:s');
} else {
$data['is_published'] = 0;
}
Page::create($data);
$_SESSION['success'] = 'Página criada com sucesso!';
return new RedirectResponse(url('/admin/pages'));
}
/**
* Displays the page edit form.
*/
public function edit(Request $request, int $id): Response
{
$page = Page::findOrFail($id);
return $this->renderAdminView('@pagebuilder/admin/edit.twig', [
'page' => $page,
'title' => 'Editar Página: ' . $page->title
]);
}
/**
* Updates an existing page.
*/
public function update(Request $request, int $id): Response
{
$page = Page::findOrFail($id);
$data = $request->request->all();
// Basic validation
if (empty($data['title']) || empty($data['slug']) || empty($data['content'])) {
$_SESSION['error'] = 'Preencha todos os campos obrigatórios.';
return new RedirectResponse(url("/admin/pages/{$id}/edit"));
}
// Check unique slug (ignoring the page itself)
if (Page::where('pages_slug', $data['slug'])->where('pages_id', '!=', $id)->exists()) {
$_SESSION['error'] = 'Este slug já está sendo utilizado.';
return new RedirectResponse(url("/admin/pages/{$id}/edit"));
}
$data['slug'] = Str::slug($data['slug']);
$data['updated_by'] = $_SESSION['user_id'] ?? null;
// Check if the page is being published now
if (!$page->is_published && isset($data['is_published']) && $data['is_published']) {
$data['published_at'] = date('Y-m-d H:i:s');
}
if (!isset($data['is_published'])) {
$data['is_published'] = 0;
}
$page->update($data);
$_SESSION['success'] = 'Página atualizada com sucesso!';
return new RedirectResponse(url('/admin/pages'));
}
/**
* Deletes a page.
*/
public function destroy(Request $request, int $id): Response
{
$page = Page::findOrFail($id);
$page->delete();
$_SESSION['success'] = 'Página excluída com sucesso!';
return new RedirectResponse(url('/admin/pages'));
}
/**
* Publishes a page.
*/
public function publish(Request $request, int $id): Response
{
$page = Page::findOrFail($id);
$page->publish();
$_SESSION['success'] = 'Página publicada com sucesso!';
return new RedirectResponse($request->headers->get('referer', url('/admin/pages')));
}
/**
* Sets a page as draft.
*/
public function unpublish(Request $request, int $id): Response
{
$page = Page::findOrFail($id);
$page->unpublish();
$_SESSION['success'] = 'Página definida como rascunho!';
return new RedirectResponse($request->headers->get('referer', url('/admin/pages')));
}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
namespace Modules\PageBuilder\Controllers;
use Kernel\View;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Modules\PageBuilder\Models\Page;
use Modules\Auth\Models\User;
use Twig\Error\LoaderError;
class PageController
{
protected View $view;
public function __construct(View $view)
{
$this->view = $view;
}
/**
* Displays a custom page by slug.
*/
public function show(Request $request, string $slug): Response
{
$query = Page::where('pages_slug', $slug);
// If not admin, display only published pages
if (!$this->isAdmin()) {
$query->published();
}
$page = $query->first();
if (!$page) {
return new Response('Página não encontrada', 404);
}
return $this->renderPage($page);
}
/**
* Renders the page with error handling.
*/
private function renderPage(Page $page): Response
{
$data = [
'page' => $page,
'title' => $page->meta_title ?: $page->title,
'meta_description' => $page->meta_description,
'custom_css' => $page->getCustomCss(),
'custom_js' => $page->getCustomJs(),
'css_classes' => $page->getCssClasses()
];
try {
return new Response($this->view->render($page->getLayoutPath(), $data));
} catch (LoaderError $e) {
// If the template is not found, try the default layout
error_log("Template error for page {$page->pages_slug}: " . $e->getMessage());
return new Response($this->view->render('default.twig', $data));
}
}
/**
* Checks if the current user is admin.
*/
private function isAdmin(): bool
{
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if (!isset($_SESSION['user_id'])) {
return false;
}
try {
$user = User::find($_SESSION['user_id']);
return $user && ($user->isSuperAdmin() || $user->isAdmin());
} catch (\Exception $e) {
return false;
}
}
}
+96
View File
@@ -0,0 +1,96 @@
<?php
namespace Modules\PageBuilder\Middleware;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Modules\Auth\Models\User;
use Closure;
class AdminMiddleware
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @return Response
*/
public function handle(Request $request, Closure $next): Response
{
// Check if the user is authenticated
if (!isset($_SESSION['user_id'])) {
if ($this->expectsJson($request)) {
return new JsonResponse([
'error' => 'Não autorizado',
'message' => 'Você precisa estar autenticado para acessar esta página.'
], 401);
}
return new Response('', 302, [
'Location' => url('/login')
]);
}
// Check if the user has admin permissions
$user = User::find($_SESSION['user_id']);
if (!$user || !$this->isAdmin($user)) {
if ($this->expectsJson($request)) {
return new JsonResponse([
'error' => 'Acesso negado',
'message' => 'Você não tem permissão para acessar esta página.'
], 403);
}
return new Response('', 302, [
'Location' => url('/')
]);
}
return $next($request);
}
/**
* Check if the request expects JSON.
*
* @param Request $request
* @return bool
*/
private function expectsJson(Request $request): bool
{
return $request->isXmlHttpRequest() || strpos($request->headers->get('Accept'), 'application/json') !== false;
}
/**
* Checks if the user has admin permissions.
*
* @param User $user
* @return bool
*/
private function isAdmin(User $user): bool
{
// Check if the user has the admin role
if (method_exists($user, 'hasRole') && $user->hasRole('admin')) {
return true;
}
// Check if the user has the admin.access permission
if (method_exists($user, 'hasPermission') && $user->hasPermission('admin.access')) {
return true;
}
// Check if the is_admin field exists and is true
if (isset($user->is_admin) && $user->is_admin) {
return true;
}
// Check if the level field exists and is greater than or equal to 5
if (isset($user->level) && $user->level >= 5) {
return true;
}
return false;
}
}
+175
View File
@@ -0,0 +1,175 @@
<?php
namespace Modules\PageBuilder\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Page extends Model
{
use SoftDeletes;
protected $table = 'pages';
protected $primaryKey = 'pages_id';
protected $fillable = [
'pages_title',
'pages_slug',
'pages_content',
'pages_meta_title',
'pages_meta_description',
'pages_meta_keywords',
'pages_is_published',
'pages_public',
'pages_published_at',
'pages_layout',
'pages_css_classes',
'pages_custom_css',
'pages_custom_js',
'pages_created_by',
'pages_updated_by'
];
protected $casts = [
'pages_is_published' => 'boolean',
'pages_public' => 'boolean',
'pages_published_at' => 'datetime',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'pages_custom_css' => 'string',
'pages_custom_js' => 'string'
];
protected $dates = [
'deleted_at'
];
/**
* Gets the user who created the page.
*/
public function creator()
{
return $this->belongsTo(\Modules\Auth\Models\User::class, 'pages_created_by');
}
/**
* Gets the user who last updated the page.
*/
public function updater()
{
return $this->belongsTo(\Modules\Auth\Models\User::class, 'pages_updated_by');
}
/**
* Scope for published pages.
*/
public function scopePublished($query)
{
return $query->where('pages_is_published', true)
->where('pages_published_at', '<=', \Carbon\Carbon::now());
}
/**
* Scope for unpublished pages.
*/
public function scopeDraft($query)
{
return $query->where('pages_is_published', false);
}
/**
* Gets the page URL.
*/
public function getUrlAttribute()
{
return url('pages/' . $this->pages_slug);
}
/**
* Gets the content excerpt.
*/
public function getExcerptAttribute($length = 150)
{
$content = strip_tags($this->pages_content);
return strlen($content) > $length ? substr($content, 0, $length) . '...' : $content;
}
/**
* Publishes the page.
*/
public function publish()
{
$this->pages_is_published = true;
$this->pages_published_at = \Carbon\Carbon::now();
return $this->save();
}
/**
* Sets the page as draft.
*/
public function unpublish()
{
$this->pages_is_published = false;
return $this->save();
}
/**
* Checks if the page is published.
*/
public function isPublished()
{
return $this->pages_is_published && $this->pages_published_at <= \Carbon\Carbon::now();
}
/**
* Gets the page layout.
*/
public function getLayoutPath()
{
if ($this->pages_layout) {
$layout = $this->pages_layout;
if (substr($layout, -5) !== '.twig') {
$layout .= '.twig';
}
return $layout;
}
return 'layouts/base.twig';
}
/**
* Gets the page CSS classes.
*/
public function getCssClasses()
{
return $this->pages_css_classes ?: '';
}
/**
* Gets the page custom CSS.
*/
public function getCustomCss()
{
$css = '';
if ($this->pages_custom_css) {
$css .= '<style>' . $this->pages_custom_css . '</style>' . "\n";
}
return $css;
}
/**
* Gets the page custom JavaScript.
*/
public function getCustomJs()
{
$js = '';
if ($this->pages_custom_js) {
$js .= '<script>' . $this->pages_custom_js . '</script>' . "\n";
}
return $js;
}
}
@@ -0,0 +1,34 @@
<?php
namespace Modules\PageBuilder\Providers;
use Kernel\Application;
use Kernel\View;
use Modules\PageBuilder\Models\Page;
class PageBuilderServiceProvider
{
public function register()
{
}
public function boot()
{
$app = Application::getInstance();
$app->addPublicPath('/pages*');
try {
$view = new View();
if (class_exists(Page::class)) {
// Assuming published scope exists as per original code
$pages_public = Page::published()->where('pages_public', true)->get();
$view->getEngine()->addGlobal('pages_public', $pages_public);
} else {
$view->getEngine()->addGlobal('pages_public', []);
}
} catch (\Exception $e) {
$view = new View();
$view->getEngine()->addGlobal('pages_public', []);
}
}
}