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)) {
+95 -12
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;
}
}
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) {
// Access Deny
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 connection failed.", "High");
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.'];
}
+4 -5
View File
@@ -6,8 +6,10 @@ You don't need to specify the extension .php, the system will automatically add
*/
$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>
+112 -8
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>
<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">
<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>
<?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">