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
+432
View File
@@ -0,0 +1,432 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2026 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\Notes\Controllers;
use App\Controllers\BaseAdminController;
use Kernel\View;
use Symfony\Component\HttpFoundation\Request;
use Modules\Notes\Models\Note;
use Modules\Notes\Models\Subject;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Capsule\Manager as Capsule;
/**
* Class NotesController
*
* Main controller for Notes management.
*
* @package Modules\Notes\Controllers
*/
class NotesController extends BaseAdminController
{
/**
* NotesController constructor.
*
* @param View $view
*/
public function __construct(View $view)
{
parent::__construct($view);
}
/**
* Check if user has permission.
*
* @param string $permission
* @return void
*/
protected function checkPermission(string $permission)
{
$user = $this->getCurrentUser();
if (!$user || !$user->hasPermission($permission)) {
$this->addFlash('error', 'Você não tem permissão para acessar esta área.');
$this->redirect('admin/dashboard')->send();
exit;
}
}
/**
* Display a listing of the notes.
*
* @param Request $request
* @return \Symfony\Component\HttpFoundation\Response
*/
public function index(Request $request)
{
$this->checkPermission('notes.view');
$query = Note::currentUser()->with('subject');
// Search system
if ($search = $request->query->get('search')) {
$query->where(function ($q) use ($search) {
$q->where('notes_title', 'like', "%{$search}%")
->orWhere('notes_content', 'like', "%{$search}%");
});
}
// Subject filter
if ($subject_id = $request->query->get('subject_id')) {
$query->where('notes_subject_id', $subject_id);
}
$notes = $query->orderBy('notes_is_pinned', 'desc')
->orderBy('created_at', 'desc')
->get();
// Get subjects for filter and select
$subjects = Subject::currentUser()->orderBy('subjects_name')->get();
return $this->renderAdminView('@notes/admin/index.twig', [
'title' => __('notes.title'),
'notes' => $notes,
'subjects' => $subjects,
'filters' => [
'search' => $search,
'subject_id' => $subject_id
]
]);
}
/**
* Show the form for creating a new note.
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function create()
{
$this->checkPermission('notes.create');
$subjects = Subject::currentUser()->orderBy('subjects_name')->get();
return $this->renderAdminView('@notes/admin/create.twig', [
'title' => __('notes.add_note'),
'subjects' => $subjects
]);
}
/**
* Store a newly created note.
*
* @param Request $request
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function store(Request $request)
{
$this->checkPermission('notes.create');
$data = $request->request->all();
try {
// Ensure session is started
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$busId = $_SESSION['user_idbus'] ?? null;
if (empty($busId)) {
$busId = 1;
}
$noteData = [
'notes_idbus' => $busId,
'notes_iduser' => $_SESSION['user_id'] ?? null,
'notes_title' => $data['notes_title'],
'notes_content' => $data['notes_content'] ?? null,
'notes_subject_id' => $data['notes_subject_id'] ?: null,
'notes_color' => $data['notes_color'] ?? '#ffffff',
'notes_is_pinned' => isset($data['notes_is_pinned']) ? 1 : 0
];
// Handle image upload
if ($request->files->has('notes_image') && $request->files->get('notes_image')) {
$file = $request->files->get('notes_image');
if ($file->isValid()) {
$noteData['notes_image'] = $this->handleUpload($file);
}
}
// Handle file upload
if ($request->files->has('notes_file') && $request->files->get('notes_file')) {
$file = $request->files->get('notes_file');
if ($file->isValid()) {
$noteData['notes_file'] = $this->handleUpload($file);
}
}
Note::create($noteData);
$this->addFlash('success', __('notes.flash_note_created'));
} catch (\Exception $e) {
// Check if error is missing column (SQLSTATE[42S22])
if (strpos($e->getMessage(), '42S22') !== false) {
\App\Helpers\Log::warning('Missing columns in notes table. Migration might be pending.', ['error' => $e->getMessage()]);
// Try saving without the new columns
unset($noteData['notes_image'], $noteData['notes_file']);
try {
Note::create($noteData);
$this->addFlash('success', __('notes.flash_note_created') . ' (Aviso: Anexos não suportados até que a migração seja executada)');
} catch (\Exception $e2) {
\App\Helpers\Log::error('Error storing note fallback', ['error' => $e2->getMessage()]);
$this->addFlash('error', 'Erro ao criar a nota: ' . $e2->getMessage());
}
} else {
\App\Helpers\Log::error('Error storing note', ['error' => $e->getMessage()]);
$this->addFlash('error', 'Erro ao criar a nota: ' . $e->getMessage());
}
}
return $this->redirect('/admin/notes');
}
/**
* Show the form for editing the specified note.
*
* @param Request $request
* @param int $id
* @return \Symfony\Component\HttpFoundation\Response
*/
public function edit(Request $request, $id)
{
$this->checkPermission('notes.edit');
$note = Note::currentUser()->findOrFail($id);
$subjects = Subject::currentUser()->orderBy('subjects_name')->get();
return $this->renderAdminView('@notes/admin/edit.twig', [
'title' => __('notes.edit_note'),
'note' => $note,
'subjects' => $subjects
]);
}
/**
* Update the specified note.
*
* @param Request $request
* @param int $id
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function update(Request $request, $id)
{
$this->checkPermission('notes.edit');
$data = $request->request->all();
try {
$note = Note::currentUser()->findOrFail($id);
$noteData = [
'notes_title' => $data['notes_title'],
'notes_content' => $data['notes_content'] ?? null,
'notes_subject_id' => $data['notes_subject_id'] ?: null,
'notes_color' => $data['notes_color'] ?? '#ffffff',
'notes_is_pinned' => isset($data['notes_is_pinned']) ? 1 : 0
];
// Handle image upload
if ($request->files->has('notes_image') && $request->files->get('notes_image')) {
$file = $request->files->get('notes_image');
if ($file->isValid()) {
// Delete old image
if ($note->notes_image) {
$this->deletePhysicalFile($note->notes_image);
}
$noteData['notes_image'] = $this->handleUpload($file);
}
}
// Handle file upload
if ($request->files->has('notes_file') && $request->files->get('notes_file')) {
$file = $request->files->get('notes_file');
if ($file->isValid()) {
// Delete old file
if ($note->notes_file) {
$this->deletePhysicalFile($note->notes_file);
}
$noteData['notes_file'] = $this->handleUpload($file);
}
}
$note->update($noteData);
$this->addFlash('success', __('notes.flash_note_updated'));
} catch (\Exception $e) {
// Check if error is missing column (SQLSTATE[42S22])
if (strpos($e->getMessage(), '42S22') !== false) {
\App\Helpers\Log::warning('Missing columns in notes table. Migration might be pending.', ['id' => $id, 'error' => $e->getMessage()]);
// Try updating without the new columns
unset($noteData['notes_image'], $noteData['notes_file']);
try {
$note->update($noteData);
$this->addFlash('success', __('notes.flash_note_updated') . ' (Aviso: Anexos não suportados até que a migração seja executada)');
} catch (\Exception $e2) {
\App\Helpers\Log::error('Error updating note fallback', ['id' => $id, 'error' => $e2->getMessage()]);
$this->addFlash('error', 'Erro ao atualizar a nota: ' . $e2->getMessage());
}
} else {
\App\Helpers\Log::error('Error updating note', ['id' => $id, 'error' => $e->getMessage()]);
$this->addFlash('error', 'Erro ao atualizar a nota: ' . $e->getMessage());
}
}
return $this->redirect('/admin/notes');
}
/**
* Remove the specified note.
*
* @param Request $request
* @param int $id
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function delete(Request $request, $id)
{
$this->checkPermission('notes.delete');
$note = Note::currentUser()->findOrFail($id);
// Delete attachments
if ($note->notes_image) {
$this->deletePhysicalFile($note->notes_image);
}
if ($note->notes_file) {
$this->deletePhysicalFile($note->notes_file);
}
$note->delete();
$this->addFlash('success', __('notes.flash_note_deleted'));
return $this->redirect('/admin/notes');
}
/**
* Handle file upload.
*
* @param \Symfony\Component\HttpFoundation\File\UploadedFile $file
* @return string|null
*/
protected function handleUpload($file): ?string
{
if (!$file) {
return null;
}
$destination = dirname(__DIR__, 4) . '/storage/uploads/notes';
if (!is_dir($destination)) {
mkdir($destination, 0755, true);
}
$filename = uniqid('img_') . '.' . $file->guessExtension();
$file->move($destination, $filename);
return $filename;
}
/**
* Delete physical file.
*
* @param string $filename
* @return void
*/
protected function deletePhysicalFile(string $filename)
{
$filePath = dirname(__DIR__, 4) . '/storage/uploads/notes/' . $filename;
if (file_exists($filePath) && is_file($filePath)) {
unlink($filePath);
}
}
/**
* Toggle the pinned status of a note.
*
* @param Request $request
* @param int $id
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function pin(Request $request, $id)
{
$this->checkPermission('notes.edit');
$note = Note::currentUser()->findOrFail($id);
$note->notes_is_pinned = !$note->notes_is_pinned;
$note->save();
$this->addFlash('success', $note->notes_is_pinned ? __('notes.flash_note_pinned') : __('notes.flash_note_unpinned'));
return $this->redirect('/admin/notes');
}
/**
* Store a newly created subject.
*
* @param Request $request
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function storeSubject(Request $request)
{
$this->checkPermission('notes.create');
$data = $request->request->all();
// Ensure session is started
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$busId = $_SESSION['user_idbus'] ?? null;
if (empty($busId)) {
$busId = 1;
}
Subject::create([
'subjects_idbus' => $busId,
'subjects_iduser' => $_SESSION['user_id'] ?? null,
'subjects_name' => $data['subjects_name'],
'subjects_color' => $data['subjects_color'] ?? '#6c757d'
]);
$this->addFlash('success', __('notes.flash_subject_created'));
return $this->redirect('/admin/notes');
}
/**
* Delete a subject.
*
* @param Request $request
* @param int $id
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function deleteSubject(Request $request, $id)
{
$this->checkPermission('notes.delete');
$subject = Subject::currentUser()->findOrFail($id);
// Remove reference from notes
Note::currentUser()->where('notes_subject_id', $id)->update(['notes_subject_id' => null]);
$subject->delete();
$this->addFlash('success', __('notes.flash_subject_deleted'));
return $this->redirect('/admin/notes');
}
}
+109
View File
@@ -0,0 +1,109 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2026 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\Notes\Models;
use Illuminate\Database\Eloquent\Model;
/**
* Class Note
*
* Model for notes.
*
* @package Modules\Notes\Models
*/
class Note extends Model
{
/**
* @var string The table associated with the model.
*/
protected $table = 'notes';
/**
* @var string The primary key associated with the table.
*/
protected $primaryKey = 'notes_id';
/**
* @var array The attributes that are mass assignable.
*/
protected $fillable = [
'notes_idbus',
'notes_iduser',
'notes_title',
'notes_content',
'notes_subject_id',
'notes_is_pinned',
'notes_color',
'notes_image',
'notes_file'
];
/**
* The "booted" method of the model.
*
* @return void
*/
protected static function booted()
{
static::creating(function ($note) {
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$note->notes_idbus = $_SESSION['user_idbus'] ?? null;
if (empty($note->notes_idbus)) {
$note->notes_idbus = 1;
}
$note->notes_iduser = $_SESSION['user_id'] ?? null;
});
}
/**
* Scope a query to only include notes for the current business and user.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeCurrentUser($query)
{
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$busId = $_SESSION['user_idbus'] ?? null;
if (empty($busId)) {
$busId = 1;
}
$userId = $_SESSION['user_id'] ?? null;
return $query->where('notes_idbus', $busId)
->where('notes_iduser', $userId);
}
/**
* Get the subject that owns the note.
*/
public function subject()
{
return $this->belongsTo(Subject::class, 'notes_subject_id', 'subjects_id');
}
}
+109
View File
@@ -0,0 +1,109 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2026 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\Notes\Models;
use Illuminate\Database\Eloquent\Model;
/**
* Class Subject
*
* Model for note subjects/categories.
*
* @package Modules\Notes\Models
*/
class Subject extends Model
{
/**
* @var string The table associated with the model.
*/
protected $table = 'notes_subjects';
/**
* @var string The primary key associated with the table.
*/
protected $primaryKey = 'subjects_id';
/**
* @var array The attributes that are mass assignable.
*/
protected $fillable = [
'subjects_idbus',
'subjects_iduser',
'subjects_name',
'subjects_color'
];
/**
* The "booted" method of the model.
*
* @return void
*/
protected static function booted()
{
static::creating(function ($subject) {
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if (empty($subject->subjects_idbus)) {
$subject->subjects_idbus = $_SESSION['user_idbus'] ?? null;
}
if (empty($subject->subjects_idbus)) {
$subject->subjects_idbus = 1;
}
if (empty($subject->subjects_iduser)) {
$subject->subjects_iduser = $_SESSION['user_id'] ?? null;
}
});
}
/**
* Scope a query to only include subjects for the current business and user.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeCurrentUser($query)
{
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$busId = $_SESSION['user_idbus'] ?? null;
if (empty($busId)) {
$busId = 1;
}
$userId = $_SESSION['user_id'] ?? null;
return $query->where('subjects_idbus', $busId)
->where('subjects_iduser', $userId);
}
/**
* Get the notes for the subject.
*/
public function notes()
{
return $this->hasMany(Note::class, 'notes_subject_id', 'subjects_id');
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
/**
* SysCMS - Content Management System
*
* Copyright (c) 2026 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\Notes\Providers;
use Kernel\Application;
/**
* Class NotesServiceProvider
*
* Service provider for the Notes module.
*
* @package Modules\Notes\Providers
*/
class NotesServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
// Registration logic if needed
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
$app = Application::getInstance();
// You can register global variables for Twig here if needed
// $app->getView()->addGlobal('notes_count', Note::currentUser()->count());
}
}