Compare commits

..
2 Commits
Author SHA1 Message Date
sld-admin a2f36b6fac Fixed the READMEs and small fix around the project 2026-08-19 17:36:34 +01:00
sld-admin 121f788c28 First Commit 2026-08-19 17:26:06 +01:00
28 changed files with 4763 additions and 213 deletions
+30
View File
@@ -0,0 +1,30 @@
# OS Artifacts
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Environment & IDE
.idea/
.vscode/
*.swp
*.swo
# Scratch & Temporary Folders
scratch/
*.tmp
*.log
# Dynamic Uploaded Favicons (Keep default favicon.png)
assets/img/favicon_*.png
assets/img/favicon_*.jpg
assets/img/favicon_*.jpeg
assets/img/favicon_*.webp
assets/img/favicon_*.ico
assets/img/favicon_*.svg
# Installer Completed Marker (Optional)
first_start.php.completed
+94
View File
@@ -0,0 +1,94 @@
# 📝 sld-blocknotes - Sistema Moderno de Notas en Bloques y Gestión de Comandos
> **🌐 Idiomas disponibles**: [🇬🇧 English](README.md) | [🇮🇹 Italiano](README.it.md) | [🇪🇸 Español](README.es.md) | [🇫🇷 Français](README.fr.md)
![PHP](https://img.shields.io/badge/PHP-7.4%20%7C%208.x-777BB4?style=for-the-badge&logo=php&logoColor=white)
![Database](https://img.shields.io/badge/Database-MySQL%20%2F%20MariaDB-4479A1?style=for-the-badge&logo=mysql&logoColor=white)
![Frontend](https://img.shields.io/badge/Frontend-Vanilla%20CSS%20%26%20JS-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black)
![Framework](https://img.shields.io/badge/Framework-Zero%20Dependencies-22c55e?style=for-the-badge)
Una potente y moderna aplicación web sin dependencias externas para organizar notas estructuradas en bloques, comandos de terminal, fragmentos de código e instrucciones. Desarrollada en **Pure PHP**, **Vanilla CSS**, **Vanilla JS** y **MySQL/MariaDB**.
URL del Repositorio: [https://gitea.sld-server.org/sld-admin/sld-blocknotes](https://gitea.sld-server.org/sld-admin/sld-blocknotes)
---
## ✨ Características Principales
- 📑 **Notas en Bloques y Subnotas Estructuradas**:
- Organiza tus notas dentro de blocknotes personalizados.
- Las notas contienen título, descripción y múltiples subnotas.
- **Notas Plegables**: Las notas están plegadas por defecto para optimizar el espacio vertical, con botones globales de **Expandir Todas / Contraer Todas**.
-**Subnotas de Tipo Comando de Terminal**:
- Renderizado diferenciado entre subnotas de texto genérico y comandos de terminal.
- **Botón Copiar en 1 Clic**: Copia instantáneamente comandos al portapapeles con retroalimentación visual (`✓ ¡Copiado!`).
- Cuadro estilo IDE/Terminal de alto contraste con barra de desplazamiento vertical.
- 🔍 **Búsqueda en Tiempo Real**:
- Filtra títulos de notas, descripciones, subnotas y contenido de comandos en tiempo real dentro del blocknote activo.
- Expande automáticamente los bloques coincidentes durante la búsqueda.
- 📥 / 💾 **Importación y Exportación JSON**:
- Descarga el archivo JSON de cualquier blocknote con un clic (`export_blocknote.php`).
- Importador JSON interactivo compatible con formato estándar y formato antiguo.
- 🌐 **Interfaz Multilingüe (IT, EN, ES, FR)**:
- Localización completa en **Italiano 🇮🇹, Inglés 🇬🇧, Español 🇪🇸 y Francés 🇫🇷**.
- Selector de idioma rápido en la barra de navegación para visitantes y usuarios conectados.
- El idioma preferido de cada usuario se guarda en su perfil para futuros accesos.
- 🏠 **Página de Inicio Personalizable**:
- Elige entre acceder directamente al **Panel Principal (Todos los Blocknotes)** o abrir un **Blocknote Predeterminado específico** al iniciar sesión.
- 👑 **Rol Único de Propietario y Jerarquía Avanzada (`owner` > `admin` > `user`)**:
- **Cuenta de Propietario Inmune**: La cuenta inicial de Propietario (`owner`) no puede ser eliminada por ningún administrador ni por sí misma.
- Los administradores estándar no pueden degradar ni modificar el rol del Propietario.
- ⚙️ **Personalización del Sitio (Exclusivo Propietario)**:
- Cambia el **Título Global de la Aplicación**.
- Sube y valida un **Favicon de Alta Resolución** (mínimo 64x64px) con vista previa en tiempo real.
- 🚀 **Asistente de Configuración Inicial (`first_start.php`)**:
- Instalador automático para configurar credenciales MySQL, crear tablas y registrar los primeros usuarios privilegiados (`owner` y `admin`).
- Completamente multilingüe en los 4 idiomas.
---
## 🚀 Guía de Instalación
### Requisitos
- Servidor Web (Apache, Nginx, o servidor CLI integrado de PHP)
- PHP 7.4 o 8.x (con extensión `mysqli` habilitada)
- MySQL 5.7+ o MariaDB 10.x+
### Instalación Paso a Paso
1. **Clona el Repositorio**:
```bash
git clone https://gitea.sld-server.org/sld-admin/sld-blocknotes.git
cd sld-blocknotes
```
2. **Inicia el Servidor Web**:
Puedes utilizar el servidor integrado de PHP:
```bash
php -S localhost:8000
```
3. **Ejecuta el Asistente de Configuración**:
Abre tu navegador en `http://localhost:8000/`.
El sistema detectará el primer inicio y abrirá automáticamente `first_start.php`:
- Introduce los datos de tu **Base de Datos MySQL** (Host, Nombre, Usuario, Contraseña).
- Configura las credenciales de la **Cuenta de Propietario**.
- Configura las credenciales de la **Primera Cuenta de Admin**.
- Haz clic en **⚡ Completar Instalación y Configurar Sistema**.
4. **Inicia Sesión**:
Al finalizar, serás redirigido a la página de inicio de sesión `index.php?page=login`.
---
## 📄 Licencia
Este proyecto se distribuye bajo la licencia open-source [MIT License](LICENSE).
+94
View File
@@ -0,0 +1,94 @@
# 📝 sld-blocknotes - Système Moderne de Notes par Blocs et Gestion de Commandes
> **🌐 Langues disponibles**: [🇬🇧 English](README.md) | [🇮🇹 Italiano](README.it.md) | [🇪🇸 Español](README.es.md) | [🇫🇷 Français](README.fr.md)
![PHP](https://img.shields.io/badge/PHP-7.4%20%7C%208.x-777BB4?style=for-the-badge&logo=php&logoColor=white)
![Database](https://img.shields.io/badge/Database-MySQL%20%2F%20MariaDB-4479A1?style=for-the-badge&logo=mysql&logoColor=white)
![Frontend](https://img.shields.io/badge/Frontend-Vanilla%20CSS%20%26%20JS-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black)
![Framework](https://img.shields.io/badge/Framework-Zero%20Dependencies-22c55e?style=for-the-badge)
Une application Web moderne et puissante, sans dépendance externe, pour organiser des notes structurées en blocs, des commandes de terminal, des extraits de code et des instructions. Développée en **Pure PHP**, **Vanilla CSS**, **Vanilla JS** et **MySQL/MariaDB**.
URL du Dépôt: [https://gitea.sld-server.org/sld-admin/sld-blocknotes](https://gitea.sld-server.org/sld-admin/sld-blocknotes)
---
## ✨ Fonctionnalités Principales
- 📑 **Notes en Blocs et Sous-notes Structurées**:
- Organisez vos notes au sein de blocknotes personnalisés.
- Les notes contiennent un titre, une description et plusieurs sous-notes.
- **Notes Repliables**: Les notes sont réduites par défaut pour optimiser l'espace vertical, avec des boutons globaux **Tout Développer / Tout Réduire**.
-**Sous-notes de Type Commande de Terminal**:
- Rendu différencié entre sous-notes de texte générique et commandes de terminal.
- **Bouton Copier en 1 Clic**: Copiez instantanément les commandes dans le presse-papiers avec retour visuel (`✓ Copié!`).
- Cadre style IDE/Terminal à haut contraste avec barre de défilement verticale.
- 🔍 **Recherche en Temps Réel**:
- Filtrez les titres des notes, descriptions, sous-notes et contenus des commandes en temps réel au sein du blocknote ouvert.
- Développe automatiquement les blocs correspondants pendant la recherche.
- 📥 / 💾 **Importation et Exportation JSON**:
- Téléchargez le fichier JSON de chaque blocknote en un clic (`export_blocknote.php`).
- Importateur JSON interactif compatible avec le format standard et l'ancien format tableau.
- 🌐 **Interface Multilingue (IT, EN, ES, FR)**:
- Localisation complète en **Italien 🇮🇹, Anglais 🇬🇧, Espagnol 🇪🇸 et Français 🇫🇷**.
- Sélecteur de langue rapide dans la barre de navigation pour les visiteurs et utilisateurs connectés.
- La langue préférée de chaque utilisateur est enregistrée dans son profil pour les futures connexions.
- 🏠 **Page d'Accueil Personnalisable**:
- Choisissez entre accéder directement au **Panneau Principal (Tous les Blocknotes)** ou ouvrir un **Blocknote par Défaut spécifique** lors de la connexion.
- 👑 **Rôle Unique de Propriétaire et Hiérarchie Avancée (`owner` > `admin` > `user`)**:
- **Compte Propriétaire Protégé**: Le compte Propriétaire initial (`owner`) ne peut être supprimé ni par un administrateur ni par lui-même.
- Les administrateurs standard ne peuvent ni rétrograder ni modifier le rôle du Propriétaire.
- ⚙️ **Personnalisation du Site (Exclusif Propriétaire)**:
- Modifiez le **Titre Global de l'Application**.
- Téléversez et validez un **Favicon Haute Résolution** (minimum 64x64px) avec aperçu en temps réel.
- 🚀 **Assistant de Configuration Initiale (`first_start.php`)**:
- Installateur automatique pour configurer les identifiants MySQL, créer les tables et enregistrer les premiers utilisateurs privilégiés (`owner` et `admin`).
- Entièrement multilingue dans les 4 langues.
---
## 🚀 Guide d'Installation
### Prérequis
- Serveur Web (Apache, Nginx, ou serveur CLI intégré de PHP)
- PHP 7.4 ou 8.x (avec extension `mysqli` activée)
- MySQL 5.7+ or MariaDB 10.x+
### Installation Étape par Étape
1. **Cloner le Dépôt**:
```bash
git clone https://gitea.sld-server.org/sld-admin/sld-blocknotes.git
cd sld-blocknotes
```
2. **Démarrer le Serveur Web**:
Vous pouvez utiliser le serveur CLI intégré de PHP:
```bash
php -S localhost:8000
```
3. **Exécuter l'Assistant de Configuration**:
Ouvrez votre navigateur sur `http://localhost:8000/`.
Le système détectera le premier démarrage et ouvrira automatiquement `first_start.php`:
- Entrez les informations de votre **Base de Données MySQL** (Hôte, Nom, Utilisateur, Mot de passe).
- Configurez les identifiants du **Compte Propriétaire**.
- Configurez les identifiants du **Premier Compte Admin**.
- Cliquez sur **⚡ Terminer l'Installation et Configurer le Système**.
4. **Se Connecter**:
Une fois terminé, vous serez redirigé vers la page de connexion `index.php?page=login`.
---
## 📄 Licence
Ce projet est distribué sous licence open-source [MIT License](LICENSE).
+144
View File
@@ -0,0 +1,144 @@
# 📝 sld-blocknotes - Sistema Moderno di Note a Blocchi & Gestione Comandi
> **🌐 Lingue disponibili**: [🇬🇧 English](README.md) | [🇮🇹 Italiano](README.it.md) | [🇪🇸 Español](README.es.md) | [🇫🇷 Français](README.fr.md)
![PHP](https://img.shields.io/badge/PHP-7.4%20%7C%208.x-777BB4?style=for-the-badge&logo=php&logoColor=white)
![Database](https://img.shields.io/badge/Database-MySQL%20%2F%20MariaDB-4479A1?style=for-the-badge&logo=mysql&logoColor=white)
![Frontend](https://img.shields.io/badge/Frontend-Vanilla%20CSS%20%26%20JS-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black)
![Framework](https://img.shields.io/badge/Framework-Zero%20Dependencies-22c55e?style=for-the-badge)
Una potente applicazione Web moderna senza dipendenze esterne per organizzare note strutturate a blocchi, comandi di terminale, frammenti di codice ed istruzioni. Realizzata in **Pure PHP**, **Vanilla CSS**, **Vanilla JS** e **MySQL/MariaDB**.
URL Repository: [https://gitea.sld-server.org/sld-admin/sld-blocknotes](https://gitea.sld-server.org/sld-admin/sld-blocknotes)
---
## ✨ Funzionalità Principali
- 📑 **Note a Blocchi & Sottonote Strutturate**:
- Organizza le tue note all'interno di blocknotes personalizzati.
- Le note contengono titolo, descrizione e sottonote multiple.
- **Note Richiudibili**: Le note sono ridotte di default per ottimizzare lo spazio verticale, con pulsanti globali **Espandi Tutte / Riduci Tutte**.
-**Sottonote di Tipo Comando Terminale**:
- Rendering differenziato tra sottonote di testo generico e comandi di terminale.
- **Pulsante Copia con 1-Click**: Copia istantaneamente i comandi negli appunti con feedback visivo (`✓ Copiato!`).
- Riquadro stile IDE/Terminale ad alto contrasto con barra di scorrimento verticale ad altezza limitata.
- 🔍 **Ricerca in Tempo Reale**:
- Filtra titoli delle note, descrizioni, sottonote e contenuti dei comandi in tempo reale all'interno del blocknote aperto.
- Espande automaticamente i blocchi corrisposti durante la ricerca.
- 📥 / 💾 **Importazione ed Esportazione JSON**:
- Scarica il file JSON di ogni blocknote con un click (`export_blocknote.php`).
- Importatore JSON interattivo con supporto per il formato standard e vecchio formato array.
- 🌐 **Interfaccia Multilingua (IT, EN, ES, FR)**:
- Localizzazione completa in **Italiano 🇮🇹, English 🇬🇧, Español 🇪🇸 e Français 🇫🇷**.
- Selector di lingua rapido nella navbar per ospiti ed utenti connessi.
- La lingua preferita di ciascun utente viene salvata nel profilo DB per i futuri accessi.
- 🏠 **Pagina di Primo Accesso Personalizzabile**:
- Scegli se accedere direttamente alla **Pagina Principale (Tutti i Blocknotes)** oppure aprire un **Blocknote Predefinito specifico** al login.
- 👑 **Ruolo Unico Owner & Gerarchia Avanzata (`owner` > `admin` > `user`)**:
- **Account Owner Ineliminabile**: L'account Owner iniziale (`owner`) non può essere eliminato da nessun admin né da se stesso.
- Gli amministratori standard non possono declassare né modificare il ruolo dell'Owner.
- ⚙️ **Personalizzazione Sito (Riservato Owner)**:
- Modifica il **Titolo Globale dell'Applicazione**.
- Carica e valida la **Favicon ad Alta Risoluzione** (minimo 64x64px) con anteprima in tempo reale.
- 🚀 **Wizard di Setup Iniziale (`first_start.php`)**:
- Installatore automatico per configurare i dati di connessione MySQL, creare le tabelle ed impostare i primi utenti privilegiati (`owner` ed `admin`).
- Completamente multilingua nelle 4 lingue.
---
## 🚀 Guida all'Installazione
### Requisiti
- Server Web (Apache, Nginx, o PHP CLI server integrato)
- PHP 7.4 o 8.x (con estensione `mysqli` abilitata)
- MySQL 5.7+ o MariaDB 10.x+
### Installazione Passo-Passo
1. **Clona il Repository**:
```bash
git clone https://gitea.sld-server.org/sld-admin/sld-blocknotes.git
cd sld-blocknotes
```
2. **Avvia il Server Web**:
Puoi utilizzare il server integrato di PHP:
```bash
php -S localhost:8000
```
3. **Esegui il Wizard di Setup**:
Apri il browser su `http://localhost:8000/`.
Il sistema rileverà il primo avvio ed aprirà automaticamente `first_start.php`:
- Inserisci i dati del tuo **Database MySQL** (Host, Nome Database, Utente, Password).
- Configura le credenziali dell'**Account Owner**.
- Configura le credenziali del **Primo Account Admin**.
- Clicca su **⚡ Completa Installazione & Configura Sistema**.
4. **Accedi ed Inizia**:
Al termine del setup verrai reindirizzato alla pagina di login `index.php?page=login`.
---
## 📁 Struttura del Progetto
```text
sld-blocknotes/
├── assets/
│ ├── css/
│ │ └── style.css # Design system Vanilla CSS e variabili HSL
│ └── js/
│ └── main.js # Vanilla JS per modali, note richiudibili, cerca e copia
├── blocknotes/ # Cartella di salvataggio JSON dei blocknotes
│ └── .gitkeep
├── config/
│ ├── constants.php # Definizione costanti di percorso
│ ├── db.php # Connessione MySQL, auto-migrazione e redirect installer
│ ├── functions_init.php # Router e sanificazione pagine
│ ├── init.php # Inizializzazione sistema
│ └── settings.php # Template configurazione credenziali DB
├── functions/
│ ├── auth.php # Autenticazione, permessi e protezioni Owner
│ ├── blocknotes.php # Engine CRUD JSON blocknotes ed importatore
│ ├── functions.php # Autoloader moduli funzioni
│ ├── lang.php # Dizionario di traduzione multilingua (IT, EN, ES, FR)
│ └── site_settings.php # Gestione titolo sito e favicon ad alta risoluzione
├── includes/
│ ├── head_links.php # Inclusioni CSS ed head globali
│ └── js_footer.php # Inclusioni script footer globali
├── pages/
│ ├── 404.php # Pagina non trovata
│ ├── admin_users.php # Gestione utenti admin ed impostazioni sito Owner
│ ├── db-error.php # Template errore database
│ ├── export_blocknote.php # Endpoint download JSON blocknote
│ ├── home.php # Dashboard principale dei blocknotes
│ ├── login.php # Pagina di login multilingua
│ ├── logout.php # Disconnessione sessione
│ ├── profile.php # Profilo utente, password, colori tema, lingua e landing page
│ └── view_blocknote.php # Vista dettaglio blocknote, note richiudibili e cerca
├── templates/
│ ├── footer.php # Template footer pagina
│ └── header.php # Header navbar, logo, badge utenti e selector lingua
├── .gitignore
├── first_start.php # Wizard di installazione guidata primo avvio
├── index.php # Entry point applicazione e router
├── README.md # Documentazione Principale in Inglese
├── README.it.md # Documentazione in Italiano
├── README.es.md # Documentazione in Spagnolo
└── README.fr.md # Documentazione in Francese
```
---
## 📄 Licenza
Questo progetto è rilasciato sotto licenza open-source [MIT License](LICENSE).
+121 -140
View File
@@ -1,163 +1,144 @@
# SLD PHP Template
# 📝 sld-blocknotes - Modern Block-based Notes & Command Management System
A lightweight and modular PHP starter framework for building plain PHP websites without heavy dependencies.
> **🌐 Languages**: [🇬🇧 English](README.md) | [🇮🇹 Italiano](README.it.md) | [🇪🇸 Español](README.es.md) | [🇫🇷 Français](README.fr.md)
This project is designed to provide a clean structure, reusable components, and a simple initialization system to speed up development while keeping full control over the codebase.
![PHP](https://img.shields.io/badge/PHP-7.4%20%7C%208.x-777BB4?style=for-the-badge&logo=php&logoColor=white)
![Database](https://img.shields.io/badge/Database-MySQL%20%2F%20MariaDB-4479A1?style=for-the-badge&logo=mysql&logoColor=white)
![Frontend](https://img.shields.io/badge/Frontend-Vanilla%20CSS%20%26%20JS-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black)
![Framework](https://img.shields.io/badge/Framework-Zero%20Dependencies-22c55e?style=for-the-badge)
A powerful, modern, zero-dependency Web application for organizing structured block notes, technical commands, code snippets, and instructions. Built with **Pure PHP**, **Vanilla CSS**, **Vanilla JS**, and **MySQL/MariaDB**.
Repository URL: [https://gitea.sld-server.org/sld-admin/sld-blocknotes](https://gitea.sld-server.org/sld-admin/sld-blocknotes)
---
## 🚀 Features
## ✨ Key Features
- ⚡ Lightweight and fast (no external frameworks required)
- 📁 Clean and scalable folder structure
- 🔧 Centralized configuration system
- 🧩 Modular templates (header, footer, includes)
- 🎨 Basic frontend setup (CSS variables, animations, JS helpers)
- 🛠 Ready-to-use initialization system
- ❌ Built-in error pages (environment & database)
- 📑 **Structured Block Notes & Subnotes**:
- Organize notes into customizable blocknotes.
- Notes contain titles, descriptions, and multiple subnotes.
- **Collapsible Notes**: Collapsed by default to maximize vertical screen space, with global **Expand All / Collapse All** buttons.
- **Terminal Command Subnotes**:
- Separate rendering for generic text subnotes vs terminal command subnotes.
- **1-Click Clipboard Copy**: Copy command snippets instantly with visual feedback (`✓ Copied!`).
- High-contrast dark IDE code box with max-height vertical scrolling.
- 🔍 **Real-Time Live Search**:
- Filter note titles, descriptions, subnote titles, and terminal command contents instantly inside the active blocknote view.
- Automatically expands matched note blocks during search.
- 📥 / 💾 **JSON Import & Export**:
- Download blocknote JSON files with one click (`export_blocknote.php`).
- Interactive JSON importer supporting both standard and legacy JSON formats.
- 🌐 **Multilingual Interface (IT, EN, ES, FR)**:
- Full interface localization in **Italian 🇮🇹, English 🇬🇧, Spanish 🇪🇸, and French 🇫🇷**.
- Quick navbar language selector for guests and logged-in users.
- Each user's language choice is saved to their database profile for future logins.
- 🏠 **Customizable User Landing Page**:
- Choose between launching directly into the **Main Dashboard (All Blocknotes)** or opening a **Specific Default Blocknote** upon login.
- 👑 **Single Owner Role & Advanced Hierarchy (`owner` > `admin` > `user`)**:
- **Immune Owner Account**: The initial Owner account (`owner`) cannot be deleted by any admin or by themselves.
- Owners cannot be demoted or altered by standard administrators.
- ⚙️ **Site Customization (Owner Exclusive)**:
- Change the **Global Application / Site Title**.
- Upload & validate a **High-Resolution Favicon** (min 64x64px) with real-time preview.
- 🚀 **First Start Setup Installer Wizard (`first_start.php`)**:
- Automated first-time setup wizard for configuring MySQL database credentials, creating database tables, and setting up the initial `owner` and `admin` accounts.
- Fully localized in all 4 languages.
---
## 📂 Project Structure
## 🚀 Quickstart & Installation
```
.
├── admin/
### Requirements
- Web Server (Apache, Nginx, or PHP built-in CLI server)
- PHP 7.4 or 8.x (with `mysqli` extension enabled)
- MySQL 5.7+ or MariaDB 10.x+
### Step-by-Step Installation
1. **Clone the Repository**:
```bash
git clone https://gitea.sld-server.org/sld-admin/sld-blocknotes.git
cd sld-blocknotes
```
2. **Start Web Server**:
You can serve the directory with your local Web server or use PHP's built-in server:
```bash
php -S localhost:8000
```
3. **Run Automated Setup Wizard**:
Navigate to `http://localhost:8000/` in your browser.
The system will automatically detect first-time run and launch `first_start.php`:
- Enter your **MySQL Database Credentials** (Host, Database Name, User, Password).
- Configure credentials for the **Owner Account**.
- Configure credentials for the **First Admin Account**.
- Click **⚡ Complete Installation & Configure System**.
4. **Login & Enjoy**:
After setup completes, you will be redirected to `index.php?page=login` to sign in.
---
## 📁 Directory Structure
```text
sld-blocknotes/
├── assets/
│ ├── css/
── img/
── style.css # Vanilla CSS design system & HSL variables
│ └── js/
│ └── main.js # Vanilla JS modals, collapsible notes, search & copy
├── blocknotes/ # Blocknotes JSON storage directory
│ └── .gitkeep
├── config/
│ ├── constants.php # Path constants definition
│ ├── db.php # MySQL connection, auto-migration & installer redirect
│ ├── functions_init.php # Router & page sanitization
│ ├── init.php # System initialization
│ └── settings.php # Database credentials configuration template
├── functions/
│ ├── auth.php # Authentication, role permissions & owner protections
│ ├── blocknotes.php # Blocknote JSON CRUD engine & JSON importer
│ ├── functions.php # Function module autoloader
│ ├── lang.php # Multilingual translation dictionary (IT, EN, ES, FR)
│ └── site_settings.php # Site title & high-resolution favicon manager
├── includes/
│ ├── head_links.php # Global CSS & favicon head includes
│ └── js_footer.php # Global footer script includes
├── pages/
│ ├── 404.php # Not found page
│ ├── admin_users.php # Admin user management & Owner site settings
│ ├── db-error.php # Database error template
│ ├── export_blocknote.php # Blocknote JSON download endpoint
│ ├── home.php # Main blocknotes dashboard
│ ├── login.php # Multilingual login page
│ ├── logout.php # Session logout
│ ├── profile.php # User profile, password, theme colors, language & landing page
│ └── view_blocknote.php # Detail view for blocknotes, collapsible notes & search
├── templates/
├── themes/
├── public/
├── index.php
── README.md
│ ├── footer.php # Page footer template
│ └── header.php # Navbar header, logo, user badges & quick language selector
├── .gitignore
── first_start.php # Automated First Start Installation Wizard
├── index.php # Application entry point & router
├── README.md # Main English Documentation
├── README.it.md # Italian Documentation
├── README.es.md # Spanish Documentation
└── README.fr.md # French Documentation
```
---
## ⚙️ Configuration
## 📄 License
All core configuration is located inside the `config/` directory:
- `init.php` → Application bootstrap
- `constants.php` → Global constants
- `settings.php` → App settings
- `db.php` → Database connection
- `functions_init.php` → Function loader
---
## 🧠 How It Works
1. The application starts from `index.php`
2. Core initialization is handled by the config system
3. Templates are loaded dynamically (`header.php`, `footer.php`)
4. Pages are rendered from the `pages/` directory
5. Custom logic can be added via the `functions/` folder
---
## 🎨 Frontend
The project includes a minimal frontend setup:
### CSS System
- Variables-based color system with multiple shades
- Basic layout styles
- Animation utilities
### JavaScript
- Simple helper functions like dynamic year rendering
---
## 🛠 Usage
1. Clone the repository:
```bash
git clone https://github.com/your-username/sld-php-template.git
```
2. Move it into your web server directory
3. Configure:
- Database (`config/db.php`)
- Environment settings (`config/settings.php`)
4. Open in browser:
```
http://localhost/your-project
```
---
## 📄 Pages
- `home.php` → Main homepage
- `db-error.php` → Database error page
- `env-error.php` → Environment error page
---
## 🧩 Templates
Reusable layout components:
- `templates/header.php`
- `templates/footer.php`
---
## 🧪 Status
🚧 This project is currently in development.
New features and improvements will be added progressively.
---
## 📌 Roadmap (Planned)
- [ ] Routing system
- [ ] Theme engine
- [ ] Admin panel structure
- [ ] CLI tools
- [ ] Environment (.env) support
- [ ] Basic authentication system
---
## 🤝 Contributing
Contributions, ideas, and feedback are welcome.
Feel free to open issues or pull requests.
---
## 📜 License
This project is licensed under the MIT License.
See the `LICENSE` file for details.
---
## 👤 Author
Created by Simone (SLD)
---
## 💡 Philosophy
This project aims to stay:
- Simple
- Transparent
- Framework-free
- Easy to extend
No magic, just clean PHP.
This project is open-source software available under the [MIT License](LICENSE).
+751 -9
View File
@@ -1,18 +1,760 @@
html { scroll-behavior: smooth; }
*{ box-sizing: border-box; margin:0; padding:0;}
/* ==========================================================================
SYSTEM BLOCKNOTES - MAIN CSS (VANILLA CSS WITH HSL THEME VARIABLES)
========================================================================== */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Fira+Code:wght@400;500&display=swap');
.main-nav{
display:flex;
width:100%;
gap: 1rem;
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #0b0f19;
color: #f1f5f9;
min-height: 100vh;
display: flex;
flex-direction: column;
line-height: 1.6;
}
a {
color: var(--secCol14, #38bdf8);
text-decoration: none;
transition: all 0.2s ease;
}
a:hover {
color: var(--secCol18, #bae6fd);
}
/* --- HEADER & NAVIGATION --- */
.navbar-header {
background: rgba(15, 23, 42, 0.85);
backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.nav-container {
max-width: 1240px;
margin: 0 auto;
padding: 0.85rem 1.5rem;
display: flex;
align-items: center;
justify-content: flex-start;
list-style-type: none;
padding:1rem;
justify-content: space-between;
}
.brand-logo {
display: flex;
align-items: center;
gap: 0.6rem;
font-weight: 700;
font-size: 1.35rem;
color: #ffffff;
letter-spacing: -0.02em;
}
.brand-logo .logo-icon {
font-size: 1.6rem;
}
.main-nav {
display: flex;
align-items: center;
gap: 0.75rem;
list-style: none;
}
.main-nav a {
color: #94a3b8;
font-weight: 500;
font-size: 0.95rem;
padding: 0.5rem 0.9rem;
border-radius: 8px;
transition: all 0.2s ease;
}
.main-nav a:hover,
.main-nav a.active {
color: #ffffff;
background: rgba(255, 255, 255, 0.08);
}
.admin-link {
color: var(--thiCol15, #c084fc) !important;
background: rgba(192, 132, 252, 0.1) !important;
border: 1px solid rgba(192, 132, 252, 0.25);
}
.admin-link:hover,
.admin-link.active {
background: rgba(192, 132, 252, 0.25) !important;
color: #ffffff !important;
}
.user-badge {
display: inline-flex;
align-items: center;
font-size: 0.82rem;
font-weight: 600;
padding: 0.35rem 0.75rem;
border-radius: 20px;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.1);
color: #e2e8f0;
}
.badge-admin {
background: rgba(234, 179, 8, 0.15);
border-color: rgba(234, 179, 8, 0.3);
color: #fde047;
}
.nav-logout-btn {
color: #ef4444 !important;
background: rgba(239, 68, 68, 0.08) !important;
}
.nav-logout-btn:hover {
background: rgba(239, 68, 68, 0.2) !important;
color: #fca5a5 !important;
}
/* --- MAIN WRAPPER & FOOTER --- */
.main-wrapper {
max-width: 1240px;
width: 100%;
margin: 0 auto;
padding: 2rem 1.5rem;
flex: 1;
}
.main-footer {
border-top: 1px solid rgba(255, 255, 255, 0.08);
background: #090d16;
padding: 1.5rem 0;
margin-top: auto;
color: #64748b;
font-size: 0.85rem;
text-align: center;
}
.footer-container {
max-width: 1240px;
margin: 0 auto;
padding: 0 1.5rem;
display: flex;
flex-direction: column;
gap: 0.3rem;
}
/* --- PAGE HEADERS & CARDS --- */
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 2rem;
flex-wrap: wrap;
gap: 1rem;
}
.page-title-group h1 {
font-size: 1.85rem;
font-weight: 700;
color: #ffffff;
margin-bottom: 0.3rem;
}
.page-title-group p {
color: #94a3b8;
font-size: 0.95rem;
}
.card {
background: #131b2e;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 14px;
padding: 1.5rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
/* --- BUTTONS --- */
.btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.65rem 1.2rem;
border-radius: 8px;
font-size: 0.9rem;
font-weight: 600;
border: none;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
}
.btn-sm {
padding: 0.4rem 0.75rem;
font-size: 0.82rem;
border-radius: 6px;
}
.btn-primary {
background: var(--priCol10, #22c55e);
color: #042f13;
}
.btn-primary:hover {
background: var(--priCol12, #4ade80);
color: #042f13;
box-shadow: 0 0 15px rgba(34, 197, 94, 0.4);
}
.btn-secondary {
background: var(--secCol8, #0284c7);
color: #ffffff;
}
.btn-secondary:hover {
background: var(--secCol10, #0ea5e9);
color: #ffffff;
box-shadow: 0 0 15px rgba(14, 165, 233, 0.4);
}
.btn-dark {
background: rgba(255, 255, 255, 0.08);
color: #e2e8f0;
border: 1px solid rgba(255, 255, 255, 0.12);
}
.btn-dark:hover {
background: rgba(255, 255, 255, 0.15);
color: #ffffff;
}
.btn-danger {
background: rgba(239, 68, 68, 0.15);
color: #f87171;
border: 1px solid rgba(239, 68, 68, 0.3);
}
.btn-danger:hover {
background: #ef4444;
color: #ffffff;
}
/* --- BLOCKNOTE GRID & CARDS --- */
.blocknotes-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1.5rem;
}
.blocknote-card {
background: #131b2e;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 14px;
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1rem;
transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
position: relative;
}
.blocknote-card:hover {
transform: translateY(-4px);
border-color: rgba(255, 255, 255, 0.2);
box-shadow: 0 12px 35px rgba(0, 0, 0, 0.35);
}
.blocknote-card-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 0.75rem;
}
.blocknote-title {
font-size: 1.25rem;
font-weight: 700;
color: #ffffff;
line-height: 1.3;
}
.blocknote-owner-tag {
font-size: 0.75rem;
font-weight: 600;
padding: 0.25rem 0.6rem;
border-radius: 20px;
background: rgba(14, 165, 233, 0.15);
color: #7dd3fc;
border: 1px solid rgba(14, 165, 233, 0.3);
white-space: nowrap;
}
.blocknote-card-body {
font-size: 0.9rem;
color: #94a3b8;
flex: 1;
}
.blocknote-meta {
display: flex;
align-items: center;
gap: 1rem;
font-size: 0.8rem;
color: #64748b;
}
.blocknote-card-actions {
display: flex;
align-items: center;
gap: 0.5rem;
padding-top: 1rem;
border-top: 1px solid rgba(255, 255, 255, 0.06);
}
/* --- VIEW BLOCKNOTE DETAILED NOTE BLOCKS --- */
.notes-container {
display: flex;
flex-direction: column;
gap: 2rem;
}
.note-block {
background: #131b2e;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
overflow: hidden;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.25);
transition: all 0.25s ease;
}
.note-header {
background: rgba(255, 255, 255, 0.03);
padding: 1.1rem 1.5rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
cursor: pointer;
user-select: none;
transition: background 0.2s ease;
}
.note-header:hover {
background: rgba(255, 255, 255, 0.06);
}
.note-title-wrapper {
display: flex;
align-items: center;
gap: 0.75rem;
}
.collapse-icon {
font-size: 0.85rem;
color: var(--secCol14, #38bdf8);
transition: transform 0.25s ease;
display: inline-block;
}
.note-block.collapsed .collapse-icon {
transform: rotate(0deg);
}
.note-block:not(.collapsed) .collapse-icon {
transform: rotate(90deg);
}
.subnote-count-badge {
font-size: 0.78rem;
font-weight: 600;
padding: 0.2rem 0.6rem;
border-radius: 12px;
background: rgba(255, 255, 255, 0.07);
color: #94a3b8;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.note-block.collapsed .note-header {
border-bottom: none;
}
.note-block.collapsed .note-body {
display: none;
}
.note-description-box {
padding: 1rem 1.5rem 0.5rem 1.5rem;
font-size: 0.92rem;
color: #94a3b8;
border-bottom: 1px dashed rgba(255, 255, 255, 0.08);
}
.note-title-area h3 {
font-size: 1.25rem;
font-weight: 700;
color: #ffffff;
margin-bottom: 0;
}
.note-actions {
display: flex;
align-items: center;
gap: 0.5rem;
flex-shrink: 0;
}
.subnotes-list {
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.subnote-card {
background: #1a243a;
border: 1px solid rgba(255, 255, 255, 0.07);
border-radius: 12px;
padding: 1.25rem;
position: relative;
}
.subnote-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.85rem;
}
.subnote-title-group {
display: flex;
align-items: center;
gap: 0.75rem;
}
.subnote-title {
font-size: 1.05rem;
font-weight: 600;
color: #f1f5f9;
}
.type-badge {
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 0.2rem 0.5rem;
border-radius: 4px;
}
.type-badge.text {
background: rgba(148, 163, 184, 0.15);
color: #cbd5e1;
}
.type-badge.command {
background: rgba(234, 179, 8, 0.15);
color: #fde047;
border: 1px solid rgba(234, 179, 8, 0.3);
}
.subnote-content-text {
font-size: 0.95rem;
color: #cbd5e1;
white-space: pre-wrap;
background: rgba(0, 0, 0, 0.2);
padding: 0.85rem 1rem;
border-radius: 8px;
border-left: 3px solid var(--secCol10, #0ea5e9);
}
/* --- COMMAND BLOCK WITH COPY BUTTON --- */
.command-block-wrapper {
position: relative;
background: #050811;
border: 1px solid rgba(234, 179, 8, 0.35);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5), inset 0 0 10px rgba(234, 179, 8, 0.03);
border-radius: 10px;
overflow: hidden;
margin-top: 0.75rem;
}
.command-block-header {
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(234, 179, 8, 0.12);
padding: 0.45rem 0.9rem;
border-bottom: 1px solid rgba(234, 179, 8, 0.2);
font-size: 0.8rem;
font-family: 'Fira Code', 'Monaco', 'Consolas', monospace;
color: #fde047;
font-weight: 600;
}
.command-badge-title {
display: inline-flex;
align-items: center;
gap: 0.4rem;
font-size: 0.82rem;
letter-spacing: 0.3px;
}
.copy-btn {
background: rgba(255, 255, 255, 0.1);
color: #ffffff;
border: 1px solid rgba(255, 255, 255, 0.25);
padding: 0.3rem 0.75rem;
border-radius: 6px;
font-size: 0.78rem;
font-weight: 600;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 0.35rem;
transition: all 0.2s ease;
}
.copy-btn:hover {
background: #eab308;
color: #000000;
border-color: #eab308;
box-shadow: 0 0 10px rgba(234, 179, 8, 0.4);
}
.copy-btn.copied {
background: #22c55e !important;
color: #042f13 !important;
border-color: #22c55e !important;
}
.command-content-code {
font-family: 'Fira Code', 'Cascadia Code', 'Monaco', 'Consolas', monospace;
font-size: 0.88rem;
line-height: 1.5;
color: #4ade80;
background: #03060d;
padding: 0.85rem 1rem;
overflow-x: auto;
overflow-y: auto;
max-height: 320px;
white-space: pre;
margin: 0;
border-top: 1px solid rgba(0, 0, 0, 0.6);
}
.command-content-code code {
font-family: inherit;
color: inherit;
}
/* --- FORMS & INPUTS --- */
.form-group {
margin-bottom: 1.25rem;
}
.form-group label {
display: block;
font-size: 0.88rem;
font-weight: 600;
color: #cbd5e1;
margin-bottom: 0.4rem;
}
.form-control {
width: 100%;
padding: 0.75rem 1rem;
border-radius: 8px;
background: #090d16;
border: 1px solid rgba(255, 255, 255, 0.12);
color: #ffffff;
font-size: 0.95rem;
font-family: inherit;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.form-control:focus {
outline: none;
border-color: var(--secCol10, #0ea5e9);
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.2);
}
textarea.form-control {
resize: vertical;
min-height: 100px;
}
select.form-control {
cursor: pointer;
}
/* --- MODAL --- */
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.75);
backdrop-filter: blur(6px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.25s ease;
}
.modal-backdrop.active {
opacity: 1;
visibility: visible;
}
.modal-card {
background: #131b2e;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 16px;
width: 100%;
max-width: 540px;
padding: 1.75rem;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
transform: translateY(20px);
transition: transform 0.25s ease;
}
.modal-backdrop.active .modal-card {
transform: translateY(0);
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1.25rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.modal-header h3 {
font-size: 1.25rem;
font-weight: 700;
color: #ffffff;
}
.modal-close-btn {
background: transparent;
border: none;
color: #94a3b8;
font-size: 1.5rem;
cursor: pointer;
line-height: 1;
}
.modal-close-btn:hover {
color: #ffffff;
}
/* --- ALERTS & NOTIFICATIONS --- */
.alert {
padding: 0.9rem 1.2rem;
border-radius: 10px;
margin-bottom: 1.5rem;
font-size: 0.92rem;
font-weight: 500;
display: flex;
align-items: center;
gap: 0.6rem;
}
.alert-success {
background: rgba(34, 197, 94, 0.15);
border: 1px solid rgba(34, 197, 94, 0.3);
color: #4ade80;
}
.alert-danger {
background: rgba(239, 68, 68, 0.15);
border: 1px solid rgba(239, 68, 68, 0.3);
color: #f87171;
}
/* --- TABLES (ADMIN) --- */
.table-responsive {
overflow-x: auto;
}
.custom-table {
width: 100%;
border-collapse: collapse;
text-align: left;
font-size: 0.9rem;
}
.custom-table th {
background: rgba(255, 255, 255, 0.04);
color: #cbd5e1;
font-weight: 600;
padding: 0.85rem 1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.custom-table td {
padding: 0.85rem 1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
color: #e2e8f0;
}
.custom-table tr:hover td {
background: rgba(255, 255, 255, 0.02);
}
/* --- COLOR PICKER PREVIEW --- */
.color-preview-box {
height: 60px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
color: #0f172a;
margin-top: 0.5rem;
}
/* Empty state */
.empty-state {
text-align: center;
padding: 3rem 1rem;
background: #131b2e;
border: 1px border-dash rgba(255, 255, 255, 0.1);
border-radius: 16px;
}
.empty-state-icon {
font-size: 3rem;
margin-bottom: 1rem;
opacity: 0.7;
}
.empty-state h3 {
font-size: 1.3rem;
font-weight: 700;
color: #ffffff;
margin-bottom: 0.5rem;
}
.empty-state p {
color: #94a3b8;
max-width: 400px;
margin: 0 auto 1.5rem auto;
}
+210 -8
View File
@@ -1,9 +1,211 @@
/* @ default js*/
function yearNow(textlocation) {
//This is a function to substitutes a value into the current year.
//Usually good for the footer.
var data = new Date();
var year;
year = data.getFullYear();
document.querySelector(textlocation).replaceWith(year);
/**
* Blocknotes Application Main JavaScript (Vanilla JS)
*/
document.addEventListener("DOMContentLoaded", function() {
// --- 1. MODALS OPEN & CLOSE HANDLERS ---
document.querySelectorAll('[data-modal-target]').forEach(function(trigger) {
trigger.addEventListener('click', function(e) {
e.preventDefault();
const modalId = this.getAttribute('data-modal-target');
const modal = document.getElementById(modalId);
if (modal) {
modal.classList.add('active');
}
});
});
document.querySelectorAll('.modal-close-btn, .modal-close-trigger').forEach(function(closeBtn) {
closeBtn.addEventListener('click', function() {
const modal = this.closest('.modal-backdrop');
if (modal) {
modal.classList.remove('active');
}
});
});
// Close modal when clicking on backdrop outside modal-card
document.querySelectorAll('.modal-backdrop').forEach(function(backdrop) {
backdrop.addEventListener('click', function(e) {
if (e.target === this) {
this.classList.remove('active');
}
});
});
// --- 2. LIVE HSL THEME COLOR PREVIEW (PROFILE PAGE) ---
const priInput = document.getElementById('theme_pri');
const secInput = document.getElementById('theme_sec');
const thiInput = document.getElementById('theme_thi');
if (priInput && secInput && thiInput) {
function updateLiveTheme() {
const priVal = priInput.value;
const secVal = secInput.value;
const thiVal = thiInput.value;
document.documentElement.style.setProperty('--pri', priVal);
document.documentElement.style.setProperty('--sec', secVal);
document.documentElement.style.setProperty('--thi', thiVal);
const priValDisp = document.getElementById('pri_val');
const secValDisp = document.getElementById('sec_val');
const thiValDisp = document.getElementById('thi_val');
if (priValDisp) priValDisp.textContent = priVal + '°';
if (secValDisp) secValDisp.textContent = secVal + '°';
if (thiValDisp) thiValDisp.textContent = thiVal + '°';
}
priInput.addEventListener('input', updateLiveTheme);
secInput.addEventListener('input', updateLiveTheme);
thiInput.addEventListener('input', updateLiveTheme);
}
});
/**
* Copy text to clipboard with fallback and localized feedback
*/
function copyToClipboard(btnElement, textToCopy) {
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(textToCopy).then(() => {
handleCopySuccess(btnElement);
}).catch(() => {
fallbackCopyText(btnElement, textToCopy);
});
} else {
fallbackCopyText(btnElement, textToCopy);
}
}
function fallbackCopyText(btnElement, textToCopy) {
let textArea = document.createElement("textarea");
textArea.value = textToCopy;
textArea.style.position = "fixed";
textArea.style.left = "-999999px";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand('copy');
handleCopySuccess(btnElement);
} catch (err) {
console.error('Fallback copy failed', err);
}
document.body.removeChild(textArea);
}
function handleCopySuccess(btnElement) {
if (!btnElement) return;
const originalHTML = btnElement.innerHTML;
const copiedLabel = btnElement.getAttribute('data-copied-text') || '✓ Copiato!';
btnElement.innerHTML = copiedLabel;
btnElement.classList.add('copied');
setTimeout(() => {
btnElement.innerHTML = originalHTML;
btnElement.classList.remove('copied');
}, 2000);
}
/**
* Toggle collapse state for a specific note block
*/
function toggleNoteCollapse(noteId) {
const noteBlock = document.getElementById('note-block-' + noteId);
if (noteBlock) {
noteBlock.classList.toggle('collapsed');
}
}
/**
* Expand all note blocks on page
*/
function expandAllNotes() {
document.querySelectorAll('.note-block').forEach(function(nb) {
nb.classList.remove('collapsed');
});
}
/**
* Collapse all note blocks on page
*/
function collapseAllNotes() {
document.querySelectorAll('.note-block').forEach(function(nb) {
nb.classList.add('collapsed');
});
}
/**
* Filter notes and subnotes within the current blocknote in real-time
*/
function filterCurrentBlocknoteNotes(query) {
const term = query.toLowerCase().trim();
const clearBtn = document.getElementById('clear-search-btn');
const noResults = document.getElementById('no-search-results');
const noteBlocks = document.querySelectorAll('.note-block');
if (clearBtn) {
clearBtn.style.display = term ? 'inline-flex' : 'none';
}
let totalMatches = 0;
noteBlocks.forEach(function(noteBlock) {
if (!term) {
// Reset search
noteBlock.style.display = '';
noteBlock.classList.add('collapsed');
noteBlock.querySelectorAll('.subnote-card').forEach(function(sub) {
sub.style.display = '';
});
totalMatches++;
return;
}
const noteTitle = (noteBlock.querySelector('.note-title-wrapper h3')?.innerText || '').toLowerCase();
const noteDesc = (noteBlock.querySelector('.note-description-box p')?.innerText || '').toLowerCase();
const noteTitleMatch = noteTitle.includes(term) || noteDesc.includes(term);
let subnoteMatchCount = 0;
const subnoteCards = noteBlock.querySelectorAll('.subnote-card');
subnoteCards.forEach(function(subCard) {
const subTitle = (subCard.querySelector('.subnote-title')?.innerText || '').toLowerCase();
const subContent = (subCard.querySelector('.subnote-content-text, .command-code')?.innerText || '').toLowerCase();
if (noteTitleMatch || subTitle.includes(term) || subContent.includes(term)) {
subCard.style.display = '';
subnoteMatchCount++;
} else {
subCard.style.display = 'none';
}
});
if (noteTitleMatch || subnoteMatchCount > 0) {
noteBlock.style.display = '';
noteBlock.classList.remove('collapsed'); // Auto expand on match
totalMatches++;
} else {
noteBlock.style.display = 'none';
}
});
if (noResults) {
if (term && totalMatches === 0) {
noResults.style.display = 'block';
} else {
noResults.style.display = 'none';
}
}
}
/**
* Clear search input and restore view
*/
function clearNoteSearch() {
const input = document.getElementById('note-search-input');
if (input) {
input.value = '';
filterCurrentBlocknoteNotes('');
}
}
+1
View File
@@ -0,0 +1 @@
# Keep blocknotes directory tracked in git while empty
+1 -2
View File
@@ -54,8 +54,7 @@
$full_path = $const_before . $const_value;
if (!is_dir($full_path) && !is_file($full_path)) {
debugStatusMes("The folder/file declared into '$const_name' constant is missing or not accessible. Please check it.", "High");
die;
@mkdir($full_path, 0777, true);
}
if (!defined($const_name)) {
+97 -14
View File
@@ -1,21 +1,104 @@
<?php
// Exceptions PHP-MSQLI
// Exceptions PHP-MYSQLI
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
// Create Connection
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
// Connections Successfully
if(DB_DEBUG == "yes"){
echo "Connected successfully";
// Check if first_start.php installer file exists and database setup is needed
$first_start_path = ROOT_DIR . "first_start.php";
if (file_exists($first_start_path) && basename($_SERVER['SCRIPT_FILENAME'] ?? '') !== 'first_start.php') {
// Check connection first
try {
$test_conn = @new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($test_conn->connect_error) {
header("Location: first_start.php");
exit;
}
$test_conn->close();
} catch (Exception $ex) {
header("Location: first_start.php");
exit;
}
} catch (mysqli_sql_exception $e) {
// Access Deny
include(PAGES_FOLD . "db-error.php");
debugStatusMes("Database connection failed.", "High");
}
die();
try {
// Create Connection to MySQL host first (in case DB doesn't exist yet)
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS);
// Create Database if not exists
$db_name_escaped = str_replace('`', '``', DB_NAME);
$conn->query("CREATE DATABASE IF NOT EXISTS `$db_name_escaped` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
$conn->select_db(DB_NAME);
// Ensure BLOCKNOTES_FOLD directory exists
if (!is_dir(BLOCKNOTES_FOLD)) {
@mkdir(BLOCKNOTES_FOLD, 0777, true);
}
// Auto-create users table if missing (with ENUM containing 'owner')
$sql_table = "CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
role ENUM('owner', 'admin', 'user') NOT NULL DEFAULT 'user',
theme_pri INT DEFAULT 98,
theme_sec INT DEFAULT 207,
theme_thi INT DEFAULT 280,
language VARCHAR(5) NOT NULL DEFAULT 'it',
default_blocknote VARCHAR(100) NOT NULL DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
$conn->query($sql_table);
// Auto-migrate role column ENUM if missing 'owner'
$conn->query("ALTER TABLE users MODIFY COLUMN role ENUM('owner', 'admin', 'user') NOT NULL DEFAULT 'user'");
// Auto-migrate columns if missing in existing table
$check_lang = $conn->query("SHOW COLUMNS FROM users LIKE 'language'");
if ($check_lang->num_rows === 0) {
$conn->query("ALTER TABLE users ADD COLUMN language VARCHAR(5) NOT NULL DEFAULT 'it'");
}
$check_def_bn = $conn->query("SHOW COLUMNS FROM users LIKE 'default_blocknote'");
if ($check_def_bn->num_rows === 0) {
$conn->query("ALTER TABLE users ADD COLUMN default_blocknote VARCHAR(100) NOT NULL DEFAULT ''");
}
// Auto-create site_settings table
$sql_settings = "CREATE TABLE IF NOT EXISTS site_settings (
setting_key VARCHAR(50) PRIMARY KEY,
setting_value TEXT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
$conn->query($sql_settings);
// Seed default site settings if empty
$conn->query("INSERT IGNORE INTO site_settings (setting_key, setting_value) VALUES ('site_title', 'sld-blocknotes')");
$conn->query("INSERT IGNORE INTO site_settings (setting_key, setting_value) VALUES ('site_favicon', 'assets/img/favicon.png')");
// Seed default owner account if table is empty or ensure admin user is owner if no owner exists
$res = $conn->query("SELECT COUNT(*) AS total FROM users");
$row = $res->fetch_assoc();
if ((int)$row['total'] === 0) {
$default_pass = password_hash("admin123", PASSWORD_DEFAULT);
$admin_user = "admin";
$stmt = $conn->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, 'owner')");
$stmt->bind_param("ss", $admin_user, $default_pass);
$stmt->execute();
$stmt->close();
} else {
// Check if an owner exists, if not, promote the first user to owner
$check_owner = $conn->query("SELECT COUNT(*) AS total_owners FROM users WHERE role = 'owner'");
$owner_row = $check_owner->fetch_assoc();
if ((int)$owner_row['total_owners'] === 0) {
$conn->query("UPDATE users SET role = 'owner' ORDER BY id ASC LIMIT 1");
}
}
} catch (mysqli_sql_exception $e) {
if (file_exists($first_start_path) && basename($_SERVER['SCRIPT_FILENAME'] ?? '') !== 'first_start.php') {
header("Location: first_start.php");
exit;
}
include(PAGES_FOLD . "db-error.php");
debugStatusMes("Database setup error: " . $e->getMessage(), "High");
die();
}
?>
+1 -1
View File
@@ -22,7 +22,7 @@ function get_current_page() {
$page = $_GET["page"] ?? "home";
$page = strtolower(trim($page));
$page = preg_replace("/[^a-z0-9\-]/", "", $page);
$page = preg_replace("/[^a-z0-9\-_]/", "", $page);
if ($page === "") {
$page = "home";
+7 -5
View File
@@ -5,12 +5,14 @@
switch($env){
/*------ DON'T TOUCH THIS PART ------ */
case "generic":
define('WEB_ROOT', $_SERVER['DOCUMENT_ROOT']);
define('DOMAIN', $_SERVER['HTTP_HOST']);
$doc_root = !empty($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : dirname(__DIR__);
define('WEB_ROOT', $doc_root);
$host = !empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';
define('DOMAIN', $host);
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https://' : 'http://';
define('FULL_DOMAIN', $protocol . $_SERVER['HTTP_HOST']);
define("ROOT_URL", "http://" . DOMAIN );
define("ROOT_DIR", WEB_ROOT . "/");
define('FULL_DOMAIN', $protocol . DOMAIN);
define("ROOT_URL", $protocol . DOMAIN );
define("ROOT_DIR", rtrim(WEB_ROOT, '/') . "/");
break;
/* ----------------------------------- */
default:
+6 -6
View File
@@ -1,15 +1,15 @@
<?php
// -- GENERAL SETTING --
$debug="on";
$php_debug="on";
$debug="off";
$php_debug="off";
// --- DB SETTINGS ---
$db_needed="no";
$db_needed="yes";
$db_debug="yes";
$db_host="localhost";
$db_name="database-name";
$db_user="database-user";
$db_pass="database-user-password";
$db_name="sld_blocknotes_db";
$db_user="root";
$db_pass="";
//-------------------------------------------------------------------------------------
// --- DON'T CHANGE ALL THE CODE BELOW!!! ---
+320
View File
@@ -0,0 +1,320 @@
<?php
/**
* Blocknotes System - First Start Setup Installer Wizard (Multilingual)
*/
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Define ROOT_DIR and include lang helper
define("ROOT_DIR", __DIR__ . "/");
require_once(__DIR__ . "/functions/lang.php");
// Handle language selection
if (isset($_GET['set_lang'])) {
set_active_lang($_GET['set_lang']);
header("Location: first_start.php");
exit;
}
$current_lang = get_current_lang();
$msg_error = "";
$msg_success = "";
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$db_host = trim($_POST['db_host'] ?? 'localhost');
$db_name = trim($_POST['db_name'] ?? 'sld_blocknotes_db');
$db_user = trim($_POST['db_user'] ?? '');
$db_pass = $_POST['db_pass'] ?? '';
$owner_user = trim($_POST['owner_user'] ?? '');
$owner_pass = $_POST['owner_pass'] ?? '';
$admin_user = trim($_POST['admin_user'] ?? '');
$admin_pass = $_POST['admin_pass'] ?? '';
if (empty($db_host) || empty($db_name) || empty($db_user)) {
$msg_error = __('err_setup_db_fields');
} elseif (empty($owner_user) || empty($owner_pass)) {
$msg_error = __('err_setup_owner_fields');
} elseif (empty($admin_user) || empty($admin_pass)) {
$msg_error = __('err_setup_admin_fields');
} elseif (strtolower($owner_user) === strtolower($admin_user)) {
$msg_error = __('err_setup_same_user');
} else {
mysqli_report(MYSQLI_REPORT_OFF);
$conn = @new mysqli($db_host, $db_user, $db_pass);
if ($conn->connect_error) {
$msg_error = "Connessione MySQL fallita: " . $conn->connect_error;
} else {
// Create database
$db_name_escaped = str_replace('`', '``', $db_name);
if (!$conn->query("CREATE DATABASE IF NOT EXISTS `$db_name_escaped` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")) {
$msg_error = "Impossibile creare il database '$db_name': " . $conn->error;
} else {
$conn->select_db($db_name);
// Create users table
$sql_users = "CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
role ENUM('owner', 'admin', 'user') NOT NULL DEFAULT 'user',
theme_pri INT DEFAULT 98,
theme_sec INT DEFAULT 207,
theme_thi INT DEFAULT 280,
language VARCHAR(5) NOT NULL DEFAULT 'it',
default_blocknote VARCHAR(100) NOT NULL DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
$conn->query($sql_users);
// Create site_settings table
$sql_settings = "CREATE TABLE IF NOT EXISTS site_settings (
setting_key VARCHAR(50) PRIMARY KEY,
setting_value TEXT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
$conn->query($sql_settings);
// Insert site_settings default values
$conn->query("INSERT IGNORE INTO site_settings (setting_key, setting_value) VALUES ('site_title', 'sld-blocknotes')");
$conn->query("INSERT IGNORE INTO site_settings (setting_key, setting_value) VALUES ('site_favicon', 'assets/img/favicon.png')");
// Create Owner account
$hash_owner = password_hash($owner_pass, PASSWORD_DEFAULT);
$stmt = $conn->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, 'owner') ON DUPLICATE KEY UPDATE password = VALUES(password), role = 'owner'");
$stmt->bind_param("ss", $owner_user, $hash_owner);
$stmt->execute();
$stmt->close();
// Create Admin account
$hash_admin = password_hash($admin_pass, PASSWORD_DEFAULT);
$stmt = $conn->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, 'admin') ON DUPLICATE KEY UPDATE password = VALUES(password), role = 'admin'");
$stmt->bind_param("ss", $admin_user, $hash_admin);
$stmt->execute();
$stmt->close();
// Create blocknotes directory if missing
$blocknotes_dir = __DIR__ . "/blocknotes/";
if (!is_dir($blocknotes_dir)) {
@mkdir($blocknotes_dir, 0777, true);
}
// Update config/settings.php file
$settings_file_content = '<?php
// -- GENERAL SETTING --
$debug="off";
$php_debug="off";
// --- DB SETTINGS ---
$db_needed="yes";
$db_debug="yes";
$db_host="' . addslashes($db_host) . '";
$db_name="' . addslashes($db_name) . '";
$db_user="' . addslashes($db_user) . '";
$db_pass="' . addslashes($db_pass) . '";
//-------------------------------------------------------------------------------------
// --- DON\'T CHANGE ALL THE CODE BELOW!!! ---
//-------------------------------------------------------------------------------------
// --- GENERAL SECTION CONFIGURATION
if ($debug == "on"){ define("DEBUG",true);} else { define("DEBUG", false); }
if ($php_debug == "on") {
ini_set(\'display_errors\', 1);
ini_set(\'display_startup_errors\', 1);
error_reporting(E_ALL);
} else {
ini_set(\'display_errors\', 0);
error_reporting(0);
}
// --- DB SECTION CONFIGURATION
if ($db_needed == "yes"){
define("DB_HOST", $db_host);
define("DB_USER", $db_user);
define("DB_PASS", $db_pass);
define("DB_NAME", $db_name);
define("DB_DEBUG", $db_debug);
require_once(CONFIG_FOLD . "db.php");
}
?>';
file_put_contents(__DIR__ . "/config/settings.php", $settings_file_content);
// Rename first_start.php to first_start.php.completed to prevent installer re-entry
@rename(__FILE__, __DIR__ . "/first_start.php.completed");
// Redirect to login page
header("Location: index.php?page=login&installed=1");
exit;
}
}
}
}
?>
<!DOCTYPE html>
<html lang="<?= htmlspecialchars($current_lang) ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= __('setup_title') ?> - sld-blocknotes</title>
<link rel="stylesheet" href="assets/css/style.css">
<style>
body {
background: radial-gradient(circle at top left, #0f172a, #020617);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem 1rem;
font-family: system-ui, -apple-system, sans-serif;
color: #f8fafc;
}
.setup-container {
max-width: 680px;
width: 100%;
background: rgba(15, 23, 42, 0.75);
backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 16px;
padding: 2.5rem;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
}
.setup-header {
text-align: center;
margin-bottom: 2rem;
}
.setup-header h1 {
font-size: 1.8rem;
font-weight: 800;
color: #fff;
margin-bottom: 0.5rem;
}
.setup-header p {
color: #94a3b8;
font-size: 0.95rem;
}
.form-section-title {
font-size: 1.1rem;
font-weight: 700;
color: #7dd3fc;
margin-top: 1.5rem;
margin-bottom: 1rem;
padding-bottom: 0.4rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
display: flex;
align-items: center;
gap: 0.5rem;
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
@media (max-width: 580px) {
.grid-2 { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="setup-container">
<!-- LANGUAGE SELECTOR -->
<div style="display: flex; justify-content: flex-end; margin-bottom: 1rem;">
<select onchange="location.href='first_start.php?set_lang=' + this.value;" style="background: rgba(0,0,0,0.4); color: #fff; border: 1px solid rgba(255,255,255,0.2); border-radius: 6px; padding: 0.35rem 0.6rem; font-size: 0.85rem; cursor: pointer;">
<option value="it" <?= ($current_lang === 'it') ? 'selected' : '' ?>>🇮🇹 IT</option>
<option value="en" <?= ($current_lang === 'en') ? 'selected' : '' ?>>🇬🇧 EN</option>
<option value="es" <?= ($current_lang === 'es') ? 'selected' : '' ?>>🇪🇸 ES</option>
<option value="fr" <?= ($current_lang === 'fr') ? 'selected' : '' ?>>🇫🇷 FR</option>
</select>
</div>
<div class="setup-header">
<h1><?= __('setup_title') ?></h1>
<p><?= __('setup_subtitle') ?></p>
</div>
<?php if (!empty($msg_error)): ?>
<div class="alert alert-danger" style="background: rgba(239, 68, 68, 0.2); border: 1px solid rgba(239, 68, 68, 0.4); color: #fca5a5; padding: 0.85rem 1rem; border-radius: 8px; margin-bottom: 1.5rem; font-size: 0.9rem;">
⚠️ <?= htmlspecialchars($msg_error) ?>
</div>
<?php endif; ?>
<form action="first_start.php" method="POST">
<!-- SEC 1: DATABASE -->
<div class="form-section-title">
<span>🗄️</span> <?= __('setup_sec1_title') ?>
</div>
<div class="grid-2">
<div class="form-group">
<label for="db_host"><?= __('setup_db_host') ?></label>
<input type="text" name="db_host" id="db_host" class="form-control" value="<?= htmlspecialchars($_POST['db_host'] ?? 'localhost') ?>" required>
</div>
<div class="form-group">
<label for="db_name"><?= __('setup_db_name') ?></label>
<input type="text" name="db_name" id="db_name" class="form-control" value="<?= htmlspecialchars($_POST['db_name'] ?? 'sld_blocknotes_db') ?>" required>
</div>
</div>
<div class="grid-2">
<div class="form-group">
<label for="db_user"><?= __('setup_db_user') ?></label>
<input type="text" name="db_user" id="db_user" class="form-control" value="<?= htmlspecialchars($_POST['db_user'] ?? 'simone') ?>" required>
</div>
<div class="form-group">
<label for="db_pass"><?= __('setup_db_pass') ?></label>
<input type="password" name="db_pass" id="db_pass" class="form-control" placeholder="<?= htmlspecialchars(__('setup_db_pass_placeholder')) ?>">
</div>
</div>
<!-- SEC 2: OWNER ACCOUNT -->
<div class="form-section-title" style="color: #fde047;">
<span>👑</span> <?= __('setup_sec2_title') ?>
</div>
<div class="grid-2">
<div class="form-group">
<label for="owner_user"><?= __('setup_owner_user') ?></label>
<input type="text" name="owner_user" id="owner_user" class="form-control" value="<?= htmlspecialchars($_POST['owner_user'] ?? 'owner') ?>" required placeholder="owner">
</div>
<div class="form-group">
<label for="owner_pass"><?= __('setup_owner_pass') ?></label>
<input type="password" name="owner_pass" id="owner_pass" class="form-control" required placeholder="<?= htmlspecialchars(__('setup_owner_pass_placeholder')) ?>">
</div>
</div>
<!-- SEC 3: ADMIN ACCOUNT -->
<div class="form-section-title" style="color: #a7f3d0;">
<span>🛡️</span> <?= __('setup_sec3_title') ?>
</div>
<div class="grid-2">
<div class="form-group">
<label for="admin_user"><?= __('setup_admin_user') ?></label>
<input type="text" name="admin_user" id="admin_user" class="form-control" value="<?= htmlspecialchars($_POST['admin_user'] ?? 'admin') ?>" required placeholder="admin">
</div>
<div class="form-group">
<label for="admin_pass"><?= __('setup_admin_pass') ?></label>
<input type="password" name="admin_pass" id="admin_pass" class="form-control" required placeholder="<?= htmlspecialchars(__('setup_admin_pass_placeholder')) ?>">
</div>
</div>
<div style="margin-top: 2rem;">
<button type="submit" class="btn btn-primary" style="width: 100%; justify-content: center; padding: 0.9rem; font-size: 1.05rem; font-weight: 700;">
<?= __('setup_btn_submit') ?>
</button>
</div>
</form>
</div>
</body>
</html>
+314
View File
@@ -0,0 +1,314 @@
<?php
/**
* Authentication and User Management Helper Functions
*/
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
/**
* Check if a user is logged in.
*/
function is_logged_in() {
return isset($_SESSION['user']) && !empty($_SESSION['user']['id']);
}
/**
* Get current logged in user details.
*/
function current_user() {
return $_SESSION['user'] ?? null;
}
/**
* Check if logged in user is owner.
*/
function is_owner() {
$user = current_user();
return $user && ($user['role'] === 'owner');
}
/**
* Check if logged in user is admin (or owner).
*/
function is_admin() {
$user = current_user();
return $user && ($user['role'] === 'admin' || $user['role'] === 'owner');
}
/**
* Require login to access a page.
*/
function require_login() {
if (!is_logged_in()) {
header("Location: index.php?page=login");
exit;
}
}
/**
* Require admin role to access a page.
*/
function require_admin() {
require_login();
if (!is_admin()) {
header("Location: index.php?page=home");
exit;
}
}
/**
* Authenticate user with username and password.
*/
function login_user($username, $password) {
global $conn;
$username = trim($username);
if (empty($username) || empty($password)) {
return ['success' => false, 'message' => 'Inserisci sia username che password.'];
}
$stmt = $conn->prepare("SELECT id, username, password, role, theme_pri, theme_sec, theme_thi, language, default_blocknote FROM users WHERE username = ? LIMIT 1");
$stmt->bind_param("s", $username);
$stmt->execute();
$result = $stmt->get_result();
if ($row = $result->fetch_assoc()) {
if (password_verify($password, $row['password'])) {
$_SESSION['user'] = [
'id' => $row['id'],
'username' => $row['username'],
'role' => $row['role'],
'theme_pri' => $row['theme_pri'] ?? 98,
'theme_sec' => $row['theme_sec'] ?? 207,
'theme_thi' => $row['theme_thi'] ?? 280,
'language' => $row['language'] ?? 'it',
'default_blocknote' => $row['default_blocknote'] ?? ''
];
$stmt->close();
return ['success' => true];
}
}
$stmt->close();
return ['success' => false, 'message' => 'Username o password errati.'];
}
/**
* Logout current user.
*/
function logout_user() {
$_SESSION = array();
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
session_destroy();
}
/**
* Update user password.
*/
function update_user_password($user_id, $new_password) {
global $conn;
if (strlen($new_password) < 4) {
return ['success' => false, 'message' => 'La password deve contenere almeno 4 caratteri.'];
}
$hash = password_hash($new_password, PASSWORD_DEFAULT);
$stmt = $conn->prepare("UPDATE users SET password = ? WHERE id = ?");
$stmt->bind_param("si", $hash, $user_id);
$res = $stmt->execute();
$stmt->close();
return $res ? ['success' => true, 'message' => 'Password aggiornata con successo!'] : ['success' => false, 'message' => 'Errore durante il salvataggio della password.'];
}
/**
* Update user language preference.
*/
function update_user_language($user_id, $lang) {
global $conn;
if (!in_array($lang, ['it', 'en', 'es', 'fr'])) {
$lang = 'it';
}
$stmt = $conn->prepare("UPDATE users SET language = ? WHERE id = ?");
$stmt->bind_param("si", $lang, $user_id);
$res = $stmt->execute();
$stmt->close();
if ($res) {
if (isset($_SESSION['user']) && $_SESSION['user']['id'] == $user_id) {
$_SESSION['user']['language'] = $lang;
}
return ['success' => true, 'message' => 'Lingua aggiornata con successo!'];
}
return ['success' => false, 'message' => 'Errore durante l\'aggiornamento della lingua.'];
}
/**
* Update user default landing blocknote preference.
*/
function update_user_default_blocknote($user_id, $bn_id) {
global $conn;
$bn_id = trim($bn_id);
$stmt = $conn->prepare("UPDATE users SET default_blocknote = ? WHERE id = ?");
$stmt->bind_param("si", $bn_id, $user_id);
$res = $stmt->execute();
$stmt->close();
if ($res) {
if (isset($_SESSION['user']) && $_SESSION['user']['id'] == $user_id) {
$_SESSION['user']['default_blocknote'] = $bn_id;
}
return ['success' => true, 'message' => 'Pagina di avvio aggiornata con successo!'];
}
return ['success' => false, 'message' => 'Errore durante l\'aggiornamento della pagina di avvio.'];
}
/**
* Update user interface theme colors (HSL).
*/
function update_user_theme($user_id, $pri, $sec, $thi) {
global $conn;
$pri = max(0, min(360, (int)$pri));
$sec = max(0, min(360, (int)$sec));
$thi = max(0, min(360, (int)$thi));
$stmt = $conn->prepare("UPDATE users SET theme_pri = ?, theme_sec = ?, theme_thi = ? WHERE id = ?");
$stmt->bind_param("iiii", $pri, $sec, $thi, $user_id);
$res = $stmt->execute();
$stmt->close();
if ($res) {
if (isset($_SESSION['user']) && $_SESSION['user']['id'] == $user_id) {
$_SESSION['user']['theme_pri'] = $pri;
$_SESSION['user']['theme_sec'] = $sec;
$_SESSION['user']['theme_thi'] = $thi;
}
return ['success' => true, 'message' => 'Colori dell\'interfaccia aggiornati!'];
}
return ['success' => false, 'message' => 'Errore durante l\'aggiornamento dei colori.'];
}
/**
* Admin: Get list of all registered users.
*/
function get_all_users() {
global $conn;
$result = $conn->query("SELECT id, username, role, created_at FROM users ORDER BY id ASC");
$users = [];
while ($row = $result->fetch_assoc()) {
$users[] = $row;
}
return $users;
}
/**
* Admin: Register a new user.
*/
function admin_create_user($username, $password, $role = 'user') {
global $conn;
$username = trim($username);
if (empty($username) || empty($password)) {
return ['success' => false, 'message' => 'Username e password sono obbligatori.'];
}
if (!in_array($role, ['user', 'admin', 'owner'])) {
$role = 'user';
}
if ($role === 'owner') {
$check_owner = $conn->query("SELECT id FROM users WHERE role = 'owner'");
if ($check_owner->num_rows > 0) {
return ['success' => false, 'message' => 'Può esistere un solo account Owner nel sistema!'];
}
}
// Check if exists
$stmt = $conn->prepare("SELECT id FROM users WHERE username = ? LIMIT 1");
$stmt->bind_param("s", $username);
$stmt->execute();
if ($stmt->get_result()->num_rows > 0) {
$stmt->close();
return ['success' => false, 'message' => 'Un utente con questo username esiste già.'];
}
$stmt->close();
$hash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $conn->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $username, $hash, $role);
$res = $stmt->execute();
$stmt->close();
return $res ? ['success' => true, 'message' => 'Utente creato con successo!'] : ['success' => false, 'message' => 'Impossibile creare l\'utente.'];
}
/**
* Admin: Delete a user by ID.
*/
function admin_delete_user($user_id) {
global $conn;
$user_id = (int)$user_id;
// Prevent self-deletion
if (current_user() && current_user()['id'] == $user_id) {
return ['success' => false, 'message' => 'Non puoi eliminare il tuo stesso account!'];
}
// Check target user role - Owner can NEVER be deleted
$stmt = $conn->prepare("SELECT role FROM users WHERE id = ? LIMIT 1");
$stmt->bind_param("i", $user_id);
$stmt->execute();
$target_user = $stmt->get_result()->fetch_assoc();
$stmt->close();
if ($target_user && $target_user['role'] === 'owner') {
return ['success' => false, 'message' => 'L\'account Owner non può essere eliminato da nessuno!'];
}
$stmt = $conn->prepare("DELETE FROM users WHERE id = ?");
$stmt->bind_param("i", $user_id);
$res = $stmt->execute();
$stmt->close();
return $res ? ['success' => true, 'message' => 'Utente eliminato.'] : ['success' => false, 'message' => 'Errore nell\'eliminazione dell\'utente.'];
}
/**
* Admin: Update user role.
*/
function admin_update_user_role($user_id, $role) {
global $conn;
$user_id = (int)$user_id;
if (!in_array($role, ['admin', 'user', 'owner'])) return ['success' => false, 'message' => 'Ruolo non valido.'];
// Check target user role - Owner role cannot be changed
$stmt = $conn->prepare("SELECT role FROM users WHERE id = ? LIMIT 1");
$stmt->bind_param("i", $user_id);
$stmt->execute();
$target_user = $stmt->get_result()->fetch_assoc();
$stmt->close();
if ($target_user && $target_user['role'] === 'owner') {
return ['success' => false, 'message' => 'Il ruolo dell\'account Owner non può essere modificato!'];
}
if ($role === 'owner') {
$check_owner = $conn->query("SELECT id FROM users WHERE role = 'owner'");
if ($check_owner->num_rows > 0) {
return ['success' => false, 'message' => 'Può esistere un solo account Owner nel sistema!'];
}
}
$stmt = $conn->prepare("UPDATE users SET role = ? WHERE id = ?");
$stmt->bind_param("si", $role, $user_id);
$res = $stmt->execute();
$stmt->close();
return $res ? ['success' => true, 'message' => 'Ruolo aggiornato.'] : ['success' => false, 'message' => 'Impossibile aggiornare il ruolo.'];
}
+421
View File
@@ -0,0 +1,421 @@
<?php
/**
* Blocknotes JSON Storage Engine Functions
*/
/**
* Ensure storage directory exists.
*/
function ensure_blocknotes_directory() {
if (!is_dir(BLOCKNOTES_FOLD)) {
@mkdir(BLOCKNOTES_FOLD, 0777, true);
}
}
/**
* Scan BLOCKNOTES_FOLD and load all JSON files.
*/
function get_all_blocknotes() {
ensure_blocknotes_directory();
$files = glob(BLOCKNOTES_FOLD . "*.json");
$blocknotes = [];
if ($files) {
foreach ($files as $filepath) {
$content = file_get_contents($filepath);
$data = json_decode($content, true);
if (is_array($data) && isset($data['id'])) {
$blocknotes[] = $data;
}
}
}
// Sort by updated_at descending
usort($blocknotes, function($a, $b) {
return strtotime($b['updated_at'] ?? 0) - strtotime($a['updated_at'] ?? 0);
});
return $blocknotes;
}
/**
* Get blocknotes assigned to a specific user (or all if admin).
*/
function get_user_blocknotes($username, $is_admin = false) {
$all = get_all_blocknotes();
if ($is_admin) {
return $all;
}
return array_filter($all, function($bn) use ($username) {
return isset($bn['username']) && strtolower($bn['username']) === strtolower($username);
});
}
/**
* Find blocknote by ID.
*/
function get_blocknote_by_id($id) {
ensure_blocknotes_directory();
$filename = BLOCKNOTES_FOLD . preg_replace('/[^a-zA-Z0-9_\-]/', '', $id) . ".json";
if (file_exists($filename)) {
$content = file_get_contents($filename);
$data = json_decode($content, true);
if (is_array($data)) return $data;
}
// Fallback search across all files if ID is stored inside
$all = get_all_blocknotes();
foreach ($all as $bn) {
if (isset($bn['id']) && $bn['id'] === $id) {
return $bn;
}
}
return null;
}
/**
* Save blocknote to JSON file.
*/
function save_blocknote($data) {
ensure_blocknotes_directory();
if (empty($data['id'])) {
$data['id'] = 'bn_' . uniqid();
}
$data['updated_at'] = date('Y-m-d H:i:s');
if (empty($data['created_at'])) {
$data['created_at'] = $data['updated_at'];
}
if (!isset($data['notes'])) {
$data['notes'] = [];
}
$filepath = BLOCKNOTES_FOLD . preg_replace('/[^a-zA-Z0-9_\-]/', '', $data['id']) . ".json";
$json_string = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
return file_put_contents($filepath, $json_string) !== false;
}
/**
* Create a new blocknote.
*/
function create_blocknote($title, $username) {
$title = trim($title);
if (empty($title)) {
return ['success' => false, 'message' => 'Il titolo del blocknote è obbligatorio.'];
}
$id = 'bn_' . uniqid();
$data = [
'id' => $id,
'title' => $title,
'username' => $username,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
'notes' => []
];
if (save_blocknote($data)) {
return ['success' => true, 'id' => $id, 'message' => 'Blocknote creato con successo!'];
}
return ['success' => false, 'message' => 'Errore nel salvataggio del file JSON.'];
}
/**
* Import a JSON string (supports standard format and legacy codes format).
*/
function import_blocknote_json($json_string, $username, $custom_title = '') {
$data = json_decode($json_string, true);
if (!is_array($data)) {
return ['success' => false, 'message' => 'File JSON non valido o malformato.'];
}
$id = 'bn_' . uniqid();
$title = !empty($custom_title) ? trim($custom_title) : 'Blocknote Importato';
// Case 1: Standard format with 'notes' array
if (isset($data['notes']) && is_array($data['notes'])) {
$bn = [
'id' => $id,
'title' => !empty($data['title']) ? $data['title'] : $title,
'username' => $username,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
'notes' => $data['notes']
];
if (save_blocknote($bn)) {
return ['success' => true, 'id' => $id, 'message' => 'Blocknote importato con successo!'];
}
}
// Case 2: Legacy format (array of notes with 'title', 'description', 'codes')
if (isset($data[0]) && is_array($data[0])) {
$notes = [];
foreach ($data as $n_idx => $legacy_note) {
$note_id = 'note_' . ($n_idx + 1) . '_' . uniqid();
$subnotes = [];
if (isset($legacy_note['codes']) && is_array($legacy_note['codes'])) {
foreach ($legacy_note['codes'] as $s_idx => $legacy_code) {
$subnotes[] = [
'id' => 'sub_' . ($s_idx + 1) . '_' . uniqid(),
'title' => $legacy_code['sub_title'] ?? 'Comando',
'type' => 'command',
'content' => $legacy_code['code'] ?? ''
];
}
}
$notes[] = [
'id' => $note_id,
'title' => $legacy_note['title'] ?? 'Nota ' . ($n_idx + 1),
'description' => $legacy_note['description'] ?? '',
'subnotes' => $subnotes
];
}
$bn = [
'id' => $id,
'title' => $title,
'username' => $username,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
'notes' => $notes
];
if (save_blocknote($bn)) {
return ['success' => true, 'id' => $id, 'message' => 'Blocknote in formato legacy importato e convertito con successo!'];
}
}
return ['success' => false, 'message' => 'Formato JSON non riconosciuto.'];
}
/**
* Check permission on blocknote.
*/
function can_access_blocknote($bn, $username, $is_admin) {
if (!$bn) return false;
if ($is_admin) return true;
return strtolower($bn['username'] ?? '') === strtolower($username);
}
/**
* Rename a blocknote.
*/
function rename_blocknote($id, $new_title, $username, $is_admin) {
$bn = get_blocknote_by_id($id);
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
if (!can_access_blocknote($bn, $username, $is_admin)) {
return ['success' => false, 'message' => 'Non hai i permessi per modificare questo blocknote.'];
}
$new_title = trim($new_title);
if (empty($new_title)) return ['success' => false, 'message' => 'Il titolo non può essere vuoto.'];
$bn['title'] = $new_title;
if (save_blocknote($bn)) {
return ['success' => true, 'message' => 'Blocknote rinominato con successo.'];
}
return ['success' => false, 'message' => 'Errore nel salvataggio del file JSON.'];
}
/**
* Delete a blocknote.
*/
function delete_blocknote($id, $username, $is_admin) {
$bn = get_blocknote_by_id($id);
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
if (!can_access_blocknote($bn, $username, $is_admin)) {
return ['success' => false, 'message' => 'Non hai i permessi per eliminare questo blocknote.'];
}
$filepath = BLOCKNOTES_FOLD . preg_replace('/[^a-zA-Z0-9_\-]/', '', $bn['id']) . ".json";
if (file_exists($filepath)) {
@unlink($filepath);
}
return ['success' => true, 'message' => 'Blocknote eliminato con successo.'];
}
/**
* Add a new Note block to a Blocknote.
*/
function add_note($blocknote_id, $title, $description, $username, $is_admin) {
$bn = get_blocknote_by_id($blocknote_id);
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
if (!can_access_blocknote($bn, $username, $is_admin)) return ['success' => false, 'message' => 'Permesso negato.'];
$title = trim($title);
if (empty($title)) return ['success' => false, 'message' => 'Il titolo della nota è obbligatorio.'];
$note_id = 'note_' . uniqid();
$new_note = [
'id' => $note_id,
'title' => $title,
'description' => trim($description),
'subnotes' => []
];
$bn['notes'][] = $new_note;
if (save_blocknote($bn)) {
return ['success' => true, 'note_id' => $note_id, 'message' => 'Nota aggiunta con successo!'];
}
return ['success' => false, 'message' => 'Errore durante il salvataggio.'];
}
/**
* Edit an existing Note block.
*/
function edit_note($blocknote_id, $note_id, $title, $description, $username, $is_admin) {
$bn = get_blocknote_by_id($blocknote_id);
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
if (!can_access_blocknote($bn, $username, $is_admin)) return ['success' => false, 'message' => 'Permesso negato.'];
$title = trim($title);
if (empty($title)) return ['success' => false, 'message' => 'Il titolo della nota è obbligatorio.'];
$found = false;
foreach ($bn['notes'] as &$note) {
if ($note['id'] === $note_id) {
$note['title'] = $title;
$note['description'] = trim($description);
$found = true;
break;
}
}
if (!$found) return ['success' => false, 'message' => 'Nota non trovata.'];
if (save_blocknote($bn)) {
return ['success' => true, 'message' => 'Nota modificata con successo!'];
}
return ['success' => false, 'message' => 'Errore nel salvataggio.'];
}
/**
* Delete a Note block.
*/
function delete_note($blocknote_id, $note_id, $username, $is_admin) {
$bn = get_blocknote_by_id($blocknote_id);
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
if (!can_access_blocknote($bn, $username, $is_admin)) return ['success' => false, 'message' => 'Permesso negato.'];
$initial_count = count($bn['notes']);
$bn['notes'] = array_values(array_filter($bn['notes'], function($n) use ($note_id) {
return $n['id'] !== $note_id;
}));
if (count($bn['notes']) === $initial_count) {
return ['success' => false, 'message' => 'Nota non trovata.'];
}
if (save_blocknote($bn)) {
return ['success' => true, 'message' => 'Nota eliminata con successo.'];
}
return ['success' => false, 'message' => 'Errore nel salvataggio.'];
}
/**
* Add a Subnote to a Note block.
*/
function add_subnote($blocknote_id, $note_id, $sub_title, $type, $content, $username, $is_admin) {
$bn = get_blocknote_by_id($blocknote_id);
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
if (!can_access_blocknote($bn, $username, $is_admin)) return ['success' => false, 'message' => 'Permesso negato.'];
$sub_title = trim($sub_title);
if (empty($sub_title)) return ['success' => false, 'message' => 'Il titolo della sottonota è obbligatorio.'];
$type = ($type === 'command') ? 'command' : 'text';
$sub_id = 'sub_' . uniqid();
$found = false;
foreach ($bn['notes'] as &$note) {
if ($note['id'] === $note_id) {
if (!isset($note['subnotes'])) $note['subnotes'] = [];
$note['subnotes'][] = [
'id' => $sub_id,
'title' => $sub_title,
'type' => $type,
'content' => $content
];
$found = true;
break;
}
}
if (!$found) return ['success' => false, 'message' => 'Nota genitore non trovata.'];
if (save_blocknote($bn)) {
return ['success' => true, 'subnote_id' => $sub_id, 'message' => 'Sottonota aggiunta con successo!'];
}
return ['success' => false, 'message' => 'Errore nel salvataggio.'];
}
/**
* Edit a Subnote.
*/
function edit_subnote($blocknote_id, $note_id, $sub_id, $sub_title, $type, $content, $username, $is_admin) {
$bn = get_blocknote_by_id($blocknote_id);
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
if (!can_access_blocknote($bn, $username, $is_admin)) return ['success' => false, 'message' => 'Permesso negato.'];
$sub_title = trim($sub_title);
if (empty($sub_title)) return ['success' => false, 'message' => 'Il titolo della sottonota è obbligatorio.'];
$type = ($type === 'command') ? 'command' : 'text';
$found = false;
foreach ($bn['notes'] as &$note) {
if ($note['id'] === $note_id) {
if (isset($note['subnotes'])) {
foreach ($note['subnotes'] as &$sub) {
if ($sub['id'] === $sub_id) {
$sub['title'] = $sub_title;
$sub['type'] = $type;
$sub['content'] = $content;
$found = true;
break 2;
}
}
}
}
}
if (!$found) return ['success' => false, 'message' => 'Sottonota non trovata.'];
if (save_blocknote($bn)) {
return ['success' => true, 'message' => 'Sottonota modificata con successo!'];
}
return ['success' => false, 'message' => 'Errore nel salvataggio.'];
}
/**
* Delete a Subnote.
*/
function delete_subnote($blocknote_id, $note_id, $sub_id, $username, $is_admin) {
$bn = get_blocknote_by_id($blocknote_id);
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
if (!can_access_blocknote($bn, $username, $is_admin)) return ['success' => false, 'message' => 'Permesso negato.'];
$found = false;
foreach ($bn['notes'] as &$note) {
if ($note['id'] === $note_id && isset($note['subnotes'])) {
$initial = count($note['subnotes']);
$note['subnotes'] = array_values(array_filter($note['subnotes'], function($s) use ($sub_id) {
return $s['id'] !== $sub_id;
}));
if (count($note['subnotes']) < $initial) {
$found = true;
break;
}
}
}
if (!$found) return ['success' => false, 'message' => 'Sottonota non trovata.'];
if (save_blocknote($bn)) {
return ['success' => true, 'message' => 'Sottonota eliminata con successo.'];
}
return ['success' => false, 'message' => 'Errore nel salvataggio.'];
}
+5 -6
View File
@@ -1,13 +1,15 @@
<?php
/*
### LIST OF FUNCTIONS TO INCLUDE ###
Include in this array all the function files you may want to include.
Include in this array all the function files you may want to include.
You don't need to specify the extension .php, the system will automatically add it.
*/
$fun_to_inc = array(
//"example",
"demo-example"
"lang",
"site_settings",
"auth",
"blocknotes"
);
// DON'T CHANGE THE CODE BELOW!!!
@@ -17,9 +19,6 @@ if (isset($fun_to_inc) && is_array($fun_to_inc) && !empty($fun_to_inc)){
require_once(FUNCTIONS_FOLD . $fun . ".php");
} else {
debugStatusMes("Function file $fun.php not found in functions folder: '" . FUNCTIONS_FOLD . "'", "Medium");
if ($fun === "demo-example") {
debugStatusMes("The 'demo-example' function is only a demo. If you create a new function, needs to be included in the file specified above. Please remove the demo-example record from 'functions/functions.php' ", "Info");
}
}
}
}
+720
View File
@@ -0,0 +1,720 @@
<?php
/**
* Multilingual System Helper (IT, EN, ES, FR)
*/
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
/**
* Get current user language code.
*/
function get_current_lang() {
$user = $_SESSION['user'] ?? null;
$lang = $user['language'] ?? $_SESSION['guest_language'] ?? 'it';
if (!in_array($lang, ['it', 'en', 'es', 'fr'])) {
$lang = 'it';
}
return $lang;
}
/**
* Set guest/active language.
*/
function set_active_lang($lang) {
if (in_array($lang, ['it', 'en', 'es', 'fr'])) {
if (isset($_SESSION['user'])) {
$_SESSION['user']['language'] = $lang;
global $conn;
if (isset($conn) && !empty($_SESSION['user']['id'])) {
$stmt = $conn->prepare("UPDATE users SET language = ? WHERE id = ?");
$stmt->bind_param("si", $lang, $_SESSION['user']['id']);
$stmt->execute();
$stmt->close();
}
} else {
$_SESSION['guest_language'] = $lang;
}
}
}
/**
* Translations Dictionary
*/
function get_translations() {
return [
'it' => [
// Login Page
'login_title' => 'Accedi a Blocknotes',
'login_subtitle' => 'Gestisci le tue note e i tuoi comandi',
'label_username' => 'Nome Utente',
'label_password' => 'Password',
'placeholder_username' => 'Inserisci username',
'placeholder_password' => 'Inserisci password',
'btn_submit_login' => '🔑 Accedi',
'login_hint_footer' => 'Accredito predefinito per il primo accesso',
'login_installed_success' => '🎉 Setup completato! Accedi con le credenziali create.',
// Navbar
'nav_blocknotes' => '📓 Blocknotes',
'nav_colors_profile' => '🎨 Colori & Profilo',
'nav_admin' => '👑 Gestione Utenti',
'nav_logout' => '🚪 Esci',
'nav_login' => '🔑 Accedi',
'nav_language' => '🌐 Lingua',
// Dashboard (home)
'dashboard_title_user' => 'I Miei Blocknotes',
'dashboard_title_admin' => 'Tutti i Blocknotes (Vista Admin)',
'dashboard_desc_user' => 'Crea, organizza e consulta i tuoi blocknotes.',
'dashboard_desc_admin' => 'In qualità di Admin puoi visualizzare, gestire ed eliminare i blocknotes di tutti gli utenti.',
'btn_new_blocknote' => ' Nuovo Blocknote',
'btn_import_json' => '📥 Importa JSON',
'btn_export_json' => '💾 Esporta JSON',
'btn_open_edit' => '👁️ Apri & Modifica',
'btn_rename' => '✏️ Rinomina',
'btn_delete' => '🗑️ Elimina',
'empty_blocknotes_title' => 'Nessun Blocknote trovato',
'empty_blocknotes_desc_user' => 'Non hai ancora creato nessun blocknote. Inizia subito!',
'empty_blocknotes_desc_admin' => 'Non ci sono ancora blocknotes salvati nel sistema.',
'modal_create_title' => ' Crea Nuovo Blocknote',
'modal_rename_title' => '✏️ Rinomina Blocknote',
'modal_import_title' => '📥 Importa Blocknote (JSON)',
'label_title' => 'Titolo Blocknote',
'label_file_json' => 'Carica File .json',
'label_paste_json' => 'Oppure incolla qui il codice JSON',
'btn_cancel' => 'Annulla',
'btn_save' => 'Salva',
'btn_create' => 'Crea Blocknote',
'btn_import' => 'Importa Blocknote',
'note_single' => 'Nota',
'note_plural' => 'Note',
// View Blocknote
'btn_back_list' => '⬅️ Torna ai Blocknotes',
'btn_expand_all' => '📂 Espandi Tutte',
'btn_collapse_all' => '📁 Riduci Tutte',
'btn_new_note' => ' Nuova Nota',
'btn_add_subnote' => ' Sottonota',
'subnote_single' => 'sottonota',
'subnote_plural' => 'sottonote',
'badge_text' => '📄 Testo',
'badge_command' => '⚡ Comando',
'btn_copy' => '📋 Copia',
'btn_copied' => '✓ Copiato!',
'search_placeholder' => '🔍 Cerca note, sottonote o comandi in questo blocknote...',
'btn_clear_search' => '❌ Annulla Cerca',
'search_no_results' => 'Nessun risultato trovato',
'search_no_results_desc' => 'Nessuna nota o comando corrisponde al termine cercato in questo blocknote.',
'modal_add_note' => ' Nuova Nota a Blocchi',
'modal_edit_note' => '✏️ Modifica Nota',
'modal_add_subnote' => ' Aggiungi Sottonota',
'modal_edit_subnote' => '✏️ Modifica Sottonota',
'label_note_description' => 'Descrizione (opzionale)',
'label_subnote_title' => 'Titolo Sottonota',
'label_subnote_type' => 'Tipo di Sottonota',
'label_subnote_content' => 'Contenuto',
'type_option_text' => '📄 Testo generico',
'type_option_command' => '⚡ Comando (con pulsante Copia)',
// Profile & Settings
'profile_title' => '🎨 Impostazioni Profilo & Tema',
'profile_desc' => 'Personalizza password, colori dell\'interfaccia, lingua e pagina di avvio.',
'card_password_title' => '🔒 Cambio Password',
'card_colors_title' => '🎨 Personalizza Colori Interfaccia (HSL)',
'card_lang_title' => '🌍 Lingua dell\'Interfaccia',
'card_landing_title' => '🏠 Pagina di Primo Accesso (Landing Page)',
'label_new_pass' => 'Nuova Password',
'label_confirm_pass' => 'Conferma Nuova Password',
'label_select_lang' => 'Seleziona Lingua',
'label_select_landing' => 'Seleziona Pagina di Avvio',
'landing_option_dashboard' => '📋 Pagina Principale (Tutti i Blocknotes)',
'landing_option_bn_prefix' => '📌 Apri direttamente: ',
'btn_update_pass' => '🔑 Aggiorna Password',
'btn_save_colors' => '💾 Salva Preferenze Colori',
'btn_save_lang' => '🌍 Salva Lingua',
'btn_save_landing' => '🏠 Salva Pagina di Avvio',
// Admin Users
'admin_title' => '👑 Pannello Amministrazione Utenti',
'admin_desc' => 'Crea, gestisci e modifica i ruoli e gli accessi degli utenti.',
'btn_add_user' => ' Aggiungi Nuovo Utente',
'col_username' => 'Nome Utente',
'col_role' => 'Ruolo',
'col_bns' => 'Blocknotes Creati',
'col_created' => 'Data Registrazione',
'col_actions' => 'Azioni Admin',
'role_standard' => 'Standard User',
'role_admin' => 'Administrator',
'role_owner' => '👑 Owner (Proprietario)',
'badge_owner' => '👑 Owner',
'btn_reset_pass' => '🔑 Reset Pass',
'modal_create_user' => ' Registra Nuovo Utente',
'modal_reset_pass' => '🔑 Reset Password Utente',
// Confirm Dialogs
'confirm_delete_blocknote' => 'Sei sicuro di voler eliminare questo blocknote e tutte le sue note?',
'confirm_delete_note' => 'Sei sicuro di voler eliminare questa nota e tutte le sue sottonote?',
'confirm_delete_subnote' => 'Sei sicuro di voler eliminare questa sottonota?',
'confirm_delete_user' => 'Sei sicuro di voler eliminare questo utente?',
// Placeholders
'placeholder_bn_title' => 'Es. Comandi Server Linux, Note Progetto PHP...',
'placeholder_rename_title' => 'Nuovo titolo...',
'placeholder_import_title' => 'Es. Positive Internet',
'placeholder_paste_json' => 'Incolla qui il JSON nel vecchio o nuovo formato...',
'placeholder_note_title' => 'Es. Installazione MySQL, Server Setup...',
'placeholder_note_desc' => 'Breve descrizione di questo blocco di note...',
'placeholder_subnote_title' => 'Es. Comando creazione database, Note configurazione...',
'placeholder_subnote_content' => 'Inserisci il testo o le istruzioni del comando...',
'placeholder_site_title' => 'Es. Blocknotes, Enterprise Notes...',
'placeholder_username_new' => 'es. mario, giuseppe...',
'placeholder_password_new' => 'Inserisci nuova password',
'placeholder_password_confirm' => 'Ripeti nuova password',
// Extra Labels
'label_owner_prefix' => 'Proprietario: ',
'label_updated_prefix' => 'Ultimo aggiornamento: ',
'label_you' => 'Tu',
'label_unknown' => 'Sconosciuto',
'label_current_favicon' => 'Favicon Attuale: ',
// First Start Installer Setup
'setup_title' => '🚀 Setup Iniziale Blocknotes',
'setup_subtitle' => 'Configura il database MySQL e crea i primi utenti privilegiati (Owner & Admin).',
'setup_sec1_title' => '1. Configurazione Database MySQL',
'setup_db_host' => 'Host Database',
'setup_db_name' => 'Nome Database',
'setup_db_user' => 'Utente MySQL',
'setup_db_pass' => 'Password MySQL',
'setup_db_pass_placeholder' => 'Password (lascia vuoto se nessuna)',
'setup_sec2_title' => '2. Account Owner (Proprietario Unico)',
'setup_owner_user' => 'Username Owner',
'setup_owner_pass' => 'Password Owner',
'setup_owner_pass_placeholder' => 'Inserisci password per Owner',
'setup_sec3_title' => '3. Primo Account Amministratore (Admin)',
'setup_admin_user' => 'Username Admin',
'setup_admin_pass' => 'Password Admin',
'setup_admin_pass_placeholder' => 'Inserisci password per Admin',
'setup_btn_submit' => '⚡ Completa Installazione & Configura Sistema',
'err_setup_db_fields' => 'Compila tutti i dati di connessione al Database MySQL.',
'err_setup_owner_fields' => 'Inserisci username e password per l\'utente Owner (Proprietario).',
'err_setup_admin_fields' => 'Inserisci username e password per il primo utente Admin.',
'err_setup_same_user' => 'L\'utente Owner e l\'utente Admin devono avere nomi utente differenti.'
],
'en' => [
// Login Page
'login_title' => 'Login to Blocknotes',
'login_subtitle' => 'Manage your notes and commands',
'label_username' => 'Username',
'label_password' => 'Password',
'placeholder_username' => 'Enter username',
'placeholder_password' => 'Enter password',
'btn_submit_login' => '🔑 Login',
'login_hint_footer' => 'Default first login credential',
'login_installed_success' => '🎉 Setup completed! Login with your created credentials.',
// Navbar
'nav_blocknotes' => '📓 Blocknotes',
'nav_colors_profile' => '🎨 Colors & Profile',
'nav_admin' => '👑 User Management',
'nav_logout' => '🚪 Logout',
'nav_login' => '🔑 Login',
'nav_language' => '🌐 Language',
// Dashboard (home)
'dashboard_title_user' => 'My Blocknotes',
'dashboard_title_admin' => 'All Blocknotes (Admin View)',
'dashboard_desc_user' => 'Create, organize, and view your blocknotes.',
'dashboard_desc_admin' => 'As an Admin you can view, manage, and delete blocknotes of all users.',
'btn_new_blocknote' => ' New Blocknote',
'btn_import_json' => '📥 Import JSON',
'btn_export_json' => '💾 Export JSON',
'btn_open_edit' => '👁️ Open & Edit',
'btn_rename' => '✏️ Rename',
'btn_delete' => '🗑️ Delete',
'empty_blocknotes_title' => 'No Blocknotes found',
'empty_blocknotes_desc_user' => 'You haven\'t created any blocknote yet. Start now!',
'empty_blocknotes_desc_admin' => 'There are no blocknotes saved in the system yet.',
'modal_create_title' => ' Create New Blocknote',
'modal_rename_title' => '✏️ Rename Blocknote',
'modal_import_title' => '📥 Import Blocknote (JSON)',
'label_title' => 'Blocknote Title',
'label_file_json' => 'Upload .json File',
'label_paste_json' => 'Or paste JSON code here',
'btn_cancel' => 'Cancel',
'btn_save' => 'Save',
'btn_create' => 'Create Blocknote',
'btn_import' => 'Import Blocknote',
'note_single' => 'Note',
'note_plural' => 'Notes',
// View Blocknote
'btn_back_list' => '⬅️ Back to Blocknotes',
'btn_expand_all' => '📂 Expand All',
'btn_collapse_all' => '📁 Collapse All',
'btn_new_note' => ' New Note',
'btn_add_subnote' => ' Subnote',
'subnote_single' => 'subnote',
'subnote_plural' => 'subnotes',
'badge_text' => '📄 Text',
'badge_command' => '⚡ Command',
'btn_copy' => '📋 Copy',
'btn_copied' => '✓ Copied!',
'search_placeholder' => '🔍 Search notes, subnotes, or commands in this blocknote...',
'btn_clear_search' => '❌ Clear Search',
'search_no_results' => 'No results found',
'search_no_results_desc' => 'No notes or commands match your search term in this blocknote.',
'modal_add_note' => ' New Block Note',
'modal_edit_note' => '✏️ Edit Note',
'modal_add_subnote' => ' Add Subnote',
'modal_edit_subnote' => '✏️ Edit Subnote',
'label_note_description' => 'Description (optional)',
'label_subnote_title' => 'Subnote Title',
'label_subnote_type' => 'Subnote Type',
'label_subnote_content' => 'Content',
'type_option_text' => '📄 Generic Text',
'type_option_command' => '⚡ Command (with Copy button)',
// Profile & Settings
'profile_title' => '🎨 Profile & Theme Settings',
'profile_desc' => 'Customize password, UI colors, language, and default landing page.',
'card_password_title' => '🔒 Change Password',
'card_colors_title' => '🎨 Interface Colors (HSL)',
'card_lang_title' => '🌍 Interface Language',
'card_landing_title' => '🏠 Default Landing Page',
'label_new_pass' => 'New Password',
'label_confirm_pass' => 'Confirm New Password',
'label_select_lang' => 'Select Language',
'label_select_landing' => 'Select Landing Page',
'landing_option_dashboard' => '📋 Main Selection Page (All Blocknotes)',
'landing_option_bn_prefix' => '📌 Open directly: ',
'btn_update_pass' => '🔑 Update Password',
'btn_save_colors' => '💾 Save Color Preferences',
'btn_save_lang' => '🌍 Save Language',
'btn_save_landing' => '🏠 Save Landing Page',
// Admin Users
'admin_title' => '👑 User Administration Panel',
'admin_desc' => 'Create, manage, and edit user roles and access.',
'btn_add_user' => ' Add New User',
'col_username' => 'Username',
'col_role' => 'Role',
'col_bns' => 'Blocknotes Created',
'col_created' => 'Created Date',
'col_actions' => 'Admin Actions',
'role_standard' => 'Standard User',
'role_admin' => 'Administrator',
'role_owner' => '👑 Owner',
'badge_owner' => '👑 Owner',
'btn_reset_pass' => '🔑 Reset Pass',
'modal_create_user' => ' Register New User',
'modal_reset_pass' => '🔑 Reset User Password',
'card_site_settings_title' => '⚙️ Site Settings (Owner Only)',
'label_site_title' => 'Global Application / Site Title',
'label_favicon' => 'Site Favicon (High Resolution, min 64x64px)',
'btn_save_site_title' => '💾 Save Site Title',
'btn_upload_favicon' => '📤 Upload New Favicon',
// Confirm Dialogs
'confirm_delete_blocknote' => 'Are you sure you want to delete this blocknote and all its notes?',
'confirm_delete_note' => 'Are you sure you want to delete this note and all its subnotes?',
'confirm_delete_subnote' => 'Are you sure you want to delete this subnote?',
'confirm_delete_user' => 'Are you sure you want to delete this user?',
// Placeholders
'placeholder_bn_title' => 'E.g. Linux Server Commands, PHP Notes...',
'placeholder_rename_title' => 'New title...',
'placeholder_import_title' => 'E.g. Positive Internet',
'placeholder_paste_json' => 'Paste JSON here in old or new format...',
'placeholder_note_title' => 'E.g. MySQL Installation, Server Setup...',
'placeholder_note_desc' => 'Short description of this note block...',
'placeholder_subnote_title' => 'E.g. Database creation command...',
'placeholder_subnote_content' => 'Enter text or command instructions...',
'placeholder_site_title' => 'E.g. Blocknotes, Enterprise Notes...',
'placeholder_username_new' => 'e.g. john, mary...',
'placeholder_password_new' => 'Enter new password',
'placeholder_password_confirm' => 'Repeat new password',
// Extra Labels
'label_owner_prefix' => 'Owner: ',
'label_updated_prefix' => 'Last updated: ',
'label_you' => 'You',
'label_unknown' => 'Unknown',
'label_current_favicon' => 'Current Favicon: ',
// First Start Installer Setup
'setup_title' => '🚀 Blocknotes Initial Setup',
'setup_subtitle' => 'Configure MySQL database and create the initial privileged users (Owner & Admin).',
'setup_sec1_title' => '1. MySQL Database Configuration',
'setup_db_host' => 'Database Host',
'setup_db_name' => 'Database Name',
'setup_db_user' => 'MySQL User',
'setup_db_pass' => 'MySQL Password',
'setup_db_pass_placeholder' => 'Password (leave empty if none)',
'setup_sec2_title' => '2. Owner Account (Single Owner)',
'setup_owner_user' => 'Owner Username',
'setup_owner_pass' => 'Owner Password',
'setup_owner_pass_placeholder' => 'Enter password for Owner',
'setup_sec3_title' => '3. First Admin Account',
'setup_admin_user' => 'Admin Username',
'setup_admin_pass' => 'Admin Password',
'setup_admin_pass_placeholder' => 'Enter password for Admin',
'setup_btn_submit' => '⚡ Complete Installation & Configure System',
'err_setup_db_fields' => 'Please fill in all MySQL database connection fields.',
'err_setup_owner_fields' => 'Please enter username and password for the Owner user.',
'err_setup_admin_fields' => 'Please enter username and password for the first Admin user.',
'err_setup_same_user' => 'Owner user and Admin user must have different usernames.'
],
'es' => [
// Login Page
'login_title' => 'Iniciar sesión en Blocknotes',
'login_subtitle' => 'Gestiona tus notas y comandos',
'label_username' => 'Nombre de Usuario',
'label_password' => 'Contraseña',
'placeholder_username' => 'Introduce nombre de usuario',
'placeholder_password' => 'Introduce contraseña',
'btn_submit_login' => '🔑 Iniciar Sesión',
'login_hint_footer' => 'Credencial predeterminada para el primer acceso',
'login_installed_success' => '🎉 ¡Configuración completada! Inicia sesión con tus credenciales.',
// Navbar
'nav_blocknotes' => '📓 Blocknotes',
'nav_colors_profile' => '🎨 Colores y Perfil',
'nav_admin' => '👑 Gestión de Usuarios',
'nav_logout' => '🚪 Salir',
'nav_login' => '🔑 Iniciar Sesión',
'nav_language' => '🌐 Idioma',
// Dashboard (home)
'dashboard_title_user' => 'Mis Blocknotes',
'dashboard_title_admin' => 'Todos los Blocknotes (Vista Admin)',
'dashboard_desc_user' => 'Crea, organiza y consulta tus blocknotes.',
'dashboard_desc_admin' => 'Como Admin puedes ver, gestionar y eliminar los blocknotes de todos los usuarios.',
'btn_new_blocknote' => ' Nuevo Blocknote',
'btn_import_json' => '📥 Importar JSON',
'btn_export_json' => '💾 Exportar JSON',
'btn_open_edit' => '👁️ Abrir y Editar',
'btn_rename' => '✏️ Renombrar',
'btn_delete' => '🗑️ Eliminar',
'empty_blocknotes_title' => 'No se encontraron blocknotes',
'empty_blocknotes_desc_user' => 'Aún no has creado ningún blocknote. ¡Empieza ahora!',
'empty_blocknotes_desc_admin' => 'Aún no hay blocknotes guardados en el sistema.',
'modal_create_title' => ' Crear Nuevo Blocknote',
'modal_rename_title' => '✏️ Renombrar Blocknote',
'modal_import_title' => '📥 Importar Blocknote (JSON)',
'label_title' => 'Título del Blocknote',
'label_file_json' => 'Subir archivo .json',
'label_paste_json' => 'O pega el código JSON aquí',
'btn_cancel' => 'Cancelar',
'btn_save' => 'Guardar',
'btn_create' => 'Crear Blocknote',
'btn_import' => 'Importar Blocknote',
'note_single' => 'Nota',
'note_plural' => 'Notas',
// View Blocknote
'btn_back_list' => '⬅️ Volver a Blocknotes',
'btn_expand_all' => '📂 Expandir Todas',
'btn_collapse_all' => '📁 Contraer Todas',
'btn_new_note' => ' Nueva Nota',
'btn_add_subnote' => ' Subnota',
'subnote_single' => 'subnota',
'subnote_plural' => 'subnotas',
'badge_text' => '📄 Texto',
'badge_command' => '⚡ Comando',
'btn_copy' => '📋 Copiar',
'btn_copied' => '✓ ¡Copiado!',
'search_placeholder' => '🔍 Buscar notas, subnotas o comandos...',
'btn_clear_search' => '❌ Cancelar Búsqueda',
'search_no_results' => 'No se encontraron resultados',
'search_no_results_desc' => 'Ninguna nota o comando coincide con tu búsqueda.',
'modal_add_note' => ' Nueva Nota en Bloque',
'modal_edit_note' => '✏️ Editar Nota',
'modal_add_subnote' => ' Añadir Subnota',
'modal_edit_subnote' => '✏️ Editar Subnota',
'label_note_description' => 'Descripción (opcional)',
'label_subnote_title' => 'Título de Subnota',
'label_subnote_type' => 'Tipo de Subnota',
'label_subnote_content' => 'Contenido',
'type_option_text' => '📄 Texto genérico',
'type_option_command' => '⚡ Comando (con botón Copiar)',
// Profile & Settings
'profile_title' => '🎨 Ajustes de Perfil y Tema',
'profile_desc' => 'Personaliza contraseña, colores de interfaz, idioma y página de inicio.',
'card_password_title' => '🔒 Cambiar Contraseña',
'card_colors_title' => '🎨 Colores de Interfaz (HSL)',
'card_lang_title' => '🌍 Idioma de la Interfaz',
'card_landing_title' => '🏠 Página de Inicio Predeterminada',
'label_new_pass' => 'Nueva Contraseña',
'label_confirm_pass' => 'Confirmar Nueva Contraseña',
'label_select_lang' => 'Seleccionar Idioma',
'label_select_landing' => 'Seleccionar Página de Inicio',
'landing_option_dashboard' => '📋 Página Principal (Todos los Blocknotes)',
'landing_option_bn_prefix' => '📌 Abrir directamente: ',
'btn_update_pass' => '🔑 Actualizar Contraseña',
'btn_save_colors' => '💾 Guardar Colores',
'btn_save_lang' => '🌍 Guardar Idioma',
'btn_save_landing' => '🏠 Guardar Página de Inicio',
// Admin Users
'admin_title' => '👑 Panel de Administración de Usuarios',
'admin_desc' => 'Crea, gestiona y edita roles de usuarios y accesos.',
'btn_add_user' => ' Añadir Nuevo Usuario',
'col_username' => 'Nombre de Usuario',
'col_role' => 'Rol',
'col_bns' => 'Blocknotes Creados',
'col_created' => 'Fecha de Creación',
'col_actions' => 'Acciones de Admin',
'role_standard' => 'Usuario Estándar',
'role_admin' => 'Administrador',
'role_owner' => '👑 Propietario (Owner)',
'badge_owner' => '👑 Propietario',
'btn_reset_pass' => '🔑 Reset Pass',
'modal_create_user' => ' Registrar Nuevo Usuario',
'modal_reset_pass' => '🔑 Resetear Contraseña',
'card_site_settings_title' => '⚙️ Ajustes del Sitio (Solo Propietario)',
'label_site_title' => 'Título del Sitio / Aplicación',
'label_favicon' => 'Favicon del Sitio (Alta resolución, mín 64x64px)',
'btn_save_site_title' => '💾 Guardar Título',
'btn_upload_favicon' => '📤 Subir Nueva Favicon',
// Confirm Dialogs
'confirm_delete_blocknote' => '¿Estás seguro de que quieres eliminar este blocknote y todas sus notas?',
'confirm_delete_note' => '¿Estás seguro de que quieres eliminar esta nota y todas sus subnotas?',
'confirm_delete_subnote' => '¿Estás seguro de que quieres eliminar esta subnota?',
'confirm_delete_user' => '¿Estás seguro de que quieres eliminar este usuario?',
// Placeholders
'placeholder_bn_title' => 'Ej. Comandos Servidor Linux...',
'placeholder_rename_title' => 'Nuevo título...',
'placeholder_import_title' => 'Ej. Positive Internet',
'placeholder_paste_json' => 'Pega el JSON aquí en formato antiguo o nuevo...',
'placeholder_note_title' => 'Ej. Instalación MySQL...',
'placeholder_note_desc' => 'Breve descripción de este bloque de notas...',
'placeholder_subnote_title' => 'Ej. Comando creación base de datos...',
'placeholder_subnote_content' => 'Introduce el texto o instrucciones del comando...',
'placeholder_site_title' => 'Ej. Blocknotes...',
'placeholder_username_new' => 'ej. mario, juan...',
'placeholder_password_new' => 'Introduce nueva contraseña',
'placeholder_password_confirm' => 'Repite nueva contraseña',
// Extra Labels
'label_owner_prefix' => 'Propietario: ',
'label_updated_prefix' => 'Última actualización: ',
'label_you' => 'Tú',
'label_unknown' => 'Desconocido',
'label_current_favicon' => 'Favicon Actual: ',
// First Start Installer Setup
'setup_title' => '🚀 Configuración Inicial de Blocknotes',
'setup_subtitle' => 'Configura la base de datos MySQL y crea los primeros usuarios privilegiados (Propietario y Admin).',
'setup_sec1_title' => '1. Configuración de Base de Datos MySQL',
'setup_db_host' => 'Host de Base de Datos',
'setup_db_name' => 'Nombre de la Base de Datos',
'setup_db_user' => 'Usuario MySQL',
'setup_db_pass' => 'Contraseña MySQL',
'setup_db_pass_placeholder' => 'Contraseña (deja en blanco si no hay)',
'setup_sec2_title' => '2. Cuenta de Propietario (Único)',
'setup_owner_user' => 'Nombre de Usuario Propietario',
'setup_owner_pass' => 'Contraseña de Propietario',
'setup_owner_pass_placeholder' => 'Introduce contraseña para Propietario',
'setup_sec3_title' => '3. Primera Cuenta de Administrador (Admin)',
'setup_admin_user' => 'Nombre de Usuario Admin',
'setup_admin_pass' => 'Contraseña de Admin',
'setup_admin_pass_placeholder' => 'Introduce contraseña para Admin',
'setup_btn_submit' => '⚡ Completar Instalación y Configurar Sistema',
'err_setup_db_fields' => 'Por favor completa todos los campos de conexión a la base de datos MySQL.',
'err_setup_owner_fields' => 'Por favor introduce nombre de usuario y contraseña para el Propietario.',
'err_setup_admin_fields' => 'Por favor introduce nombre de usuario y contraseña para el primer Admin.',
'err_setup_same_user' => 'El usuario Propietario y el usuario Admin deben tener nombres diferentes.'
],
'fr' => [
// Login Page
'login_title' => 'Connexion à Blocknotes',
'login_subtitle' => 'Gérez vos notes et vos commandes',
'label_username' => 'Nom d\'Utilisateur',
'label_password' => 'Mot de Passe',
'placeholder_username' => 'Entrez nom d\'utilisateur',
'placeholder_password' => 'Entrez mot de passe',
'btn_submit_login' => '🔑 Connexion',
'login_hint_footer' => 'Identifiant par défaut pour le premier accès',
'login_installed_success' => '🎉 Configuration terminée! Connectez-vous avec vos identifiants.',
// Navbar
'nav_blocknotes' => '📓 Blocknotes',
'nav_colors_profile' => '🎨 Couleurs & Profil',
'nav_admin' => '👑 Gestion des Utilisateurs',
'nav_logout' => '🚪 Déconnexion',
'nav_login' => '🔑 Connexion',
'nav_language' => '🌐 Langue',
// Dashboard (home)
'dashboard_title_user' => 'Mes Blocknotes',
'dashboard_title_admin' => 'Tous les Blocknotes (Vue Admin)',
'dashboard_desc_user' => 'Créez, organisez et consultez vos blocknotes.',
'dashboard_desc_admin' => 'En tant qu\'Admin vous pouvez afficher, gérer et supprimer les blocknotes de tous les utilisateurs.',
'btn_new_blocknote' => ' Nouveau Blocknote',
'btn_import_json' => '📥 Importer JSON',
'btn_export_json' => '💾 Exporter JSON',
'btn_open_edit' => '👁️ Ouvrir & Modifier',
'btn_rename' => '✏️ Renommer',
'btn_delete' => '🗑️ Supprimer',
'empty_blocknotes_title' => 'Aucun blocknote trouvé',
'empty_blocknotes_desc_user' => 'Vous n\'avez pas encore créé de blocknote. Commencez dès maintenant!',
'empty_blocknotes_desc_admin' => 'Il n\'y a encore aucun blocknote enregistré dans le système.',
'modal_create_title' => ' Créer un Nouveau Blocknote',
'modal_rename_title' => '✏️ Renommer le Blocknote',
'modal_import_title' => '📥 Importer Blocknote (JSON)',
'label_title' => 'Titre du Blocknote',
'label_file_json' => 'Téléverser le fichier .json',
'label_paste_json' => 'Ou collez le code JSON ici',
'btn_cancel' => 'Annuler',
'btn_save' => 'Enregistrer',
'btn_create' => 'Créer Blocknote',
'btn_import' => 'Importer Blocknote',
'note_single' => 'Note',
'note_plural' => 'Notes',
// View Blocknote
'btn_back_list' => '⬅️ Retour aux Blocknotes',
'btn_expand_all' => '📂 Tout Développer',
'btn_collapse_all' => '📁 Tout Réduire',
'btn_new_note' => ' Nouvelle Note',
'btn_add_subnote' => ' Sous-note',
'subnote_single' => 'sous-note',
'subnote_plural' => 'sous-notes',
'badge_text' => '📄 Texte',
'badge_command' => '⚡ Commande',
'btn_copy' => '📋 Copier',
'btn_copied' => '✓ Copié!',
'search_placeholder' => '🔍 Rechercher notes, sous-notes ou commandes...',
'btn_clear_search' => '❌ Annuler la Recherche',
'search_no_results' => 'Aucun résultat trouvé',
'search_no_results_desc' => 'Aucune note ou commande ne correspond à votre recherche.',
'modal_add_note' => ' Nouvelle Note par Bloc',
'modal_edit_note' => '✏️ Modifier la Note',
'modal_add_subnote' => ' Ajouter une Sous-note',
'modal_edit_subnote' => '✏️ Modifier la Sous-note',
'label_note_description' => 'Description (facultatif)',
'label_subnote_title' => 'Titre de la Sous-note',
'label_subnote_type' => 'Type de Sous-note',
'label_subnote_content' => 'Contenu',
'type_option_text' => '📄 Texte générique',
'type_option_command' => '⚡ Commande (avec bouton Copier)',
// Profile & Settings
'profile_title' => '🎨 Paramètres Profil & Thème',
'profile_desc' => 'Personnalisez votre mot de passe, couleurs, langue et page d\'accueil.',
'card_password_title' => '🔒 Changer le Mot de Passe',
'card_colors_title' => '🎨 Couleurs de l\'Interface (HSL)',
'card_lang_title' => '🌍 Langue de l\'Interface',
'card_landing_title' => '🏠 Page d\'Accueil par Défaut',
'label_new_pass' => 'Nouveau Mot de Passe',
'label_confirm_pass' => 'Confirmer le Mot de Passe',
'label_select_lang' => 'Sélectionner la Langue',
'label_select_landing' => 'Sélectionner la Page d\'Accueil',
'landing_option_dashboard' => '📋 Page Principale (Tous les Blocknotes)',
'landing_option_bn_prefix' => '📌 Ouvrir directement: ',
'btn_update_pass' => '🔑 Mettre à Jour',
'btn_save_colors' => '💾 Enregistrer les Couleurs',
'btn_save_lang' => '🌍 Enregistrer la Langue',
'btn_save_landing' => '🏠 Enregistrer la Page d\'Accueil',
// Admin Users
'admin_title' => '👑 Panneau d\'Administration Utilisateurs',
'admin_desc' => 'Créez, gérez et modifiez les rôles et accès des utilisateurs.',
'btn_add_user' => ' Ajouter un Utilisateur',
'col_username' => 'Nom d\'Utilisateur',
'col_role' => 'Rôle',
'col_bns' => 'Blocknotes Créés',
'col_created' => 'Date de Création',
'col_actions' => 'Actions Admin',
'role_standard' => 'Utilisateur Standard',
'role_admin' => 'Administrateur',
'role_owner' => '👑 Propriétaire (Owner)',
'badge_owner' => '👑 Propriétaire',
'btn_reset_pass' => '🔑 Réinit Pass',
'modal_create_user' => ' Enregistrer un Utilisateur',
'modal_reset_pass' => '🔑 Réinitialiser le Mot de Passe',
'card_site_settings_title' => '⚙️ Paramètres du Site (Propriétaire Uniquement)',
'label_site_title' => 'Titre Global du Site',
'label_favicon' => 'Favicon du Site (Haute résolution, min 64x64px)',
'btn_save_site_title' => '💾 Enregistrer le Titre',
'btn_upload_favicon' => '📤 Téléverser la Favicon',
// Confirm Dialogs
'confirm_delete_blocknote' => 'Êtes-vous sûr de vouloir supprimer ce blocknote et toutes ses notes?',
'confirm_delete_note' => 'Êtes-vous sûr de vouloir supprimer cette note et toutes ses sous-notes?',
'confirm_delete_subnote' => 'Êtes-vous sûr de vouloir supprimer cette sous-note?',
'confirm_delete_user' => 'Êtes-vous sûr de vouloir supprimer cet utilisateur?',
// Placeholders
'placeholder_bn_title' => 'Ex. Commandes Serveur Linux...',
'placeholder_rename_title' => 'Nouveau titre...',
'placeholder_import_title' => 'Ex. Positive Internet',
'placeholder_paste_json' => 'Collez le JSON ici au format ancien ou nouveau...',
'placeholder_note_title' => 'Ex. Installation MySQL...',
'placeholder_note_desc' => 'Brève description de ce bloc de notes...',
'placeholder_subnote_title' => 'Ex. Commande création base de données...',
'placeholder_subnote_content' => 'Entrez le texte ou les instructions de la commande...',
'placeholder_site_title' => 'Ex. Blocknotes...',
'placeholder_username_new' => 'ex. pierre, marie...',
'placeholder_password_new' => 'Entrez nouveau mot de passe',
'placeholder_password_confirm' => 'Répétez le mot de passe',
// Extra Labels
'label_owner_prefix' => 'Propriétaire: ',
'label_updated_prefix' => 'Dernière mise à jour: ',
'label_you' => 'Vous',
'label_unknown' => 'Inconnu',
'label_current_favicon' => 'Favicon Actuelle: ',
// First Start Installer Setup
'setup_title' => '🚀 Configuration Initiale de Blocknotes',
'setup_subtitle' => 'Configurez la base de données MySQL et créez les premiers utilisateurs privilégiés (Propriétaire et Admin).',
'setup_sec1_title' => '1. Configuration de la Base de Données MySQL',
'setup_db_host' => 'Hôte de la Base de Données',
'setup_db_name' => 'Nom de la Base de Données',
'setup_db_user' => 'Utilisateur MySQL',
'setup_db_pass' => 'Mot de Passe MySQL',
'setup_db_pass_placeholder' => 'Mot de passe (laissez vide si aucun)',
'setup_sec2_title' => '2. Compte Propriétaire (Unique)',
'setup_owner_user' => 'Nom d\'Utilisateur Propriétaire',
'setup_owner_pass' => 'Mot de Passe Propriétaire',
'setup_owner_pass_placeholder' => 'Entrez le mot de passe pour le Propriétaire',
'setup_sec3_title' => '3. Premier Compte Administrateur (Admin)',
'setup_admin_user' => 'Nom d\'Utilisateur Admin',
'setup_admin_pass' => 'Mot de Passe Admin',
'setup_admin_pass_placeholder' => 'Entrez le mot de passe pour Admin',
'setup_btn_submit' => '⚡ Terminer l\'Installation et Configurer le Système',
'err_setup_db_fields' => 'Veuillez remplir tous les champs de connexion à la base de données MySQL.',
'err_setup_owner_fields' => 'Veuillez entrer le nom d\'utilisateur et le mot de passe pour le Propriétaire.',
'err_setup_admin_fields' => 'Veuillez entrer le nom d\'utilisateur et le mot de passe pour le premier Admin.',
'err_setup_same_user' => 'L\'utilisateur Propriétaire et l\'utilisateur Admin doivent avoir des noms différents.'
]
];
}
/**
* Translate key to current active language string.
*/
function __($key, $default = '') {
$lang = get_current_lang();
$translations = get_translations();
if (isset($translations[$lang][$key])) {
return $translations[$lang][$key];
}
if (isset($translations['it'][$key])) {
return $translations['it'][$key];
}
if (isset($translations['en'][$key])) {
return $translations['en'][$key];
}
return !empty($default) ? $default : $key;
}
+89
View File
@@ -0,0 +1,89 @@
<?php
/**
* Global Site Settings & High-Resolution Favicon Management (Owner Exclusive)
*/
/**
* Get site setting value by key.
*/
function get_site_setting($key, $default = '') {
global $conn;
if (!isset($conn)) return $default;
$stmt = $conn->prepare("SELECT setting_value FROM site_settings WHERE setting_key = ? LIMIT 1");
$stmt->bind_param("s", $key);
$stmt->execute();
$res = $stmt->get_result();
if ($row = $res->fetch_assoc()) {
$stmt->close();
return $row['setting_value'];
}
$stmt->close();
return $default;
}
/**
* Update or insert site setting.
*/
function update_site_setting($key, $value) {
global $conn;
if (!isset($conn)) return false;
$stmt = $conn->prepare("INSERT INTO site_settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
$stmt->bind_param("ss", $key, $value);
$res = $stmt->execute();
$stmt->close();
return $res;
}
/**
* Upload and validate high-resolution site Favicon.
*/
function upload_site_favicon($file) {
if (empty($file) || $file['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'message' => 'Errore nel caricamento del file favicon.'];
}
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$allowed_extensions = ['png', 'ico', 'webp', 'svg', 'jpeg', 'jpg'];
if (!in_array($ext, $allowed_extensions)) {
return ['success' => false, 'message' => 'Formato favicon non supportato. Formati ammessi: PNG, ICO, WEBP, SVG, JPG.'];
}
// High resolution dimension check for raster formats (PNG, WEBP, JPG, ICO)
if (in_array($ext, ['png', 'webp', 'jpeg', 'jpg', 'ico'])) {
$image_info = @getimagesize($file['tmp_name']);
if ($image_info) {
$width = $image_info[0];
$height = $image_info[1];
// Minimum high resolution requirement: 64x64px
if ($width < 64 || $height < 64) {
return [
'success' => false,
'message' => "Risoluzione favicon troppo bassa ({$width}x{$height}px). Per garantire la massima qualità visiva, carica un'immagine ad alta risoluzione (almeno 64x64px)."
];
}
}
}
// Ensure assets/img directory exists
$img_dir = ROOT_DIR . "assets/img/";
if (!is_dir($img_dir)) {
@mkdir($img_dir, 0777, true);
}
$new_filename = "favicon_" . time() . "." . $ext;
$target_file = $img_dir . $new_filename;
$relative_path = "assets/img/" . $new_filename;
if (move_uploaded_file($file['tmp_name'], $target_file)) {
update_site_setting('site_favicon', $relative_path);
return ['success' => true, 'message' => 'Favicon aggiornata con successo!'];
}
return ['success' => false, 'message' => 'Impossibile salvare il file favicon su disco.'];
}
+270
View File
@@ -0,0 +1,270 @@
<?php
require_admin();
$msg_success = "";
$msg_error = "";
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'create_user') {
$res = admin_create_user($_POST['username'] ?? '', $_POST['password'] ?? '', $_POST['role'] ?? 'user');
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
} elseif ($action === 'delete_user') {
$res = admin_delete_user($_POST['user_id'] ?? 0);
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
} elseif ($action === 'update_role') {
$res = admin_update_user_role($_POST['user_id'] ?? 0, $_POST['role'] ?? 'user');
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
} elseif ($action === 'reset_password') {
$res = update_user_password($_POST['user_id'] ?? 0, $_POST['new_password'] ?? '');
if ($res['success']) $msg_success = "Password resettata con successo!"; else $msg_error = $res['message'];
} elseif ($action === 'update_site_title' && is_owner()) {
$new_title = trim($_POST['site_title'] ?? '');
if (!empty($new_title)) {
update_site_setting('site_title', $new_title);
$msg_success = "Titolo del sito aggiornato con successo!";
} else {
$msg_error = "Il titolo del sito non può essere vuoto.";
}
} elseif ($action === 'upload_favicon' && is_owner()) {
$res = upload_site_favicon($_FILES['favicon_file'] ?? null);
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
}
}
// Fetch all users and all blocknotes to count per user
$users_list = get_all_users();
$all_blocknotes = get_all_blocknotes();
// Map blocknote count per username
$bn_counts = [];
foreach ($all_blocknotes as $bn) {
$uname = strtolower($bn['username'] ?? '');
if ($uname) {
$bn_counts[$uname] = ($bn_counts[$uname] ?? 0) + 1;
}
}
$site_title = get_site_setting('site_title', 'sld-blocknotes');
$site_favicon = get_site_setting('site_favicon', 'assets/img/favicon.png');
?>
<main class="main-content">
<div class="page-header">
<div class="page-title-group">
<h1><?= __('admin_title') ?></h1>
<p><?= __('admin_desc') ?></p>
</div>
<div>
<button class="btn btn-primary" data-modal-target="modal-create-user">
<?= __('btn_add_user') ?>
</button>
</div>
</div>
<?php if (!empty($msg_success)): ?>
<div class="alert alert-success">
✅ <?= htmlspecialchars($msg_success) ?>
</div>
<?php endif; ?>
<?php if (!empty($msg_error)): ?>
<div class="alert alert-danger">
⚠️ <?= htmlspecialchars($msg_error) ?>
</div>
<?php endif; ?>
<?php if (is_owner()): ?>
<!-- CARD IMPOSTAZIONI SITO (ESCLUSIVO OWNER) -->
<div class="card" style="margin-bottom: 2rem; border-color: rgba(234, 179, 8, 0.3); background: rgba(234, 179, 8, 0.03);">
<div style="display: flex; align-items: center; gap: 0.6rem; margin-bottom: 1.25rem;">
<span style="font-size: 1.5rem;">👑</span>
<h2 style="font-size: 1.3rem; font-weight: 700; color: #fde047; margin: 0;"><?= __('card_site_settings_title') ?></h2>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 2rem;">
<!-- Modifica Titolo Sito -->
<form action="index.php?page=admin_users" method="POST">
<input type="hidden" name="action" value="update_site_title">
<div class="form-group">
<label for="site_title"><?= __('label_site_title') ?></label>
<input type="text" name="site_title" id="site_title" class="form-control" value="<?= htmlspecialchars($site_title) ?>" required placeholder="<?= htmlspecialchars(__('placeholder_site_title')) ?>">
</div>
<button type="submit" class="btn btn-primary" style="margin-top: 0.5rem;">
<?= __('btn_save_site_title') ?>
</button>
</form>
<!-- Caricamento Favicon -->
<form action="index.php?page=admin_users" method="POST" enctype="multipart/form-data">
<input type="hidden" name="action" value="upload_favicon">
<div class="form-group">
<label for="favicon_file"><?= __('label_favicon') ?></label>
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 0.75rem;">
<?php if (!empty($site_favicon) && file_exists(ROOT_DIR . $site_favicon)): ?>
<img src="<?= htmlspecialchars($site_favicon) ?>" alt="Favicon Preview" style="width: 42px; height: 42px; object-fit: contain; border-radius: 8px; border: 1px solid rgba(255,255,255,0.2); background: #000; padding: 4px;">
<?php endif; ?>
<span style="font-size: 0.82rem; color: #94a3b8;"><?= __('label_current_favicon') ?> <code><?= htmlspecialchars($site_favicon) ?></code></span>
</div>
<input type="file" name="favicon_file" id="favicon_file" class="form-control" accept=".png, .ico, .webp, .svg, .jpg, .jpeg" required>
</div>
<button type="submit" class="btn btn-secondary" style="margin-top: 0.5rem;">
<?= __('btn_upload_favicon') ?>
</button>
</form>
</div>
</div>
<?php endif; ?>
<!-- TABELLA UTENTI -->
<div class="card">
<div class="table-responsive">
<table class="custom-table">
<thead>
<tr>
<th>ID</th>
<th><?= __('col_username') ?></th>
<th><?= __('col_role') ?></th>
<th><?= __('col_bns') ?></th>
<th><?= __('col_created') ?></th>
<th style="text-align: right;"><?= __('col_actions') ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($users_list as $u): ?>
<?php
$uname_lower = strtolower($u['username']);
$count = $bn_counts[$uname_lower] ?? 0;
$is_self = (current_user()['id'] == $u['id']);
$is_user_owner = ($u['role'] === 'owner');
?>
<tr>
<td>#<?= (int)$u['id'] ?></td>
<td>
<strong style="color: #ffffff; font-size: 0.95rem;"><?= htmlspecialchars($u['username']) ?></strong>
<?php if ($is_self): ?>
<span style="font-size:0.75rem; color: #7dd3fc; background: rgba(14,165,233,0.15); padding: 0.15rem 0.4rem; border-radius: 4px; margin-left: 0.4rem;"><?= __('label_you') ?></span>
<?php endif; ?>
</td>
<td>
<?php if ($is_user_owner): ?>
<span class="user-badge badge-owner" style="background: rgba(234, 179, 8, 0.25); border-color: rgba(234, 179, 8, 0.5); color: #fde047;">
👑 <?= __('badge_owner') ?>
</span>
<?php else: ?>
<form action="index.php?page=admin_users" method="POST" style="display:inline-block;">
<input type="hidden" name="action" value="update_role">
<input type="hidden" name="user_id" value="<?= (int)$u['id'] ?>">
<select name="role" onchange="this.form.submit()" class="form-control" style="padding: 0.25rem 0.5rem; font-size: 0.8rem; width: auto; background: rgba(0,0,0,0.3); cursor: pointer;" <?= ($is_self || $is_user_owner) ? 'disabled' : '' ?>>
<option value="user" <?= ($u['role'] === 'user') ? 'selected' : '' ?>><?= __('role_standard') ?></option>
<option value="admin" <?= ($u['role'] === 'admin') ? 'selected' : '' ?>><?= __('role_admin') ?></option>
</select>
</form>
<?php endif; ?>
</td>
<td>
<a href="index.php?page=home&view=all" style="color: #4ade80; font-weight: 600;">
📓 <?= $count ?> Blocknotes
</a>
</td>
<td style="color: #94a3b8; font-size: 0.85rem;">
<?= date('d/m/Y H:i', strtotime($u['created_at'])) ?>
</td>
<td style="text-align: right;">
<div style="display: flex; gap: 0.4rem; justify-content: flex-end;">
<button class="btn btn-sm btn-dark" onclick="openResetPasswordModal(<?= (int)$u['id'] ?>, '<?= htmlspecialchars(addslashes($u['username'])) ?>')">
<?= __('btn_reset_pass') ?>
</button>
<?php if (!$is_self && !$is_user_owner): ?>
<form action="index.php?page=admin_users" method="POST" onsubmit="return confirm('<?= htmlspecialchars(addslashes(__('confirm_delete_user'))) ?>');" style="display:inline;">
<input type="hidden" name="action" value="delete_user">
<input type="hidden" name="user_id" value="<?= (int)$u['id'] ?>">
<button type="submit" class="btn btn-sm btn-danger" title="<?= __('btn_delete') ?>">
🗑️ <?= __('btn_delete') ?>
</button>
</form>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</main>
<!-- MODAL CREA UTENTE -->
<div class="modal-backdrop" id="modal-create-user">
<div class="modal-card">
<div class="modal-header">
<h3><?= __('modal_create_user') ?></h3>
<button class="modal-close-btn">&times;</button>
</div>
<form action="index.php?page=admin_users" method="POST">
<input type="hidden" name="action" value="create_user">
<div class="form-group">
<label for="new_username"><?= __('col_username') ?></label>
<input type="text" name="username" id="new_username" class="form-control" required placeholder="<?= htmlspecialchars(__('placeholder_username_new')) ?>">
</div>
<div class="form-group">
<label for="new_user_password"><?= __('label_password') ?></label>
<input type="password" name="password" id="new_user_password" class="form-control" required placeholder="<?= htmlspecialchars(__('placeholder_password')) ?>">
</div>
<div class="form-group">
<label for="new_role"><?= __('col_role') ?></label>
<select name="role" id="new_role" class="form-control">
<option value="user"><?= __('role_standard') ?></option>
<option value="admin"><?= __('role_admin') ?></option>
</select>
</div>
<div style="display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem;">
<button type="button" class="btn btn-dark modal-close-trigger"><?= __('btn_cancel') ?></button>
<button type="submit" class="btn btn-primary"><?= __('btn_save') ?></button>
</div>
</form>
</div>
</div>
<!-- MODAL RESET PASSWORD UTENTE -->
<div class="modal-backdrop" id="modal-reset-password">
<div class="modal-card">
<div class="modal-header">
<h3><?= __('modal_reset_pass') ?></h3>
<button class="modal-close-btn">&times;</button>
</div>
<form action="index.php?page=admin_users" method="POST">
<input type="hidden" name="action" value="reset_password">
<input type="hidden" name="user_id" id="reset_user_id">
<div class="form-group">
<label><?= __('col_username') ?></label>
<input type="text" id="reset_username_display" class="form-control" disabled style="opacity: 0.7;">
</div>
<div class="form-group">
<label for="reset_new_password"><?= __('label_new_pass') ?></label>
<input type="password" name="new_password" id="reset_new_password" class="form-control" required placeholder="<?= htmlspecialchars(__('placeholder_password_new')) ?>">
</div>
<div style="display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem;">
<button type="button" class="btn btn-dark modal-close-trigger"><?= __('btn_cancel') ?></button>
<button type="submit" class="btn btn-secondary"><?= __('btn_save') ?></button>
</div>
</form>
</div>
</div>
<script>
function openResetPasswordModal(userId, username) {
document.getElementById('reset_user_id').value = userId;
document.getElementById('reset_username_display').value = username;
document.getElementById('modal-reset-password').classList.add('active');
}
</script>
+27
View File
@@ -0,0 +1,27 @@
<?php
require_login();
$user = current_user();
$blocknote_id = $_GET['id'] ?? '';
$bn = get_blocknote_by_id($blocknote_id);
if (!$bn || !can_access_blocknote($bn, $user['username'], is_admin())) {
header("Location: index.php?page=home");
exit;
}
// Clean output buffer before outputting binary/file response
if (ob_get_level()) {
ob_end_clean();
}
$safe_title = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $bn['title']);
$filename = "blocknote_" . $safe_title . "_" . $bn['id'] . ".json";
header('Content-Type: application/json; charset=utf-8');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Pragma: no-cache');
header('Expires: 0');
echo json_encode($bn, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
+231 -4
View File
@@ -1,6 +1,233 @@
<?php
require_login();
$user = current_user();
// Check landing page redirection if default blocknote is set and user is not requesting explicitly view=all
if (!isset($_GET['view']) && !empty($user['default_blocknote'])) {
$def_bn = get_blocknote_by_id($user['default_blocknote']);
if ($def_bn && can_access_blocknote($def_bn, $user['username'], is_admin())) {
header("Location: index.php?page=view_blocknote&id=" . urlencode($user['default_blocknote']));
exit;
}
}
$msg_success = "";
$msg_error = "";
// Handle Form Submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'create_blocknote') {
$res = create_blocknote($_POST['title'] ?? '', $user['username']);
if ($res['success']) {
$msg_success = $res['message'];
} else {
$msg_error = $res['message'];
}
} elseif ($action === 'rename_blocknote') {
$res = rename_blocknote($_POST['id'] ?? '', $_POST['title'] ?? '', $user['username'], is_admin());
if ($res['success']) {
$msg_success = $res['message'];
} else {
$msg_error = $res['message'];
}
} elseif ($action === 'delete_blocknote') {
$res = delete_blocknote($_POST['id'] ?? '', $user['username'], is_admin());
if ($res['success']) {
$msg_success = $res['message'];
} else {
$msg_error = $res['message'];
}
} elseif ($action === 'import_blocknote') {
$json_content = "";
if (!empty($_FILES['json_file']['tmp_name'])) {
$json_content = file_get_contents($_FILES['json_file']['tmp_name']);
} else {
$json_content = $_POST['json_text'] ?? '';
}
$res = import_blocknote_json($json_content, $user['username'], $_POST['title'] ?? '');
if ($res['success']) {
$msg_success = $res['message'];
} else {
$msg_error = $res['message'];
}
}
}
// Fetch blocknotes
$blocknotes = get_user_blocknotes($user['username'], is_admin());
?>
<main class="main-content">
<section>
<h1>Welcome to the Home Page</h1>
<p>This is the home page.</p>
</section>
<div class="page-header">
<div class="page-title-group">
<h1>📓 <?= is_admin() ? __('dashboard_title_admin') : __('dashboard_title_user') ?></h1>
<p><?= is_admin() ? __('dashboard_desc_admin') : __('dashboard_desc_user') ?></p>
</div>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button class="btn btn-dark" data-modal-target="modal-import-blocknote">
<?= __('btn_import_json') ?>
</button>
<button class="btn btn-primary" data-modal-target="modal-create-blocknote">
<?= __('btn_new_blocknote') ?>
</button>
</div>
</div>
<?php if (!empty($msg_success)): ?>
<div class="alert alert-success">
✅ <?= htmlspecialchars($msg_success) ?>
</div>
<?php endif; ?>
<?php if (!empty($msg_error)): ?>
<div class="alert alert-danger">
⚠️ <?= htmlspecialchars($msg_error) ?>
</div>
<?php endif; ?>
<?php if (empty($blocknotes)): ?>
<div class="empty-state">
<div class="empty-state-icon">📋</div>
<h3><?= __('empty_blocknotes_title') ?></h3>
<p><?= is_admin() ? __('empty_blocknotes_desc_admin') : __('empty_blocknotes_desc_user') ?></p>
<button class="btn btn-primary" data-modal-target="modal-create-blocknote">
<?= __('btn_new_blocknote') ?>
</button>
</div>
<?php else: ?>
<div class="blocknotes-grid">
<?php foreach ($blocknotes as $bn): ?>
<?php
$note_count = count($bn['notes'] ?? []);
$is_owner = (strtolower($bn['username'] ?? '') === strtolower($user['username']));
?>
<div class="blocknote-card">
<div class="blocknote-card-header">
<h2 class="blocknote-title">
<a href="index.php?page=view_blocknote&id=<?= urlencode($bn['id']) ?>">
<?= htmlspecialchars($bn['title']) ?>
</a>
</h2>
<?php if (is_admin() || !$is_owner): ?>
<span class="blocknote-owner-tag">
👤 <?= htmlspecialchars($bn['username'] ?? __('label_unknown')) ?>
</span>
<?php endif; ?>
</div>
<div class="blocknote-card-body">
<div class="blocknote-meta">
<span>📄 <?= $note_count ?> <?= ($note_count === 1) ? __('note_single') : __('note_plural') ?></span>
<span>🕒 <?= date('d/m/Y H:i', strtotime($bn['updated_at'] ?? 'now')) ?></span>
</div>
</div>
<div class="blocknote-card-actions">
<a href="index.php?page=view_blocknote&id=<?= urlencode($bn['id']) ?>" class="btn btn-sm btn-secondary" style="flex:1; justify-content:center;">
<?= __('btn_open_edit') ?>
</a>
<a href="index.php?page=export_blocknote&id=<?= urlencode($bn['id']) ?>" class="btn btn-sm btn-dark" title="<?= __('btn_export_json') ?>">
💾
</a>
<button class="btn btn-sm btn-dark" title="<?= __('btn_rename') ?>" onclick="openRenameModal('<?= htmlspecialchars($bn['id']) ?>', '<?= htmlspecialchars(addslashes($bn['title'])) ?>')">
✏️
</button>
<form action="index.php?page=home&view=all" method="POST" onsubmit="return confirm('<?= htmlspecialchars(addslashes(__('confirm_delete_blocknote'))) ?>');" style="display:inline;">
<input type="hidden" name="action" value="delete_blocknote">
<input type="hidden" name="id" value="<?= htmlspecialchars($bn['id']) ?>">
<button type="submit" class="btn btn-sm btn-danger" title="<?= __('btn_delete') ?>">
🗑️
</button>
</form>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</main>
<!-- MODAL CREA BLOCKNOTE -->
<div class="modal-backdrop" id="modal-create-blocknote">
<div class="modal-card">
<div class="modal-header">
<h3><?= __('modal_create_title') ?></h3>
<button class="modal-close-btn">&times;</button>
</div>
<form action="index.php?page=home&view=all" method="POST">
<input type="hidden" name="action" value="create_blocknote">
<div class="form-group">
<label for="create_title"><?= __('label_title') ?></label>
<input type="text" name="title" id="create_title" class="form-control" required placeholder="<?= htmlspecialchars(__('placeholder_bn_title')) ?>">
</div>
<div style="display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem;">
<button type="button" class="btn btn-dark modal-close-trigger"><?= __('btn_cancel') ?></button>
<button type="submit" class="btn btn-primary"><?= __('btn_create') ?></button>
</div>
</form>
</div>
</div>
<!-- MODAL RINOMINA BLOCKNOTE -->
<div class="modal-backdrop" id="modal-rename-blocknote">
<div class="modal-card">
<div class="modal-header">
<h3><?= __('modal_rename_title') ?></h3>
<button class="modal-close-btn">&times;</button>
</div>
<form action="index.php?page=home&view=all" method="POST">
<input type="hidden" name="action" value="rename_blocknote">
<input type="hidden" name="id" id="rename_id">
<div class="form-group">
<label for="rename_title"><?= __('label_title') ?></label>
<input type="text" name="title" id="rename_title" class="form-control" required placeholder="<?= htmlspecialchars(__('placeholder_rename_title')) ?>">
</div>
<div style="display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem;">
<button type="button" class="btn btn-dark modal-close-trigger"><?= __('btn_cancel') ?></button>
<button type="submit" class="btn btn-secondary"><?= __('btn_save') ?></button>
</div>
</form>
</div>
</div>
<!-- MODAL IMPORTA BLOCKNOTE -->
<div class="modal-backdrop" id="modal-import-blocknote">
<div class="modal-card">
<div class="modal-header">
<h3><?= __('modal_import_title') ?></h3>
<button class="modal-close-btn">&times;</button>
</div>
<form action="index.php?page=home&view=all" method="POST" enctype="multipart/form-data">
<input type="hidden" name="action" value="import_blocknote">
<div class="form-group">
<label for="import_title"><?= __('label_title') ?></label>
<input type="text" name="title" id="import_title" class="form-control" placeholder="<?= htmlspecialchars(__('placeholder_import_title')) ?>">
</div>
<div class="form-group">
<label for="json_file"><?= __('label_file_json') ?></label>
<input type="file" name="json_file" id="json_file" class="form-control" accept=".json">
</div>
<div class="form-group">
<label for="json_text"><?= __('label_paste_json') ?></label>
<textarea name="json_text" id="json_text" class="form-control" style="font-family: monospace; min-height: 120px;" placeholder="<?= htmlspecialchars(__('placeholder_paste_json')) ?>"></textarea>
</div>
<div style="display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem;">
<button type="button" class="btn btn-dark modal-close-trigger"><?= __('btn_cancel') ?></button>
<button type="submit" class="btn btn-primary"><?= __('btn_import') ?></button>
</div>
</form>
</div>
</div>
<script>
function openRenameModal(id, currentTitle) {
document.getElementById('rename_id').value = id;
document.getElementById('rename_title').value = currentTitle;
document.getElementById('modal-rename-blocknote').classList.add('active');
}
</script>
+71
View File
@@ -0,0 +1,71 @@
<?php
if (is_logged_in()) {
header("Location: index.php?page=home");
exit;
}
$error_msg = "";
$success_msg = "";
if (isset($_GET['installed'])) {
$success_msg = __('login_installed_success');
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$res = login_user($username, $password);
if ($res['success']) {
header("Location: index.php?page=home");
exit;
} else {
$error_msg = $res['message'];
}
}
?>
<main class="main-content">
<div style="max-width: 420px; margin: 3rem auto;">
<div class="card" style="padding: 2.25rem;">
<div style="text-align: center; margin-bottom: 2rem;">
<span style="font-size: 3rem;">📝</span>
<h2 style="font-size: 1.6rem; font-weight: 700; color: #fff; margin-top: 0.5rem;"><?= __('login_title') ?></h2>
<p style="color: #94a3b8; font-size: 0.9rem; margin-top: 0.25rem;"><?= __('login_subtitle') ?></p>
</div>
<?php if (!empty($success_msg)): ?>
<div class="alert alert-success">
✅ <?= htmlspecialchars($success_msg) ?>
</div>
<?php endif; ?>
<?php if (!empty($error_msg)): ?>
<div class="alert alert-danger">
⚠️ <?= htmlspecialchars($error_msg) ?>
</div>
<?php endif; ?>
<form action="index.php?page=login" method="POST">
<div class="form-group">
<label for="username"><?= __('label_username') ?></label>
<input type="text" name="username" id="username" class="form-control" required placeholder="<?= __('placeholder_username') ?>" autofocus value="<?= htmlspecialchars($_POST['username'] ?? '') ?>">
</div>
<div class="form-group">
<label for="password"><?= __('label_password') ?></label>
<input type="password" name="password" id="password" class="form-control" required placeholder="<?= __('placeholder_password') ?>">
</div>
<button type="submit" class="btn btn-primary" style="width: 100%; justify-content: center; margin-top: 1rem; padding: 0.8rem;">
<?= __('btn_submit_login') ?>
</button>
</form>
<div style="margin-top: 1.5rem; text-align: center; padding-top: 1rem; border-top: 1px solid rgba(255,255,255,0.08); font-size: 0.82rem; color: #64748b;">
<?= __('login_hint_footer') ?>:<br>
<strong style="color: #94a3b8;"><?= __('label_username') ?>:</strong> admin | <strong style="color: #94a3b8;"><?= __('label_password') ?>:</strong> admin123
</div>
</div>
</div>
</main>
+4
View File
@@ -0,0 +1,4 @@
<?php
logout_user();
header("Location: index.php?page=login");
exit;
+235
View File
@@ -0,0 +1,235 @@
<?php
require_login();
$user = current_user();
$msg_password_success = "";
$msg_password_error = "";
$msg_theme_success = "";
$msg_theme_error = "";
$msg_lang_success = "";
$msg_lang_error = "";
$msg_landing_success = "";
$msg_landing_error = "";
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'change_password') {
$new_pass = $_POST['new_password'] ?? '';
$confirm_pass = $_POST['confirm_password'] ?? '';
if ($new_pass !== $confirm_pass) {
$msg_password_error = "Le password inserite non coincidono.";
} else {
$res = update_user_password($user['id'], $new_pass);
if ($res['success']) {
$msg_password_success = $res['message'];
} else {
$msg_password_error = $res['message'];
}
}
} elseif ($action === 'update_theme') {
$pri = $_POST['theme_pri'] ?? 98;
$sec = $_POST['theme_sec'] ?? 207;
$thi = $_POST['theme_thi'] ?? 280;
$res = update_user_theme($user['id'], $pri, $sec, $thi);
if ($res['success']) {
$msg_theme_success = $res['message'];
$user = current_user(); // Refresh session data
} else {
$msg_theme_error = $res['message'];
}
} elseif ($action === 'update_language') {
$lang = $_POST['language'] ?? 'it';
$res = update_user_language($user['id'], $lang);
if ($res['success']) {
$msg_lang_success = $res['message'];
$user = current_user();
} else {
$msg_lang_error = $res['message'];
}
} elseif ($action === 'update_landing') {
$bn_id = $_POST['default_blocknote'] ?? '';
$res = update_user_default_blocknote($user['id'], $bn_id);
if ($res['success']) {
$msg_landing_success = $res['message'];
$user = current_user();
} else {
$msg_landing_error = $res['message'];
}
}
}
$pri = $user['theme_pri'] ?? 98;
$sec = $user['theme_sec'] ?? 207;
$thi = $user['theme_thi'] ?? 280;
$current_lang = $user['language'] ?? 'it';
$current_default_bn = $user['default_blocknote'] ?? '';
$user_blocknotes = get_user_blocknotes($user['username'], is_admin());
?>
<main class="main-content">
<div class="page-header">
<div class="page-title-group">
<h1><?= __('profile_title') ?></h1>
<p><?= __('profile_desc') ?></p>
</div>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); gap: 2rem;">
<!-- CARD LINGUA INTERFACCIA -->
<div class="card">
<h2 style="font-size: 1.3rem; font-weight: 700; color: #fff; margin-bottom: 1.25rem;"><?= __('card_lang_title') ?></h2>
<?php if (!empty($msg_lang_success)): ?>
<div class="alert alert-success">✅ <?= htmlspecialchars($msg_lang_success) ?></div>
<?php endif; ?>
<?php if (!empty($msg_lang_error)): ?>
<div class="alert alert-danger">⚠️ <?= htmlspecialchars($msg_lang_error) ?></div>
<?php endif; ?>
<form action="index.php?page=profile" method="POST">
<input type="hidden" name="action" value="update_language">
<div class="form-group">
<label for="language"><?= __('label_select_lang') ?></label>
<select name="language" id="language" class="form-control">
<option value="it" <?= ($current_lang === 'it') ? 'selected' : '' ?>>🇮🇹 Italiano</option>
<option value="en" <?= ($current_lang === 'en') ? 'selected' : '' ?>>🇬🇧 English</option>
<option value="es" <?= ($current_lang === 'es') ? 'selected' : '' ?>>🇪🇸 Español</option>
<option value="fr" <?= ($current_lang === 'fr') ? 'selected' : '' ?>>🇫🇷 Français</option>
</select>
</div>
<button type="submit" class="btn btn-secondary" style="margin-top: 1rem; width: 100%; justify-content: center;">
<?= __('btn_save_lang') ?>
</button>
</form>
</div>
<!-- CARD PAGINA DI PRIMO ACCESSO (LANDING PAGE) -->
<div class="card">
<h2 style="font-size: 1.3rem; font-weight: 700; color: #fff; margin-bottom: 1.25rem;"><?= __('card_landing_title') ?></h2>
<?php if (!empty($msg_landing_success)): ?>
<div class="alert alert-success">✅ <?= htmlspecialchars($msg_landing_success) ?></div>
<?php endif; ?>
<?php if (!empty($msg_landing_error)): ?>
<div class="alert alert-danger">⚠️ <?= htmlspecialchars($msg_landing_error) ?></div>
<?php endif; ?>
<form action="index.php?page=profile" method="POST">
<input type="hidden" name="action" value="update_landing">
<div class="form-group">
<label for="default_blocknote"><?= __('label_select_landing') ?></label>
<select name="default_blocknote" id="default_blocknote" class="form-control">
<option value=""><?= __('landing_option_dashboard') ?></option>
<?php foreach ($user_blocknotes as $bn): ?>
<option value="<?= htmlspecialchars($bn['id']) ?>" <?= ($current_default_bn === $bn['id']) ? 'selected' : '' ?>>
<?= __('landing_option_bn_prefix') ?><?= htmlspecialchars($bn['title']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn btn-primary" style="margin-top: 1rem; width: 100%; justify-content: center;">
<?= __('btn_save_landing') ?>
</button>
</form>
</div>
<!-- CARD CAMBIO PASSWORD -->
<div class="card">
<h2 style="font-size: 1.3rem; font-weight: 700; color: #fff; margin-bottom: 1.25rem;"><?= __('card_password_title') ?></h2>
<?php if (!empty($msg_password_success)): ?>
<div class="alert alert-success">✅ <?= htmlspecialchars($msg_password_success) ?></div>
<?php endif; ?>
<?php if (!empty($msg_password_error)): ?>
<div class="alert alert-danger">⚠️ <?= htmlspecialchars($msg_password_error) ?></div>
<?php endif; ?>
<form action="index.php?page=profile" method="POST">
<input type="hidden" name="action" value="change_password">
<div class="form-group">
<label for="new_password"><?= __('label_new_pass') ?></label>
<input type="password" name="new_password" id="new_password" class="form-control" required placeholder="<?= htmlspecialchars(__('placeholder_password_new')) ?>">
</div>
<div class="form-group">
<label for="confirm_password"><?= __('label_confirm_pass') ?></label>
<input type="password" name="confirm_password" id="confirm_password" class="form-control" required placeholder="<?= htmlspecialchars(__('placeholder_password_confirm')) ?>">
</div>
<button type="submit" class="btn btn-primary" style="margin-top: 0.5rem; width: 100%; justify-content: center;">
<?= __('btn_update_pass') ?>
</button>
</form>
</div>
<!-- CARD PERSONALIZZAZIONE COLORI TEMA -->
<div class="card">
<h2 style="font-size: 1.3rem; font-weight: 700; color: #fff; margin-bottom: 1.25rem;"><?= __('card_colors_title') ?></h2>
<?php if (!empty($msg_theme_success)): ?>
<div class="alert alert-success">✅ <?= htmlspecialchars($msg_theme_success) ?></div>
<?php endif; ?>
<?php if (!empty($msg_theme_error)): ?>
<div class="alert alert-danger">⚠️ <?= htmlspecialchars($msg_theme_error) ?></div>
<?php endif; ?>
<form action="index.php?page=profile" method="POST">
<input type="hidden" name="action" value="update_theme">
<!-- Colore Primario -->
<div class="form-group">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:0.3rem;">
<label for="theme_pri">Primary Color (HSL Hue)</label>
<span id="pri_val" style="font-weight:700; color:var(--priCol10);"><?= (int)$pri ?>°</span>
</div>
<input type="range" min="0" max="360" name="theme_pri" id="theme_pri" value="<?= (int)$pri ?>" class="form-control" style="cursor:pointer; height: 10px;">
<div class="color-preview-box" style="background: hsl(var(--pri), 100%, 50%); margin-top: 0.5rem; font-size: 0.82rem;">
Preview Primary
</div>
</div>
<!-- Colore Secondario -->
<div class="form-group" style="margin-top: 1.5rem;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:0.3rem;">
<label for="theme_sec">Secondary Color (HSL Hue)</label>
<span id="sec_val" style="font-weight:700; color:var(--secCol10);"><?= (int)$sec ?>°</span>
</div>
<input type="range" min="0" max="360" name="theme_sec" id="theme_sec" value="<?= (int)$sec ?>" class="form-control" style="cursor:pointer; height: 10px;">
<div class="color-preview-box" style="background: hsl(var(--sec), 100%, 50%); margin-top: 0.5rem; font-size: 0.82rem; color: #fff;">
Preview Secondary
</div>
</div>
<!-- Colore Terziario -->
<div class="form-group" style="margin-top: 1.5rem;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:0.3rem;">
<label for="theme_thi">Tertiary Color (HSL Hue)</label>
<span id="thi_val" style="font-weight:700; color:var(--thiCol10);"><?= (int)$thi ?>°</span>
</div>
<input type="range" min="0" max="360" name="theme_thi" id="theme_thi" value="<?= (int)$thi ?>" class="form-control" style="cursor:pointer; height: 10px;">
<div class="color-preview-box" style="background: hsl(var(--thi), 100%, 50%); margin-top: 0.5rem; font-size: 0.82rem; color: #fff;">
Preview Tertiary
</div>
</div>
<button type="submit" class="btn btn-secondary" style="margin-top: 1.5rem; width: 100%; justify-content: center;">
<?= __('btn_save_colors') ?>
</button>
</form>
</div>
</div>
</main>
+375
View File
@@ -0,0 +1,375 @@
<?php
require_login();
$user = current_user();
$blocknote_id = $_GET['id'] ?? '';
$bn = get_blocknote_by_id($blocknote_id);
// Check access permission
if (!$bn || !can_access_blocknote($bn, $user['username'], is_admin())) {
header("Location: index.php?page=home");
exit;
}
$msg_success = "";
$msg_error = "";
// Handle Form Submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'add_note') {
$res = add_note_to_blocknote($blocknote_id, $_POST['title'] ?? '', $_POST['description'] ?? '', $user['username'], is_admin());
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
} elseif ($action === 'edit_note') {
$res = edit_note_in_blocknote($blocknote_id, $_POST['note_id'] ?? '', $_POST['title'] ?? '', $_POST['description'] ?? '', $user['username'], is_admin());
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
} elseif ($action === 'delete_note') {
$res = delete_note_from_blocknote($blocknote_id, $_POST['note_id'] ?? '', $user['username'], is_admin());
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
} elseif ($action === 'add_subnote') {
$res = add_subnote_to_note($blocknote_id, $_POST['note_id'] ?? '', $_POST['sub_title'] ?? '', $_POST['type'] ?? 'text', $_POST['content'] ?? '', $user['username'], is_admin());
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
} elseif ($action === 'edit_subnote') {
$res = edit_subnote_in_note($blocknote_id, $_POST['note_id'] ?? '', $_POST['sub_id'] ?? '', $_POST['sub_title'] ?? '', $_POST['type'] ?? 'text', $_POST['content'] ?? '', $user['username'], is_admin());
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
} elseif ($action === 'delete_subnote') {
$res = delete_subnote_from_note($blocknote_id, $_POST['note_id'] ?? '', $_POST['sub_id'] ?? '', $user['username'], is_admin());
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
}
// Refresh blocknote data
$bn = get_blocknote_by_id($blocknote_id);
}
$notes = $bn['notes'] ?? [];
?>
<main class="main-content">
<div style="margin-bottom: 1.5rem;">
<a href="index.php?page=home&view=all" class="btn btn-sm btn-dark"><?= __('btn_back_list') ?></a>
</div>
<div class="page-header" style="border-bottom: 1px solid rgba(255,255,255,0.08); padding-bottom: 1.5rem; margin-bottom: 2rem;">
<div class="page-title-group">
<h1>📓 <?= htmlspecialchars($bn['title']) ?></h1>
<p>
<?= __('label_owner_prefix') ?><strong style="color: #cbd5e1;"><?= htmlspecialchars($bn['username'] ?? '') ?></strong> &bull;
<?= __('label_updated_prefix') ?><?= date('d/m/Y H:i', strtotime($bn['updated_at'] ?? 'now')) ?>
</p>
</div>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<a href="index.php?page=export_blocknote&id=<?= urlencode($blocknote_id) ?>" class="btn btn-dark btn-sm" title="<?= __('btn_export_json') ?>">
<?= __('btn_export_json') ?>
</a>
<button class="btn btn-dark btn-sm" onclick="expandAllNotes()">
<?= __('btn_expand_all') ?>
</button>
<button class="btn btn-dark btn-sm" onclick="collapseAllNotes()">
<?= __('btn_collapse_all') ?>
</button>
<button class="btn btn-primary" data-modal-target="modal-add-note">
<?= __('btn_new_note') ?>
</button>
</div>
</div>
<?php if (!empty($msg_success)): ?>
<div class="alert alert-success">
✅ <?= htmlspecialchars($msg_success) ?>
</div>
<?php endif; ?>
<?php if (!empty($msg_error)): ?>
<div class="alert alert-danger">
⚠️ <?= htmlspecialchars($msg_error) ?>
</div>
<?php endif; ?>
<?php if (!empty($notes)): ?>
<!-- SEARCH TOOLBAR FOR CURRENT BLOCKNOTE -->
<div class="search-toolbar" style="margin-bottom: 1.5rem; display: flex; gap: 0.75rem; align-items: center;">
<div style="position: relative; flex: 1;">
<input type="text" id="note-search-input" class="form-control" placeholder="<?= __('search_placeholder') ?>" oninput="filterCurrentBlocknoteNotes(this.value)" style="padding-left: 2.5rem;">
<span style="position: absolute; left: 0.9rem; top: 50%; transform: translateY(-50%); font-size: 1rem; opacity: 0.5;">🔍</span>
</div>
<button class="btn btn-dark btn-sm" onclick="clearNoteSearch()" id="clear-search-btn" style="display: none; align-items: center; gap: 0.3rem;">
<?= __('btn_clear_search') ?>
</button>
</div>
<!-- SEARCH NO RESULTS STATE -->
<div id="no-search-results" class="empty-state" style="display: none; padding: 2.5rem 1rem;">
<div class="empty-state-icon">🔍</div>
<h3><?= __('search_no_results') ?></h3>
<p><?= __('search_no_results_desc') ?></p>
<button class="btn btn-dark btn-sm" onclick="clearNoteSearch()">
🔄 Reset
</button>
</div>
<?php endif; ?>
<?php if (empty($notes)): ?>
<div class="empty-state">
<div class="empty-state-icon">📝</div>
<h3><?= __('empty_blocknotes_title') ?></h3>
<p><?= __('empty_blocknotes_desc_user') ?></p>
<button class="btn btn-primary" data-modal-target="modal-add-note">
<?= __('btn_new_note') ?>
</button>
</div>
<?php else: ?>
<div class="notes-container">
<?php foreach ($notes as $note): ?>
<?php $subnotes = $note['subnotes'] ?? []; ?>
<div class="note-block collapsed" id="note-block-<?= htmlspecialchars($note['id']) ?>">
<div class="note-header" onclick="toggleNoteCollapse('<?= htmlspecialchars($note['id']) ?>')">
<div class="note-title-area">
<div class="note-title-wrapper">
<span class="collapse-icon">▶</span>
<h3>📌 <?= htmlspecialchars($note['title']) ?></h3>
<span class="subnote-count-badge">
<?= count($subnotes) ?> <?= (count($subnotes) === 1) ? __('subnote_single') : __('subnote_plural') ?>
</span>
</div>
</div>
<div class="note-actions" onclick="event.stopPropagation();">
<button class="btn btn-sm btn-secondary" onclick="openAddSubnoteModal('<?= htmlspecialchars($note['id']) ?>', '<?= htmlspecialchars(addslashes($note['title'])) ?>')">
<?= __('btn_add_subnote') ?>
</button>
<button class="btn btn-sm btn-dark" onclick="openEditNoteModal('<?= htmlspecialchars($note['id']) ?>', '<?= htmlspecialchars(addslashes($note['title'])) ?>', '<?= htmlspecialchars(addslashes($note['description'] ?? '')) ?>')">
✏️
</button>
<form action="index.php?page=view_blocknote&id=<?= urlencode($blocknote_id) ?>" method="POST" onsubmit="return confirm('<?= htmlspecialchars(addslashes(__('confirm_delete_note'))) ?>');" style="display:inline;">
<input type="hidden" name="action" value="delete_note">
<input type="hidden" name="note_id" value="<?= htmlspecialchars($note['id']) ?>">
<button type="submit" class="btn btn-sm btn-danger" title="<?= __('btn_delete') ?>">
🗑️
</button>
</form>
</div>
</div>
<div class="note-body">
<?php if (!empty($note['description'])): ?>
<div class="note-description-box">
<p><?= htmlspecialchars($note['description']) ?></p>
</div>
<?php endif; ?>
<div class="subnotes-list">
<?php if (empty($subnotes)): ?>
<div style="font-size: 0.88rem; color: #64748b; font-style: italic; text-align: center; padding: 1rem 0;">
<?= __('empty_blocknotes_desc_user') ?>
</div>
<?php else: ?>
<?php foreach ($subnotes as $sub): ?>
<div class="subnote-card">
<div class="subnote-header">
<div class="subnote-title-group">
<span class="subnote-title">📑 <?= htmlspecialchars($sub['title']) ?></span>
<span class="type-badge <?= htmlspecialchars($sub['type']) ?>">
<?= ($sub['type'] === 'command') ? __('badge_command') : __('badge_text') ?>
</span>
</div>
<div style="display: flex; gap: 0.4rem;">
<button class="btn btn-sm btn-dark" onclick="openEditSubnoteModal('<?= htmlspecialchars($note['id']) ?>', '<?= htmlspecialchars($sub['id']) ?>', '<?= htmlspecialchars(addslashes($sub['title'])) ?>', '<?= htmlspecialchars($sub['type']) ?>', '<?= htmlspecialchars(addslashes($sub['content'])) ?>')">
✏️
</button>
<form action="index.php?page=view_blocknote&id=<?= urlencode($blocknote_id) ?>" method="POST" onsubmit="return confirm('<?= htmlspecialchars(addslashes(__('confirm_delete_subnote'))) ?>');" style="display:inline;">
<input type="hidden" name="action" value="delete_subnote">
<input type="hidden" name="note_id" value="<?= htmlspecialchars($note['id']) ?>">
<input type="hidden" name="sub_id" value="<?= htmlspecialchars($sub['id']) ?>">
<button type="submit" class="btn btn-sm btn-danger" title="<?= __('btn_delete') ?>">
🗑️
</button>
</form>
</div>
</div>
<!-- Content rendering depending on type -->
<?php if ($sub['type'] === 'command'): ?>
<div class="command-block-wrapper">
<div class="command-block-header">
<span class="command-badge-title">⚡ <?= __('badge_command') ?></span>
<button class="copy-btn" onclick="copyToClipboard(this, <?= htmlspecialchars(json_encode($sub['content'])) ?>)" data-copy-text="<?= htmlspecialchars(__('btn_copy')) ?>" data-copied-text="<?= htmlspecialchars(__('btn_copied')) ?>">
<?= __('btn_copy') ?>
</button>
</div>
<pre class="command-content-code"><code><?= htmlspecialchars($sub['content']) ?></code></pre>
</div>
<?php else: ?>
<div class="subnote-content-text">
<?= nl2br(htmlspecialchars($sub['content'])) ?>
</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</main>
<!-- MODAL AGGIUNGI NOTA -->
<div class="modal-backdrop" id="modal-add-note">
<div class="modal-card">
<div class="modal-header">
<h3><?= __('modal_add_note') ?></h3>
<button class="modal-close-btn">&times;</button>
</div>
<form action="index.php?page=view_blocknote&id=<?= urlencode($blocknote_id) ?>" method="POST">
<input type="hidden" name="action" value="add_note">
<div class="form-group">
<label for="note_title"><?= __('label_title') ?></label>
<input type="text" name="title" id="note_title" class="form-control" required placeholder="<?= htmlspecialchars(__('placeholder_note_title')) ?>">
</div>
<div class="form-group">
<label for="note_description"><?= __('label_note_description') ?></label>
<textarea name="description" id="note_description" class="form-control" placeholder="<?= htmlspecialchars(__('placeholder_note_desc')) ?>"></textarea>
</div>
<div style="display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem;">
<button type="button" class="btn btn-dark modal-close-trigger"><?= __('btn_cancel') ?></button>
<button type="submit" class="btn btn-primary"><?= __('btn_save') ?></button>
</div>
</form>
</div>
</div>
<!-- MODAL MODIFICA NOTA -->
<div class="modal-backdrop" id="modal-edit-note">
<div class="modal-card">
<div class="modal-header">
<h3><?= __('modal_edit_note') ?></h3>
<button class="modal-close-btn">&times;</button>
</div>
<form action="index.php?page=view_blocknote&id=<?= urlencode($blocknote_id) ?>" method="POST">
<input type="hidden" name="action" value="edit_note">
<input type="hidden" name="note_id" id="edit_note_id">
<div class="form-group">
<label for="edit_note_title"><?= __('label_title') ?></label>
<input type="text" name="title" id="edit_note_title" class="form-control" required>
</div>
<div class="form-group">
<label for="edit_note_description"><?= __('label_note_description') ?></label>
<textarea name="description" id="edit_note_description" class="form-control"></textarea>
</div>
<div style="display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem;">
<button type="button" class="btn btn-dark modal-close-trigger"><?= __('btn_cancel') ?></button>
<button type="submit" class="btn btn-secondary"><?= __('btn_save') ?></button>
</div>
</form>
</div>
</div>
<!-- MODAL AGGIUNGI SOTTONOTA -->
<div class="modal-backdrop" id="modal-add-subnote">
<div class="modal-card">
<div class="modal-header">
<h3><?= __('modal_add_subnote') ?></h3>
<button class="modal-close-btn">&times;</button>
</div>
<form action="index.php?page=view_blocknote&id=<?= urlencode($blocknote_id) ?>" method="POST">
<input type="hidden" name="action" value="add_subnote">
<input type="hidden" name="note_id" id="add_subnote_parent_id">
<div class="form-group">
<label><?= __('note_single') ?></label>
<input type="text" id="add_subnote_parent_name" class="form-control" disabled style="opacity: 0.7;">
</div>
<div class="form-group">
<label for="sub_title"><?= __('label_subnote_title') ?></label>
<input type="text" name="sub_title" id="sub_title" class="form-control" required placeholder="<?= htmlspecialchars(__('placeholder_subnote_title')) ?>">
</div>
<div class="form-group">
<label for="sub_type"><?= __('label_subnote_type') ?></label>
<select name="type" id="sub_type" class="form-control">
<option value="text"><?= __('type_option_text') ?></option>
<option value="command"><?= __('type_option_command') ?></option>
</select>
</div>
<div class="form-group">
<label for="sub_content"><?= __('label_subnote_content') ?></label>
<textarea name="content" id="sub_content" class="form-control" style="min-height: 120px; font-family: monospace;" required placeholder="<?= htmlspecialchars(__('placeholder_subnote_content')) ?>"></textarea>
</div>
<div style="display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem;">
<button type="button" class="btn btn-dark modal-close-trigger"><?= __('btn_cancel') ?></button>
<button type="submit" class="btn btn-primary"><?= __('btn_save') ?></button>
</div>
</form>
</div>
</div>
<!-- MODAL MODIFICA SOTTONOTA -->
<div class="modal-backdrop" id="modal-edit-subnote">
<div class="modal-card">
<div class="modal-header">
<h3><?= __('modal_edit_subnote') ?></h3>
<button class="modal-close-btn">&times;</button>
</div>
<form action="index.php?page=view_blocknote&id=<?= urlencode($blocknote_id) ?>" method="POST">
<input type="hidden" name="action" value="edit_subnote">
<input type="hidden" name="note_id" id="edit_subnote_parent_id">
<input type="hidden" name="sub_id" id="edit_subnote_id">
<div class="form-group">
<label for="edit_sub_title"><?= __('label_subnote_title') ?></label>
<input type="text" name="sub_title" id="edit_sub_title" class="form-control" required>
</div>
<div class="form-group">
<label for="edit_sub_type"><?= __('label_subnote_type') ?></label>
<select name="type" id="edit_sub_type" class="form-control">
<option value="text"><?= __('type_option_text') ?></option>
<option value="command"><?= __('type_option_command') ?></option>
</select>
</div>
<div class="form-group">
<label for="edit_sub_content"><?= __('label_subnote_content') ?></label>
<textarea name="content" id="edit_sub_content" class="form-control" style="min-height: 120px; font-family: monospace;" required></textarea>
</div>
<div style="display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem;">
<button type="button" class="btn btn-dark modal-close-trigger"><?= __('btn_cancel') ?></button>
<button type="submit" class="btn btn-secondary"><?= __('btn_save') ?></button>
</div>
</form>
</div>
</div>
<script>
function openEditNoteModal(noteId, currentTitle, currentDesc) {
document.getElementById('edit_note_id').value = noteId;
document.getElementById('edit_note_title').value = currentTitle;
document.getElementById('edit_note_description').value = currentDesc;
document.getElementById('modal-edit-note').classList.add('active');
}
function openAddSubnoteModal(noteId, noteTitle) {
document.getElementById('add_subnote_parent_id').value = noteId;
document.getElementById('add_subnote_parent_name').value = noteTitle;
document.getElementById('modal-add-subnote').classList.add('active');
}
function openEditSubnoteModal(noteId, subId, title, type, content) {
document.getElementById('edit_subnote_parent_id').value = noteId;
document.getElementById('edit_subnote_id').value = subId;
document.getElementById('edit_sub_title').value = title;
document.getElementById('edit_sub_type').value = type;
document.getElementById('edit_sub_content').value = content;
document.getElementById('modal-edit-subnote').classList.add('active');
}
</script>
+9 -7
View File
@@ -1,9 +1,11 @@
<footer class="main-footer">
<div class="footer-credit">&copy; <year>year</year> - Company Name. All right reserved.</div>
<div class="webmaster_footer">Website designed by Simone Cusano - SimoLinuxDesign.org</div>
</footer>
<?php include(INCLUDES_FOLD . "js_footer.php"); ?>
<?php displayDebug(); ?>
</div> <!-- End .main-wrapper -->
<footer class="main-footer">
<div class="footer-container">
<div class="footer-credit">&copy; <?= date('Y') ?> - Blocknotes System. Tutti i diritti riservati.</div>
<div class="webmaster_footer">Sistema Blocknotes basato su PHP, JSON &amp; MySQL</div>
</div>
</footer>
<?php include(INCLUDES_FOLD . "js_footer.php"); ?>
<?php displayDebug(); ?>
</body>
</html>
+115 -11
View File
@@ -1,20 +1,124 @@
<?php
// Handle quick language switch if requested
if (isset($_GET['set_lang'])) {
set_active_lang($_GET['set_lang']);
$redirect_url = strtok($_SERVER["REQUEST_URI"], '?');
$get_params = $_GET;
unset($get_params['set_lang']);
if (!empty($get_params)) {
$redirect_url .= '?' . http_build_query($get_params);
}
header("Location: " . $redirect_url);
exit;
}
$current_lang = get_current_lang();
$site_title = get_site_setting('site_title', 'sld-blocknotes');
$site_favicon = get_site_setting('site_favicon', 'assets/img/favicon.png');
?>
<!DOCTYPE html>
<html lang="en">
<html lang="<?= htmlspecialchars($current_lang) ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="<?= htmlspecialchars($site_favicon) ?>">
<?php include(INCLUDES_FOLD . "head_links.php"); ?>
<title><?php echo ucfirst($GLOBALS["current_page"] ?? "Home"); ?></title>
<?php
// Inject dynamic HSL theme colors from session if set
$user = current_user();
$pri = $user['theme_pri'] ?? 98;
$sec = $user['theme_sec'] ?? 207;
$thi = $user['theme_thi'] ?? 280;
?>
<style id="dynamic-theme-vars">
:root {
--pri: <?= (int)$pri ?>;
--sec: <?= (int)$sec ?>;
--thi: <?= (int)$thi ?>;
}
</style>
<title><?php
$cp = $GLOBALS["current_page"] ?? "home";
if ($cp === 'home') echo __('dashboard_title_user');
elseif ($cp === 'view_blocknote') echo __('nav_blocknotes');
elseif ($cp === 'profile') echo __('profile_title');
elseif ($cp === 'admin_users') echo __('admin_title');
elseif ($cp === 'login') echo __('nav_login');
else echo ucfirst($cp);
?> - <?= htmlspecialchars($site_title) ?></title>
</head>
<body>
<div id="debug-target-container"></div>
<header>
<ul class="main-nav">
<li><a href="#">Home</a></li>
<li><a href="#">Page1</a></li>
<li><a href="#">Page2</a></li>
<li><a href="#">Page3</a></li>
<li><a href="#">Page4</a></li>
</ul>
</header>
<header class="navbar-header">
<div class="nav-container">
<a href="index.php?page=home&view=all" class="brand-logo">
<?php if (!empty($site_favicon) && file_exists(ROOT_DIR . $site_favicon)): ?>
<img src="<?= htmlspecialchars($site_favicon) ?>" alt="Favicon" style="width:28px; height:28px; object-fit:contain; border-radius:4px;">
<?php else: ?>
<span class="logo-icon">📝</span>
<?php endif; ?>
<span class="logo-text"><?= htmlspecialchars($site_title) ?></span>
</a>
<nav>
<ul class="main-nav">
<?php if (is_logged_in()): ?>
<li>
<a href="index.php?page=home&view=all" class="<?= ($cp === 'home' || $cp === 'view_blocknote') ? 'active' : '' ?>">
<?= __('nav_blocknotes') ?>
</a>
</li>
<li>
<a href="index.php?page=profile" class="<?= ($cp === 'profile') ? 'active' : '' ?>">
<?= __('nav_colors_profile') ?>
</a>
</li>
<?php if (is_admin()): ?>
<li class="admin-nav-item">
<a href="index.php?page=admin_users" class="admin-link <?= ($cp === 'admin_users') ? 'active' : '' ?>">
<?= __('nav_admin') ?>
</a>
</li>
<?php endif; ?>
<li class="user-meta-item">
<?php if (is_owner()): ?>
<span class="user-badge badge-owner" style="background: rgba(234, 179, 8, 0.25); border-color: rgba(234, 179, 8, 0.5); color: #fde047; box-shadow: 0 0 10px rgba(234,179,8,0.3);">
👑 <?= htmlspecialchars($user['username']) ?> (<?= __('badge_owner') ?>)
</span>
<?php else: ?>
<span class="user-badge <?= is_admin() ? 'badge-admin' : 'badge-user' ?>">
👤 <?= htmlspecialchars($user['username']) ?> (<?= is_admin() ? 'Admin' : 'User' ?>)
</span>
<?php endif; ?>
</li>
<!-- Language Quick Selector -->
<li class="lang-selector-item">
<select onchange="location.href=this.value;" style="background: rgba(0,0,0,0.3); color: #fff; border: 1px solid rgba(255,255,255,0.15); border-radius: 6px; padding: 0.3rem 0.5rem; font-size: 0.82rem; cursor: pointer;">
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'it'])) ?>" <?= ($current_lang === 'it') ? 'selected' : '' ?>>🇮🇹 IT</option>
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'en'])) ?>" <?= ($current_lang === 'en') ? 'selected' : '' ?>>🇬🇧 EN</option>
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'es'])) ?>" <?= ($current_lang === 'es') ? 'selected' : '' ?>>🇪🇸 ES</option>
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'fr'])) ?>" <?= ($current_lang === 'fr') ? 'selected' : '' ?>>🇫🇷 FR</option>
</select>
</li>
<li><a href="index.php?page=logout" class="nav-logout-btn"><?= __('nav_logout') ?></a></li>
<?php else: ?>
<!-- Guest Language Quick Selector -->
<li class="lang-selector-item">
<select onchange="location.href=this.value;" style="background: rgba(0,0,0,0.3); color: #fff; border: 1px solid rgba(255,255,255,0.15); border-radius: 6px; padding: 0.3rem 0.5rem; font-size: 0.82rem; cursor: pointer;">
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'it'])) ?>" <?= ($current_lang === 'it') ? 'selected' : '' ?>>🇮🇹 IT</option>
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'en'])) ?>" <?= ($current_lang === 'en') ? 'selected' : '' ?>>🇬🇧 EN</option>
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'es'])) ?>" <?= ($current_lang === 'es') ? 'selected' : '' ?>>🇪🇸 ES</option>
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'fr'])) ?>" <?= ($current_lang === 'fr') ? 'selected' : '' ?>>🇫🇷 FR</option>
</select>
</li>
<li><a href="index.php?page=login" class="active"><?= __('nav_login') ?></a></li>
<?php endif; ?>
</ul>
</nav>
</div>
</header>
<div class="main-wrapper">