First Commit

This commit is contained in:
2026-08-19 17:26:06 +01:00
parent f782c6f531
commit 121f788c28
25 changed files with 5132 additions and 73 deletions
+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('');
}
}
+41
View File
@@ -0,0 +1,41 @@
{
"id": "bn_6a8589d13417a",
"title": "Comandi Nginx e Server",
"username": "admin",
"created_at": "2026-08-19 10:47:45",
"updated_at": "2026-08-19 10:54:03",
"notes": [
{
"id": "note_6a8589d1343d6",
"title": "Configurazione Nginx",
"description": "Comandi principali per riavviare e testare Nginx",
"subnotes": [
{
"id": "sub_6a8589d1344ba",
"title": "Riavvia Nginx",
"type": "command",
"content": "sudo systemctl restart nginx"
},
{
"id": "sub_6a8589d1344f5",
"title": "Note di Sicurezza",
"type": "text",
"content": "Assicurarsi che la porta 80 e 443 siano aperte sul firewall UFW."
}
]
},
{
"id": "note_6a858a699ba2c",
"title": "Test2",
"description": "Questa e' una nota di prova .",
"subnotes": [
{
"id": "sub_6a858aede4049",
"title": "Prova 2",
"type": "command",
"content": "Questo e' un comando!"
}
]
}
]
}
+444
View File
@@ -0,0 +1,444 @@
{
"id": "bn_positive_internet",
"title": "Positive Internet",
"username": "admin",
"created_at": "2026-08-19 10:56:45",
"updated_at": "2026-08-19 10:56:45",
"notes": [
{
"id": "note_1_6a858bed6abbd",
"title": "Login Egguburt",
"description": "Login details",
"subnotes": [
{
"id": "sub_1_6a858bed6abbf",
"title": "Eggburt",
"type": "command",
"content": "I want to Admin Positive Servers!"
},
{
"id": "sub_2_6a858bed6abc0",
"title": "Timpani",
"type": "command",
"content": "I want to administer Timpani."
},
{
"id": "sub_3_6a858bed6abc1",
"title": "## How to connect in Timpani ##",
"type": "command",
"content": "From Eggburt: ssh timpani@timpops\r\nPassword: 99flak3\r\ntimpss 'servername'"
}
]
},
{
"id": "note_2_6a858bed6abcc",
"title": "Varnish",
"description": "Varnish collection of commands",
"subnotes": [
{
"id": "sub_1_6a858bed6abcd",
"title": "Varnish Domain Clean Cache",
"type": "command",
"content": "varnishadm \"ban req.http.host ~ DOMAIN\""
},
{
"id": "sub_2_6a858bed6abce",
"title": "Check the varnish syntax command:",
"type": "command",
"content": "varnishd -C -f \/etc\/varnish\/default.vcl"
}
]
},
{
"id": "note_3_6a858bed6abcf",
"title": "Find Spam IP",
"description": "findspam commands",
"subnotes": [
{
"id": "sub_1_6a858bed6abd0",
"title": "Spam in fw-a",
"type": "command",
"content": "tail -n3000 \/var\/log\/nginx\/access.log | awk '{print $1}' | sort -nr | uniq -c | sort -nr | head -n15"
},
{
"id": "sub_2_6a858bed6abd1",
"title": "Spam in Home Dir",
"type": "command",
"content": "tail -n3000 \/home\/*\/logs\/*access*log | awk '{print $1}' | sort -nr | uniq -c | sort -nr | head -n15"
},
{
"id": "sub_3_6a858bed6abd2",
"title": "Spam in App (apache2)",
"type": "command",
"content": "tail -n3000 \/var\/log\/apache2\/access.log | awk '{print $1}' | sort -nr | uniq -c | sort -nr | head -n15"
},
{
"id": "sub_4_6a858bed6abd3",
"title": "Check all kind of logs in nginx",
"type": "command",
"content": "tail -n3000 \/var\/log\/nginx\/*access*log* | awk '{print $1}' | sort -nr | uniq -c | sort -nr | head -n15"
}
]
},
{
"id": "note_4_6a858bed6abd4",
"title": "Clean Disks Space",
"description": "Cleaning Diskspaces for each kind of VMs",
"subnotes": [
{
"id": "sub_1_6a858bed6abd5",
"title": "## Usually for App VM ##",
"type": "command",
"content": "apt clean; journalctl --vacuum-size=8M; logrotate -vf \/etc\/logrotate.d\/rsyslog; logrotate -vf \/etc\/logrotate.d\/apache2; logrotate -vf \/etc\/logrotate.d\/nginx; logrotate -vf \/etc\/logrotate.d\/varnish;"
},
{
"id": "sub_2_6a858bed6abd6",
"title": "## Usually for firewall VM ##",
"type": "command",
"content": "apt clean; journalctl --vacuum-size=8M; logrotate -vf \/etc\/logrotate.d\/rsyslog; logrotate -vf \/etc\/logrotate.d\/nginx; logrotate -vf \/etc\/logrotate.d\/varnish;"
},
{
"id": "sub_3_6a858bed6abd7",
"title": "## PHP ##",
"type": "command",
"content": "logrotate -vf \/etc\/logrotate.d\/php5.6-fpm; logrotate -vf \/etc\/logrotate.d\/php7.3-fpm; logrotate -vf \/etc\/logrotate.d\/php7.4-fpm; logrotate -vf \/etc\/logrotate.d\/php8.1-fpm; logrotate -vf \/etc\/logrotate.d\/php8.2-fpm;"
},
{
"id": "sub_4_6a858bed6abd8",
"title": "## ONLY BASIC ##",
"type": "command",
"content": "apt clean; journalctl --vacuum-size=8M; logrotate -vf \/etc\/logrotate.d\/rsyslog;"
},
{
"id": "sub_5_6a858bed6abd9",
"title": "VM + only apache2",
"type": "command",
"content": "apt clean; journalctl --vacuum-size=8M; logrotate -vf \/etc\/logrotate.d\/rsyslog; logrotate -vf \/etc\/logrotate.d\/apache2;"
}
]
},
{
"id": "note_5_6a858bed6abda",
"title": "Add findspam Script",
"description": "This is the findspam from the repository.",
"subnotes": [
{
"id": "sub_1_6a858bed6abdb",
"title": "Command:",
"type": "command",
"content": "apt install -y jq; echo \"\" && echo \"Checking the reachability of the server...\"; ping -c 2 -W 2 scripts.svm.picl.co.uk > \/dev\/null 2>&1 && echo \"Server reachable. Proceeding with script update.\" || { echo \"Server unreachable. Update canceled.\"; exit 1; }; for i in updatebspamscripts findspam blockspamip spamwhat nobanip unblockspams; do echo \"Updating script $i...\"; curl -s -o \/usr\/local\/sbin\/$i https:\/\/scripts.svm.picl.co.uk\/scripts\/$i.sh && chmod 700 \/usr\/local\/sbin\/$i && echo \"Script '$i' updated.\" || echo \"Error updating script '$i'.\"; done"
}
]
},
{
"id": "note_6_6a858bed6abdc",
"title": "Create New Database",
"description": "Command to create new database and database use",
"subnotes": [
{
"id": "sub_1_6a858bed6abdd",
"title": "## Create Databases ##",
"type": "command",
"content": "CREATE DATABASE unixuser;"
},
{
"id": "sub_2_6a858bed6abde",
"title": "# Create User For DB #",
"type": "command",
"content": "grant all privileges on `unixuser`.* to `unixuser`@'%' identified by 'password database';"
}
]
},
{
"id": "note_7_6a858bed6abdf",
"title": "Rsync From Server to the other",
"description": "Command to rsync between the servers",
"subnotes": [
{
"id": "sub_1_6a858bed6abe0",
"title": "## Rsync Command ##",
"type": "command",
"content": "rsync -avzP \/home\/user\/public_html\/ [email protected]:\/home\/user\/public_html\/"
}
]
},
{
"id": "note_8_6a858bed6abe1",
"title": "WP-CLI Commands",
"description": "Usefully commands for wp-cli. Note that you need to change \"from-website\" and \"to-website\" with the correct domain.",
"subnotes": [
{
"id": "sub_1_6a858bed6abe2",
"title": "## Export DB ##",
"type": "command",
"content": "wp db export"
},
{
"id": "sub_2_6a858bed6abe3",
"title": "## Change Links for wordpress ##",
"type": "command",
"content": "wp search-replace 'from-website.Com' 'to-website.com' --all-tables"
},
{
"id": "sub_3_6a858bed6abe4",
"title": "Add New User (change the '###*' strings with the correct informations)",
"type": "command",
"content": "wp user create ###USERNAME ###EMAIL@DOMAIN --role=administrator --user_pass=\"###PASSWORD\""
},
{
"id": "sub_4_6a858bed6abe5",
"title": "Install WP-CLI (Use this as root user)",
"type": "command",
"content": "curl -O https:\/\/raw.githubusercontent.com\/wp-cli\/builds\/gh-pages\/phar\/wp-cli.phar; chmod +x wp-cli.phar; sudo mv wp-cli.phar \/usr\/local\/bin\/wp"
},
{
"id": "sub_5_6a858bed6abe6",
"title": "Install varnish-cache plugin:",
"type": "command",
"content": "wp plugin install varnish-http-purge\r\nwp plugin activate varnish-http-purge"
},
{
"id": "sub_6_6a858bed6abe7",
"title": "Configuration to add in wp-config.php for the cache plugin",
"type": "command",
"content": "define('VHP_VARNISH_IP','varnish-vip:6081');"
},
{
"id": "sub_7_6a858bed6abe8",
"title": "(Run as Root) Checksums verify WP CLI on all \/home folders",
"type": "command",
"content": "for u in \/home\/*; do user=$(basename \"$u\"); for d in httpdocs public_html; do if [ -d \"$u\/$d\" ] && [ -f \"$u\/$d\/wp-config.php\" ]; then echo \"=== $user ($d) ===\"; sudo -u \"$user\" -H wp --path=\"$u\/$d\" core verify-checksums; echo; fi; done; done"
},
{
"id": "sub_8_6a858bed6abe9",
"title": "verify checksum for a single website",
"type": "command",
"content": "wp core verify-checksums"
},
{
"id": "sub_9_6a858bed6abea",
"title": "restore corefiles",
"type": "command",
"content": "wp core download --version=$(wp core version) --force"
},
{
"id": "sub_10_6a858bed6abeb",
"title": "Create posops user",
"type": "command",
"content": "wp user create posops [email protected] --role=administrator --user_pass='P3dsfj7nYLaMxxXk'"
},
{
"id": "sub_11_6a858bed6abec",
"title": "Cambiare la password di un utente",
"type": "command",
"content": "wp user update posops --user_pass='P3dsfj7nYLaMxxXk'"
}
]
},
{
"id": "note_9_6a858bed6abed",
"title": "Find Killig Process Error",
"description": "Command to check what has been killed in the VMs \/ Machine",
"subnotes": [
{
"id": "sub_1_6a858bed6abee",
"title": "## How to check errors ##",
"type": "command",
"content": "grep -E '(reaper|oom)' \/var\/log\/syslog"
}
]
},
{
"id": "note_10_6a858bed6abef",
"title": "Generate SSH Key",
"description": "Command to generate ssh key",
"subnotes": [
{
"id": "sub_1_6a858bed6abf0",
"title": "## ED25519 ##",
"type": "command",
"content": "ssh-keygen -t ed25519"
},
{
"id": "sub_2_6a858bed6abf1",
"title": "## RSA ##",
"type": "command",
"content": "ssh-keygen -t rsa -b 4096"
}
]
},
{
"id": "note_11_6a858bed6abf2",
"title": "User Basic Auth",
"description": "How to create \/ add user for the basicauth.",
"subnotes": [
{
"id": "sub_1_6a858bed6abf3",
"title": "## CREATE A FILE \/ USER (cancel existing one) ##",
"type": "command",
"content": "htpasswd -c \/etc\/nginx\/.htpasswd-name nuovo_utente"
},
{
"id": "sub_2_6a858bed6abf4",
"title": "## ADD EXTRA USER ##",
"type": "command",
"content": "htpasswd \/etc\/nginx\/.htpasswd-name username"
},
{
"id": "sub_3_6a858bed6abf5",
"title": "Block to add",
"type": "command",
"content": "auth_basic \"Restricted Access\";\r\n auth_basic_user_file \/etc\/nginx\/.htpasswd;"
},
{
"id": "sub_4_6a858bed6abf6",
"title": "Ownership and privileges",
"type": "command",
"content": "htpassloc=\"etc\/nginx\/.htpasswd\";\r\nchown www-data:www-data $htpassloc;\r\nchmod 644 $htpassloc"
}
]
},
{
"id": "note_12_6a858bed6abf7",
"title": "Check DB Size",
"description": "Command to check the dimension of the databases; (needs to be specified the databases names)",
"subnotes": [
{
"id": "sub_1_6a858bed6abf8",
"title": "## CHECK DB SIZE ##",
"type": "command",
"content": "SELECT table_schema AS \"Database\", ROUND(SUM(data_length + index_length) \/ 1024 \/ 1024, 2) AS \"Dimension (MB)\" FROM information_schema.tables WHERE table_schema IN (\"dbname1 \", \"dbname2\", \"dbname3\", \"dbname4\") GROUP BY table_schema;"
}
]
},
{
"id": "note_13_6a858bed6abf9",
"title": "Remove Files and keep the newest one",
"description": "Change the variable keep in base to how much newest file you need to keep!",
"subnotes": [
{
"id": "sub_1_6a858bed6abfa",
"title": "## REMOVE OLDEST FILES CODE ##",
"type": "command",
"content": "count=0; keep=4; echo \"### FILE THAT WILL BE REMOVED: ###\"; echo \"-------------------------------\"; for i in $(ls -t); do if [ $count -lt $keep ]; then echo \"Keeping: $i\"; else echo \"Removing $i\"; fi; ((count++)); done; echo \"-------------------------------\"; echo \"Do you wish to continue?\"; echo \"y - Yes\"; echo \"default - no\"; read check; check=${check,,}; if [ \"$check\" == \"y\" ]; then echo \"#### REMOVING FILE ####\"; echo \"-------------------------------\"; count=0; for i in $(ls -t); do if [ $count -lt $keep ]; then echo \"Keeping: $i\"; else echo \"Removed $i\"; rm \"$i\"; fi; ((count++)); done; echo \"-------------------------------\"; else echo \"Not Removed\"; fi\r\n"
}
]
},
{
"id": "note_14_6a858bed6abfb",
"title": "Add Diskspace Command",
"description": "After increased the diskspace of the hard disk on proxmox, we can run this command to add the 100% of the diskspace to the root partition.",
"subnotes": [
{
"id": "sub_1_6a858bed6abfc",
"title": "Command:",
"type": "command",
"content": "parted \/dev\/vda resizepart 1 100%; pvresize \/dev\/vda1; lvextend -l +100%FREE \/dev\/vgmain\/root; resize2fs \/dev\/vgmain\/root"
}
]
},
{
"id": "note_15_6a858bed6abfd",
"title": "New Certboot Certificate",
"description": "Install new certboot Certificate",
"subnotes": [
{
"id": "sub_1_6a858bed6abfe",
"title": "Command:",
"type": "command",
"content": "certbot certonly --webroot -w \/var\/www\/html -d ###DOMAIN"
}
]
},
{
"id": "note_16_6a858bed6abff",
"title": "Extract pfx key",
"description": "Use this command to extract the key and the pem files from a .pfx file.\r\nChange the variables before to run the command.",
"subnotes": [
{
"id": "sub_1_6a858bed6ac00",
"title": "Command:",
"type": "command",
"content": "PFX=\"FILE_NAME\";\r\nPASS=\"PASSWORD_PFX\";\r\nopenssl pkcs12 -in \"$PFX\" -passin pass:\"$PASS\" -nocerts -nodes -out server.key && \\\r\nopenssl pkcs12 -in \"$PFX\" -passin pass:\"$PASS\" -nokeys | \\\r\nsed -n '\/BEGIN CERTIFICATE\/,\/END CERTIFICATE\/p' > fullchain.pem\r\n"
}
]
},
{
"id": "note_17_6a858bed6ac01",
"title": "check if the ssh-key is valid",
"description": "With this command we can check if a ssh key is valid or not",
"subnotes": [
{
"id": "sub_1_6a858bed6ac02",
"title": "Command:",
"type": "command",
"content": "FILENAME=\"path\/to\/the\/file\";\r\nssh-keygen -l -f $FILENAME;\r\n"
}
]
},
{
"id": "note_18_6a858bed6ac03",
"title": "checking valid rules for studiorep-fw",
"description": "This command is checking which of the following rules has been accepted for the php-rules",
"subnotes": [
{
"id": "sub_1_6a858bed6ac04",
"title": "Command:",
"type": "command",
"content": "php-fpm8.3 -tt 2>&1 | grep -A50 \"\\[POOL_NAME\\]\""
}
]
},
{
"id": "note_19_6a858bed6ac05",
"title": "Add new VM to spa",
"description": "Use this to add the new element to the spa command. Specify the Public IP, the port and the hostname",
"subnotes": [
{
"id": "sub_1_6a858bed6ac06",
"title": "Command:",
"type": "command",
"content": "sudoedit \/home\/posops\/ansible\/hosts"
}
]
},
{
"id": "note_20_6a858bed6ac07",
"title": "PHP-FPM Scripts",
"description": "Script Collection for PHP-FPM",
"subnotes": [
{
"id": "sub_1_6a858bed6ac08",
"title": "Restarting all php-fpm existing",
"type": "command",
"content": "for i in \/etc\/php\/*; do version=$(basename \"$i\"); service=\"php${version}-fpm\"; if systemctl list-units --type=service --all | grep -q \"$service\"; then echo \"[ $service ]\"; systemctl restart \"$service\"; systemctl status \"$service\" | grep Active; fi; done"
},
{
"id": "sub_2_6a858bed6ac09",
"title": "Check the php-version for the vhost",
"type": "command",
"content": "VERSION=8.3; for i in *; do socket=$(grep -oP 'SetHandler\\s+\"proxy:unix:\\K[^|\"]+' \"$i\"); if [ -n \"$socket\" ]; then domain=$(basename \"$socket\" .sock); match=$(grep -iRl \"$domain\" \/etc\/php\/*\/fpm\/pool.d\/); if [ -n \"$match\" ]; then phpver=$(echo \"$match\" | sed -n 's|\/etc\/php\/\\([^\/]*\\)\/fpm\/.*|\\1|p' | sort -u); if echo \"$phpver\" | grep -q \"^$VERSION$\"; then echo \"[ $domain ]\"; echo \"php-fpm $phpver\"; fi; fi; fi; done"
},
{
"id": "sub_3_6a858bed6ac0a",
"title": "Check the php-version for the vhost",
"type": "command",
"content": "exver=\"7.4\" instver=\"8.4\" ; for i in $(dpkg -l *php* | awk '{print $2}' | grep $exver); do apt install php$instver${i:6} -y; done"
}
]
},
{
"id": "note_21_6a858bed6ac0b",
"title": "ownership to 755 folder and 644 files",
"description": "Changing the ownership from 755 to the folders and 644 to the files (sub-directories included) with find + xarg in the current director.",
"subnotes": [
{
"id": "sub_1_6a858bed6ac0c",
"title": "Command",
"type": "command",
"content": "find . -type d -print0 | xargs -0 chmod 755;\r\nfind . -type f -print0 | xargs -0 chmod 644"
}
]
}
]
}
+1 -2
View File
@@ -54,8 +54,7 @@
$full_path = $const_before . $const_value;
if (!is_dir($full_path) && !is_file($full_path)) {
debugStatusMes("The folder/file declared into '$const_name' constant is missing or not accessible. Please check it.", "High");
die;
@mkdir($full_path, 0777, true);
}
if (!defined($const_name)) {
+97 -14
View File
@@ -1,21 +1,104 @@
<?php
// Exceptions PHP-MSQLI
// Exceptions PHP-MYSQLI
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
// Create Connection
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
// Connections Successfully
if(DB_DEBUG == "yes"){
echo "Connected successfully";
// Check if first_start.php installer file exists and database setup is needed
$first_start_path = ROOT_DIR . "first_start.php";
if (file_exists($first_start_path) && basename($_SERVER['SCRIPT_FILENAME'] ?? '') !== 'first_start.php') {
// Check connection first
try {
$test_conn = @new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($test_conn->connect_error) {
header("Location: first_start.php");
exit;
}
$test_conn->close();
} catch (Exception $ex) {
header("Location: first_start.php");
exit;
}
} catch (mysqli_sql_exception $e) {
// Access Deny
include(PAGES_FOLD . "db-error.php");
debugStatusMes("Database connection failed.", "High");
}
die();
try {
// Create Connection to MySQL host first (in case DB doesn't exist yet)
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS);
// Create Database if not exists
$db_name_escaped = str_replace('`', '``', DB_NAME);
$conn->query("CREATE DATABASE IF NOT EXISTS `$db_name_escaped` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
$conn->select_db(DB_NAME);
// Ensure BLOCKNOTES_FOLD directory exists
if (!is_dir(BLOCKNOTES_FOLD)) {
@mkdir(BLOCKNOTES_FOLD, 0777, true);
}
// Auto-create users table if missing (with ENUM containing 'owner')
$sql_table = "CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
role ENUM('owner', 'admin', 'user') NOT NULL DEFAULT 'user',
theme_pri INT DEFAULT 98,
theme_sec INT DEFAULT 207,
theme_thi INT DEFAULT 280,
language VARCHAR(5) NOT NULL DEFAULT 'it',
default_blocknote VARCHAR(100) NOT NULL DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
$conn->query($sql_table);
// Auto-migrate role column ENUM if missing 'owner'
$conn->query("ALTER TABLE users MODIFY COLUMN role ENUM('owner', 'admin', 'user') NOT NULL DEFAULT 'user'");
// Auto-migrate columns if missing in existing table
$check_lang = $conn->query("SHOW COLUMNS FROM users LIKE 'language'");
if ($check_lang->num_rows === 0) {
$conn->query("ALTER TABLE users ADD COLUMN language VARCHAR(5) NOT NULL DEFAULT 'it'");
}
$check_def_bn = $conn->query("SHOW COLUMNS FROM users LIKE 'default_blocknote'");
if ($check_def_bn->num_rows === 0) {
$conn->query("ALTER TABLE users ADD COLUMN default_blocknote VARCHAR(100) NOT NULL DEFAULT ''");
}
// Auto-create site_settings table
$sql_settings = "CREATE TABLE IF NOT EXISTS site_settings (
setting_key VARCHAR(50) PRIMARY KEY,
setting_value TEXT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
$conn->query($sql_settings);
// Seed default site settings if missing
$conn->query("INSERT IGNORE INTO site_settings (setting_key, setting_value) VALUES ('site_title', 'Blocknotes')");
$conn->query("INSERT IGNORE INTO site_settings (setting_key, setting_value) VALUES ('site_favicon', 'assets/img/favicon.png')");
// Seed default owner account if table is empty or ensure admin user is owner if no owner exists
$res = $conn->query("SELECT COUNT(*) AS total FROM users");
$row = $res->fetch_assoc();
if ((int)$row['total'] === 0) {
$default_pass = password_hash("admin123", PASSWORD_DEFAULT);
$admin_user = "admin";
$stmt = $conn->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, 'owner')");
$stmt->bind_param("ss", $admin_user, $default_pass);
$stmt->execute();
$stmt->close();
} else {
// Check if an owner exists, if not, promote the first user to owner
$check_owner = $conn->query("SELECT COUNT(*) AS total_owners FROM users WHERE role = 'owner'");
$owner_row = $check_owner->fetch_assoc();
if ((int)$owner_row['total_owners'] === 0) {
$conn->query("UPDATE users SET role = 'owner' ORDER BY id ASC LIMIT 1");
}
}
} catch (mysqli_sql_exception $e) {
if (file_exists($first_start_path) && basename($_SERVER['SCRIPT_FILENAME'] ?? '') !== 'first_start.php') {
header("Location: first_start.php");
exit;
}
include(PAGES_FOLD . "db-error.php");
debugStatusMes("Database setup error: " . $e->getMessage(), "High");
die();
}
?>
+1 -1
View File
@@ -22,7 +22,7 @@ function get_current_page() {
$page = $_GET["page"] ?? "home";
$page = strtolower(trim($page));
$page = preg_replace("/[^a-z0-9\-]/", "", $page);
$page = preg_replace("/[^a-z0-9\-_]/", "", $page);
if ($page === "") {
$page = "home";
+7 -5
View File
@@ -5,12 +5,14 @@
switch($env){
/*------ DON'T TOUCH THIS PART ------ */
case "generic":
define('WEB_ROOT', $_SERVER['DOCUMENT_ROOT']);
define('DOMAIN', $_SERVER['HTTP_HOST']);
$doc_root = !empty($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : dirname(__DIR__);
define('WEB_ROOT', $doc_root);
$host = !empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';
define('DOMAIN', $host);
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https://' : 'http://';
define('FULL_DOMAIN', $protocol . $_SERVER['HTTP_HOST']);
define("ROOT_URL", "http://" . DOMAIN );
define("ROOT_DIR", WEB_ROOT . "/");
define('FULL_DOMAIN', $protocol . DOMAIN);
define("ROOT_URL", $protocol . DOMAIN );
define("ROOT_DIR", rtrim(WEB_ROOT, '/') . "/");
break;
/* ----------------------------------- */
default:
+6 -6
View File
@@ -1,15 +1,15 @@
<?php
// -- GENERAL SETTING --
$debug="on";
$php_debug="on";
$debug="off";
$php_debug="off";
// --- DB SETTINGS ---
$db_needed="no";
$db_needed="yes";
$db_debug="yes";
$db_host="localhost";
$db_name="database-name";
$db_user="database-user";
$db_pass="database-user-password";
$db_name="blocknotes_db";
$db_user="simone";
$db_pass="Angela";
//-------------------------------------------------------------------------------------
// --- 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'] ?? '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', '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') ?> - 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'] ?? 'blocknotes_db') ?>" required>
</div>
</div>
<div class="grid-2">
<div class="form-group">
<label for="db_user"><?= __('setup_db_user') ?></label>
<input type="text" name="db_user" id="db_user" class="form-control" value="<?= htmlspecialchars($_POST['db_user'] ?? 'simone') ?>" required>
</div>
<div class="form-group">
<label for="db_pass"><?= __('setup_db_pass') ?></label>
<input type="password" name="db_pass" id="db_pass" class="form-control" placeholder="<?= htmlspecialchars(__('setup_db_pass_placeholder')) ?>">
</div>
</div>
<!-- SEC 2: OWNER ACCOUNT -->
<div class="form-section-title" style="color: #fde047;">
<span>👑</span> <?= __('setup_sec2_title') ?>
</div>
<div class="grid-2">
<div class="form-group">
<label for="owner_user"><?= __('setup_owner_user') ?></label>
<input type="text" name="owner_user" id="owner_user" class="form-control" value="<?= htmlspecialchars($_POST['owner_user'] ?? 'owner') ?>" required placeholder="owner">
</div>
<div class="form-group">
<label for="owner_pass"><?= __('setup_owner_pass') ?></label>
<input type="password" name="owner_pass" id="owner_pass" class="form-control" required placeholder="<?= htmlspecialchars(__('setup_owner_pass_placeholder')) ?>">
</div>
</div>
<!-- SEC 3: ADMIN ACCOUNT -->
<div class="form-section-title" style="color: #a7f3d0;">
<span>🛡️</span> <?= __('setup_sec3_title') ?>
</div>
<div class="grid-2">
<div class="form-group">
<label for="admin_user"><?= __('setup_admin_user') ?></label>
<input type="text" name="admin_user" id="admin_user" class="form-control" value="<?= htmlspecialchars($_POST['admin_user'] ?? 'admin') ?>" required placeholder="admin">
</div>
<div class="form-group">
<label for="admin_pass"><?= __('setup_admin_pass') ?></label>
<input type="password" name="admin_pass" id="admin_pass" class="form-control" required placeholder="<?= htmlspecialchars(__('setup_admin_pass_placeholder')) ?>">
</div>
</div>
<div style="margin-top: 2rem;">
<button type="submit" class="btn btn-primary" style="width: 100%; justify-content: center; padding: 0.9rem; font-size: 1.05rem; font-weight: 700;">
<?= __('setup_btn_submit') ?>
</button>
</div>
</form>
</div>
</body>
</html>
+314
View File
@@ -0,0 +1,314 @@
<?php
/**
* Authentication and User Management Helper Functions
*/
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
/**
* Check if a user is logged in.
*/
function is_logged_in() {
return isset($_SESSION['user']) && !empty($_SESSION['user']['id']);
}
/**
* Get current logged in user details.
*/
function current_user() {
return $_SESSION['user'] ?? null;
}
/**
* Check if logged in user is owner.
*/
function is_owner() {
$user = current_user();
return $user && ($user['role'] === 'owner');
}
/**
* Check if logged in user is admin (or owner).
*/
function is_admin() {
$user = current_user();
return $user && ($user['role'] === 'admin' || $user['role'] === 'owner');
}
/**
* Require login to access a page.
*/
function require_login() {
if (!is_logged_in()) {
header("Location: index.php?page=login");
exit;
}
}
/**
* Require admin role to access a page.
*/
function require_admin() {
require_login();
if (!is_admin()) {
header("Location: index.php?page=home");
exit;
}
}
/**
* Authenticate user with username and password.
*/
function login_user($username, $password) {
global $conn;
$username = trim($username);
if (empty($username) || empty($password)) {
return ['success' => false, 'message' => 'Inserisci sia username che password.'];
}
$stmt = $conn->prepare("SELECT id, username, password, role, theme_pri, theme_sec, theme_thi, language, default_blocknote FROM users WHERE username = ? LIMIT 1");
$stmt->bind_param("s", $username);
$stmt->execute();
$result = $stmt->get_result();
if ($row = $result->fetch_assoc()) {
if (password_verify($password, $row['password'])) {
$_SESSION['user'] = [
'id' => $row['id'],
'username' => $row['username'],
'role' => $row['role'],
'theme_pri' => $row['theme_pri'] ?? 98,
'theme_sec' => $row['theme_sec'] ?? 207,
'theme_thi' => $row['theme_thi'] ?? 280,
'language' => $row['language'] ?? 'it',
'default_blocknote' => $row['default_blocknote'] ?? ''
];
$stmt->close();
return ['success' => true];
}
}
$stmt->close();
return ['success' => false, 'message' => 'Username o password errati.'];
}
/**
* Logout current user.
*/
function logout_user() {
$_SESSION = array();
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
session_destroy();
}
/**
* Update user password.
*/
function update_user_password($user_id, $new_password) {
global $conn;
if (strlen($new_password) < 4) {
return ['success' => false, 'message' => 'La password deve contenere almeno 4 caratteri.'];
}
$hash = password_hash($new_password, PASSWORD_DEFAULT);
$stmt = $conn->prepare("UPDATE users SET password = ? WHERE id = ?");
$stmt->bind_param("si", $hash, $user_id);
$res = $stmt->execute();
$stmt->close();
return $res ? ['success' => true, 'message' => 'Password aggiornata con successo!'] : ['success' => false, 'message' => 'Errore durante il salvataggio della password.'];
}
/**
* Update user language preference.
*/
function update_user_language($user_id, $lang) {
global $conn;
if (!in_array($lang, ['it', 'en', 'es', 'fr'])) {
$lang = 'it';
}
$stmt = $conn->prepare("UPDATE users SET language = ? WHERE id = ?");
$stmt->bind_param("si", $lang, $user_id);
$res = $stmt->execute();
$stmt->close();
if ($res) {
if (isset($_SESSION['user']) && $_SESSION['user']['id'] == $user_id) {
$_SESSION['user']['language'] = $lang;
}
return ['success' => true, 'message' => 'Lingua aggiornata con successo!'];
}
return ['success' => false, 'message' => 'Errore durante l\'aggiornamento della lingua.'];
}
/**
* Update user default landing blocknote preference.
*/
function update_user_default_blocknote($user_id, $bn_id) {
global $conn;
$bn_id = trim($bn_id);
$stmt = $conn->prepare("UPDATE users SET default_blocknote = ? WHERE id = ?");
$stmt->bind_param("si", $bn_id, $user_id);
$res = $stmt->execute();
$stmt->close();
if ($res) {
if (isset($_SESSION['user']) && $_SESSION['user']['id'] == $user_id) {
$_SESSION['user']['default_blocknote'] = $bn_id;
}
return ['success' => true, 'message' => 'Pagina di avvio aggiornata con successo!'];
}
return ['success' => false, 'message' => 'Errore durante l\'aggiornamento della pagina di avvio.'];
}
/**
* Update user interface theme colors (HSL).
*/
function update_user_theme($user_id, $pri, $sec, $thi) {
global $conn;
$pri = max(0, min(360, (int)$pri));
$sec = max(0, min(360, (int)$sec));
$thi = max(0, min(360, (int)$thi));
$stmt = $conn->prepare("UPDATE users SET theme_pri = ?, theme_sec = ?, theme_thi = ? WHERE id = ?");
$stmt->bind_param("iiii", $pri, $sec, $thi, $user_id);
$res = $stmt->execute();
$stmt->close();
if ($res) {
if (isset($_SESSION['user']) && $_SESSION['user']['id'] == $user_id) {
$_SESSION['user']['theme_pri'] = $pri;
$_SESSION['user']['theme_sec'] = $sec;
$_SESSION['user']['theme_thi'] = $thi;
}
return ['success' => true, 'message' => 'Colori dell\'interfaccia aggiornati!'];
}
return ['success' => false, 'message' => 'Errore durante l\'aggiornamento dei colori.'];
}
/**
* Admin: Get list of all registered users.
*/
function get_all_users() {
global $conn;
$result = $conn->query("SELECT id, username, role, created_at FROM users ORDER BY id ASC");
$users = [];
while ($row = $result->fetch_assoc()) {
$users[] = $row;
}
return $users;
}
/**
* Admin: Register a new user.
*/
function admin_create_user($username, $password, $role = 'user') {
global $conn;
$username = trim($username);
if (empty($username) || empty($password)) {
return ['success' => false, 'message' => 'Username e password sono obbligatori.'];
}
if (!in_array($role, ['user', 'admin', 'owner'])) {
$role = 'user';
}
if ($role === 'owner') {
$check_owner = $conn->query("SELECT id FROM users WHERE role = 'owner'");
if ($check_owner->num_rows > 0) {
return ['success' => false, 'message' => 'Può esistere un solo account Owner nel sistema!'];
}
}
// Check if exists
$stmt = $conn->prepare("SELECT id FROM users WHERE username = ? LIMIT 1");
$stmt->bind_param("s", $username);
$stmt->execute();
if ($stmt->get_result()->num_rows > 0) {
$stmt->close();
return ['success' => false, 'message' => 'Un utente con questo username esiste già.'];
}
$stmt->close();
$hash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $conn->prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $username, $hash, $role);
$res = $stmt->execute();
$stmt->close();
return $res ? ['success' => true, 'message' => 'Utente creato con successo!'] : ['success' => false, 'message' => 'Impossibile creare l\'utente.'];
}
/**
* Admin: Delete a user by ID.
*/
function admin_delete_user($user_id) {
global $conn;
$user_id = (int)$user_id;
// Prevent self-deletion
if (current_user() && current_user()['id'] == $user_id) {
return ['success' => false, 'message' => 'Non puoi eliminare il tuo stesso account!'];
}
// Check target user role - Owner can NEVER be deleted
$stmt = $conn->prepare("SELECT role FROM users WHERE id = ? LIMIT 1");
$stmt->bind_param("i", $user_id);
$stmt->execute();
$target_user = $stmt->get_result()->fetch_assoc();
$stmt->close();
if ($target_user && $target_user['role'] === 'owner') {
return ['success' => false, 'message' => 'L\'account Owner non può essere eliminato da nessuno!'];
}
$stmt = $conn->prepare("DELETE FROM users WHERE id = ?");
$stmt->bind_param("i", $user_id);
$res = $stmt->execute();
$stmt->close();
return $res ? ['success' => true, 'message' => 'Utente eliminato.'] : ['success' => false, 'message' => 'Errore nell\'eliminazione dell\'utente.'];
}
/**
* Admin: Update user role.
*/
function admin_update_user_role($user_id, $role) {
global $conn;
$user_id = (int)$user_id;
if (!in_array($role, ['admin', 'user', 'owner'])) return ['success' => false, 'message' => 'Ruolo non valido.'];
// Check target user role - Owner role cannot be changed
$stmt = $conn->prepare("SELECT role FROM users WHERE id = ? LIMIT 1");
$stmt->bind_param("i", $user_id);
$stmt->execute();
$target_user = $stmt->get_result()->fetch_assoc();
$stmt->close();
if ($target_user && $target_user['role'] === 'owner') {
return ['success' => false, 'message' => 'Il ruolo dell\'account Owner non può essere modificato!'];
}
if ($role === 'owner') {
$check_owner = $conn->query("SELECT id FROM users WHERE role = 'owner'");
if ($check_owner->num_rows > 0) {
return ['success' => false, 'message' => 'Può esistere un solo account Owner nel sistema!'];
}
}
$stmt = $conn->prepare("UPDATE users SET role = ? WHERE id = ?");
$stmt->bind_param("si", $role, $user_id);
$res = $stmt->execute();
$stmt->close();
return $res ? ['success' => true, 'message' => 'Ruolo aggiornato.'] : ['success' => false, 'message' => 'Impossibile aggiornare il ruolo.'];
}
+421
View File
@@ -0,0 +1,421 @@
<?php
/**
* Blocknotes JSON Storage Engine Functions
*/
/**
* Ensure storage directory exists.
*/
function ensure_blocknotes_directory() {
if (!is_dir(BLOCKNOTES_FOLD)) {
@mkdir(BLOCKNOTES_FOLD, 0777, true);
}
}
/**
* Scan BLOCKNOTES_FOLD and load all JSON files.
*/
function get_all_blocknotes() {
ensure_blocknotes_directory();
$files = glob(BLOCKNOTES_FOLD . "*.json");
$blocknotes = [];
if ($files) {
foreach ($files as $filepath) {
$content = file_get_contents($filepath);
$data = json_decode($content, true);
if (is_array($data) && isset($data['id'])) {
$blocknotes[] = $data;
}
}
}
// Sort by updated_at descending
usort($blocknotes, function($a, $b) {
return strtotime($b['updated_at'] ?? 0) - strtotime($a['updated_at'] ?? 0);
});
return $blocknotes;
}
/**
* Get blocknotes assigned to a specific user (or all if admin).
*/
function get_user_blocknotes($username, $is_admin = false) {
$all = get_all_blocknotes();
if ($is_admin) {
return $all;
}
return array_filter($all, function($bn) use ($username) {
return isset($bn['username']) && strtolower($bn['username']) === strtolower($username);
});
}
/**
* Find blocknote by ID.
*/
function get_blocknote_by_id($id) {
ensure_blocknotes_directory();
$filename = BLOCKNOTES_FOLD . preg_replace('/[^a-zA-Z0-9_\-]/', '', $id) . ".json";
if (file_exists($filename)) {
$content = file_get_contents($filename);
$data = json_decode($content, true);
if (is_array($data)) return $data;
}
// Fallback search across all files if ID is stored inside
$all = get_all_blocknotes();
foreach ($all as $bn) {
if (isset($bn['id']) && $bn['id'] === $id) {
return $bn;
}
}
return null;
}
/**
* Save blocknote to JSON file.
*/
function save_blocknote($data) {
ensure_blocknotes_directory();
if (empty($data['id'])) {
$data['id'] = 'bn_' . uniqid();
}
$data['updated_at'] = date('Y-m-d H:i:s');
if (empty($data['created_at'])) {
$data['created_at'] = $data['updated_at'];
}
if (!isset($data['notes'])) {
$data['notes'] = [];
}
$filepath = BLOCKNOTES_FOLD . preg_replace('/[^a-zA-Z0-9_\-]/', '', $data['id']) . ".json";
$json_string = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
return file_put_contents($filepath, $json_string) !== false;
}
/**
* Create a new blocknote.
*/
function create_blocknote($title, $username) {
$title = trim($title);
if (empty($title)) {
return ['success' => false, 'message' => 'Il titolo del blocknote è obbligatorio.'];
}
$id = 'bn_' . uniqid();
$data = [
'id' => $id,
'title' => $title,
'username' => $username,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
'notes' => []
];
if (save_blocknote($data)) {
return ['success' => true, 'id' => $id, 'message' => 'Blocknote creato con successo!'];
}
return ['success' => false, 'message' => 'Errore nel salvataggio del file JSON.'];
}
/**
* Import a JSON string (supports standard format and legacy codes format).
*/
function import_blocknote_json($json_string, $username, $custom_title = '') {
$data = json_decode($json_string, true);
if (!is_array($data)) {
return ['success' => false, 'message' => 'File JSON non valido o malformato.'];
}
$id = 'bn_' . uniqid();
$title = !empty($custom_title) ? trim($custom_title) : 'Blocknote Importato';
// Case 1: Standard format with 'notes' array
if (isset($data['notes']) && is_array($data['notes'])) {
$bn = [
'id' => $id,
'title' => !empty($data['title']) ? $data['title'] : $title,
'username' => $username,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
'notes' => $data['notes']
];
if (save_blocknote($bn)) {
return ['success' => true, 'id' => $id, 'message' => 'Blocknote importato con successo!'];
}
}
// Case 2: Legacy format (array of notes with 'title', 'description', 'codes')
if (isset($data[0]) && is_array($data[0])) {
$notes = [];
foreach ($data as $n_idx => $legacy_note) {
$note_id = 'note_' . ($n_idx + 1) . '_' . uniqid();
$subnotes = [];
if (isset($legacy_note['codes']) && is_array($legacy_note['codes'])) {
foreach ($legacy_note['codes'] as $s_idx => $legacy_code) {
$subnotes[] = [
'id' => 'sub_' . ($s_idx + 1) . '_' . uniqid(),
'title' => $legacy_code['sub_title'] ?? 'Comando',
'type' => 'command',
'content' => $legacy_code['code'] ?? ''
];
}
}
$notes[] = [
'id' => $note_id,
'title' => $legacy_note['title'] ?? 'Nota ' . ($n_idx + 1),
'description' => $legacy_note['description'] ?? '',
'subnotes' => $subnotes
];
}
$bn = [
'id' => $id,
'title' => $title,
'username' => $username,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
'notes' => $notes
];
if (save_blocknote($bn)) {
return ['success' => true, 'id' => $id, 'message' => 'Blocknote in formato legacy importato e convertito con successo!'];
}
}
return ['success' => false, 'message' => 'Formato JSON non riconosciuto.'];
}
/**
* Check permission on blocknote.
*/
function can_access_blocknote($bn, $username, $is_admin) {
if (!$bn) return false;
if ($is_admin) return true;
return strtolower($bn['username'] ?? '') === strtolower($username);
}
/**
* Rename a blocknote.
*/
function rename_blocknote($id, $new_title, $username, $is_admin) {
$bn = get_blocknote_by_id($id);
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
if (!can_access_blocknote($bn, $username, $is_admin)) {
return ['success' => false, 'message' => 'Non hai i permessi per modificare questo blocknote.'];
}
$new_title = trim($new_title);
if (empty($new_title)) return ['success' => false, 'message' => 'Il titolo non può essere vuoto.'];
$bn['title'] = $new_title;
if (save_blocknote($bn)) {
return ['success' => true, 'message' => 'Blocknote rinominato con successo.'];
}
return ['success' => false, 'message' => 'Errore nel salvataggio del file JSON.'];
}
/**
* Delete a blocknote.
*/
function delete_blocknote($id, $username, $is_admin) {
$bn = get_blocknote_by_id($id);
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
if (!can_access_blocknote($bn, $username, $is_admin)) {
return ['success' => false, 'message' => 'Non hai i permessi per eliminare questo blocknote.'];
}
$filepath = BLOCKNOTES_FOLD . preg_replace('/[^a-zA-Z0-9_\-]/', '', $bn['id']) . ".json";
if (file_exists($filepath)) {
@unlink($filepath);
}
return ['success' => true, 'message' => 'Blocknote eliminato con successo.'];
}
/**
* Add a new Note block to a Blocknote.
*/
function add_note($blocknote_id, $title, $description, $username, $is_admin) {
$bn = get_blocknote_by_id($blocknote_id);
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
if (!can_access_blocknote($bn, $username, $is_admin)) return ['success' => false, 'message' => 'Permesso negato.'];
$title = trim($title);
if (empty($title)) return ['success' => false, 'message' => 'Il titolo della nota è obbligatorio.'];
$note_id = 'note_' . uniqid();
$new_note = [
'id' => $note_id,
'title' => $title,
'description' => trim($description),
'subnotes' => []
];
$bn['notes'][] = $new_note;
if (save_blocknote($bn)) {
return ['success' => true, 'note_id' => $note_id, 'message' => 'Nota aggiunta con successo!'];
}
return ['success' => false, 'message' => 'Errore durante il salvataggio.'];
}
/**
* Edit an existing Note block.
*/
function edit_note($blocknote_id, $note_id, $title, $description, $username, $is_admin) {
$bn = get_blocknote_by_id($blocknote_id);
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
if (!can_access_blocknote($bn, $username, $is_admin)) return ['success' => false, 'message' => 'Permesso negato.'];
$title = trim($title);
if (empty($title)) return ['success' => false, 'message' => 'Il titolo della nota è obbligatorio.'];
$found = false;
foreach ($bn['notes'] as &$note) {
if ($note['id'] === $note_id) {
$note['title'] = $title;
$note['description'] = trim($description);
$found = true;
break;
}
}
if (!$found) return ['success' => false, 'message' => 'Nota non trovata.'];
if (save_blocknote($bn)) {
return ['success' => true, 'message' => 'Nota modificata con successo!'];
}
return ['success' => false, 'message' => 'Errore nel salvataggio.'];
}
/**
* Delete a Note block.
*/
function delete_note($blocknote_id, $note_id, $username, $is_admin) {
$bn = get_blocknote_by_id($blocknote_id);
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
if (!can_access_blocknote($bn, $username, $is_admin)) return ['success' => false, 'message' => 'Permesso negato.'];
$initial_count = count($bn['notes']);
$bn['notes'] = array_values(array_filter($bn['notes'], function($n) use ($note_id) {
return $n['id'] !== $note_id;
}));
if (count($bn['notes']) === $initial_count) {
return ['success' => false, 'message' => 'Nota non trovata.'];
}
if (save_blocknote($bn)) {
return ['success' => true, 'message' => 'Nota eliminata con successo.'];
}
return ['success' => false, 'message' => 'Errore nel salvataggio.'];
}
/**
* Add a Subnote to a Note block.
*/
function add_subnote($blocknote_id, $note_id, $sub_title, $type, $content, $username, $is_admin) {
$bn = get_blocknote_by_id($blocknote_id);
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
if (!can_access_blocknote($bn, $username, $is_admin)) return ['success' => false, 'message' => 'Permesso negato.'];
$sub_title = trim($sub_title);
if (empty($sub_title)) return ['success' => false, 'message' => 'Il titolo della sottonota è obbligatorio.'];
$type = ($type === 'command') ? 'command' : 'text';
$sub_id = 'sub_' . uniqid();
$found = false;
foreach ($bn['notes'] as &$note) {
if ($note['id'] === $note_id) {
if (!isset($note['subnotes'])) $note['subnotes'] = [];
$note['subnotes'][] = [
'id' => $sub_id,
'title' => $sub_title,
'type' => $type,
'content' => $content
];
$found = true;
break;
}
}
if (!$found) return ['success' => false, 'message' => 'Nota genitore non trovata.'];
if (save_blocknote($bn)) {
return ['success' => true, 'subnote_id' => $sub_id, 'message' => 'Sottonota aggiunta con successo!'];
}
return ['success' => false, 'message' => 'Errore nel salvataggio.'];
}
/**
* Edit a Subnote.
*/
function edit_subnote($blocknote_id, $note_id, $sub_id, $sub_title, $type, $content, $username, $is_admin) {
$bn = get_blocknote_by_id($blocknote_id);
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
if (!can_access_blocknote($bn, $username, $is_admin)) return ['success' => false, 'message' => 'Permesso negato.'];
$sub_title = trim($sub_title);
if (empty($sub_title)) return ['success' => false, 'message' => 'Il titolo della sottonota è obbligatorio.'];
$type = ($type === 'command') ? 'command' : 'text';
$found = false;
foreach ($bn['notes'] as &$note) {
if ($note['id'] === $note_id) {
if (isset($note['subnotes'])) {
foreach ($note['subnotes'] as &$sub) {
if ($sub['id'] === $sub_id) {
$sub['title'] = $sub_title;
$sub['type'] = $type;
$sub['content'] = $content;
$found = true;
break 2;
}
}
}
}
}
if (!$found) return ['success' => false, 'message' => 'Sottonota non trovata.'];
if (save_blocknote($bn)) {
return ['success' => true, 'message' => 'Sottonota modificata con successo!'];
}
return ['success' => false, 'message' => 'Errore nel salvataggio.'];
}
/**
* Delete a Subnote.
*/
function delete_subnote($blocknote_id, $note_id, $sub_id, $username, $is_admin) {
$bn = get_blocknote_by_id($blocknote_id);
if (!$bn) return ['success' => false, 'message' => 'Blocknote non trovato.'];
if (!can_access_blocknote($bn, $username, $is_admin)) return ['success' => false, 'message' => 'Permesso negato.'];
$found = false;
foreach ($bn['notes'] as &$note) {
if ($note['id'] === $note_id && isset($note['subnotes'])) {
$initial = count($note['subnotes']);
$note['subnotes'] = array_values(array_filter($note['subnotes'], function($s) use ($sub_id) {
return $s['id'] !== $sub_id;
}));
if (count($note['subnotes']) < $initial) {
$found = true;
break;
}
}
}
if (!$found) return ['success' => false, 'message' => 'Sottonota non trovata.'];
if (save_blocknote($bn)) {
return ['success' => true, 'message' => 'Sottonota eliminata con successo.'];
}
return ['success' => false, 'message' => 'Errore nel salvataggio.'];
}
+5 -6
View File
@@ -1,13 +1,15 @@
<?php
/*
### LIST OF FUNCTIONS TO INCLUDE ###
Include in this array all the function files you may want to include.
Include in this array all the function files you may want to include.
You don't need to specify the extension .php, the system will automatically add it.
*/
$fun_to_inc = array(
//"example",
"demo-example"
"lang",
"site_settings",
"auth",
"blocknotes"
);
// DON'T CHANGE THE CODE BELOW!!!
@@ -17,9 +19,6 @@ if (isset($fun_to_inc) && is_array($fun_to_inc) && !empty($fun_to_inc)){
require_once(FUNCTIONS_FOLD . $fun . ".php");
} else {
debugStatusMes("Function file $fun.php not found in functions folder: '" . FUNCTIONS_FOLD . "'", "Medium");
if ($fun === "demo-example") {
debugStatusMes("The 'demo-example' function is only a demo. If you create a new function, needs to be included in the file specified above. Please remove the demo-example record from 'functions/functions.php' ", "Info");
}
}
}
}
+720
View File
@@ -0,0 +1,720 @@
<?php
/**
* Multilingual System Helper (IT, EN, ES, FR)
*/
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
/**
* Get current user language code.
*/
function get_current_lang() {
$user = $_SESSION['user'] ?? null;
$lang = $user['language'] ?? $_SESSION['guest_language'] ?? 'it';
if (!in_array($lang, ['it', 'en', 'es', 'fr'])) {
$lang = 'it';
}
return $lang;
}
/**
* Set guest/active language.
*/
function set_active_lang($lang) {
if (in_array($lang, ['it', 'en', 'es', 'fr'])) {
if (isset($_SESSION['user'])) {
$_SESSION['user']['language'] = $lang;
global $conn;
if (isset($conn) && !empty($_SESSION['user']['id'])) {
$stmt = $conn->prepare("UPDATE users SET language = ? WHERE id = ?");
$stmt->bind_param("si", $lang, $_SESSION['user']['id']);
$stmt->execute();
$stmt->close();
}
} else {
$_SESSION['guest_language'] = $lang;
}
}
}
/**
* Translations Dictionary
*/
function get_translations() {
return [
'it' => [
// Login Page
'login_title' => 'Accedi a Blocknotes',
'login_subtitle' => 'Gestisci le tue note e i tuoi comandi',
'label_username' => 'Nome Utente',
'label_password' => 'Password',
'placeholder_username' => 'Inserisci username',
'placeholder_password' => 'Inserisci password',
'btn_submit_login' => '🔑 Accedi',
'login_hint_footer' => 'Accredito predefinito per il primo accesso',
'login_installed_success' => '🎉 Setup completato! Accedi con le credenziali create.',
// Navbar
'nav_blocknotes' => '📓 Blocknotes',
'nav_colors_profile' => '🎨 Colori & Profilo',
'nav_admin' => '👑 Gestione Utenti',
'nav_logout' => '🚪 Esci',
'nav_login' => '🔑 Accedi',
'nav_language' => '🌐 Lingua',
// Dashboard (home)
'dashboard_title_user' => 'I Miei Blocknotes',
'dashboard_title_admin' => 'Tutti i Blocknotes (Vista Admin)',
'dashboard_desc_user' => 'Crea, organizza e consulta i tuoi blocknotes.',
'dashboard_desc_admin' => 'In qualità di Admin puoi visualizzare, gestire ed eliminare i blocknotes di tutti gli utenti.',
'btn_new_blocknote' => ' Nuovo Blocknote',
'btn_import_json' => '📥 Importa JSON',
'btn_export_json' => '💾 Esporta JSON',
'btn_open_edit' => '👁️ Apri & Modifica',
'btn_rename' => '✏️ Rinomina',
'btn_delete' => '🗑️ Elimina',
'empty_blocknotes_title' => 'Nessun Blocknote trovato',
'empty_blocknotes_desc_user' => 'Non hai ancora creato nessun blocknote. Inizia subito!',
'empty_blocknotes_desc_admin' => 'Non ci sono ancora blocknotes salvati nel sistema.',
'modal_create_title' => ' Crea Nuovo Blocknote',
'modal_rename_title' => '✏️ Rinomina Blocknote',
'modal_import_title' => '📥 Importa Blocknote (JSON)',
'label_title' => 'Titolo Blocknote',
'label_file_json' => 'Carica File .json',
'label_paste_json' => 'Oppure incolla qui il codice JSON',
'btn_cancel' => 'Annulla',
'btn_save' => 'Salva',
'btn_create' => 'Crea Blocknote',
'btn_import' => 'Importa Blocknote',
'note_single' => 'Nota',
'note_plural' => 'Note',
// View Blocknote
'btn_back_list' => '⬅️ Torna ai Blocknotes',
'btn_expand_all' => '📂 Espandi Tutte',
'btn_collapse_all' => '📁 Riduci Tutte',
'btn_new_note' => ' Nuova Nota',
'btn_add_subnote' => ' Sottonota',
'subnote_single' => 'sottonota',
'subnote_plural' => 'sottonote',
'badge_text' => '📄 Testo',
'badge_command' => '⚡ Comando',
'btn_copy' => '📋 Copia',
'btn_copied' => '✓ Copiato!',
'search_placeholder' => '🔍 Cerca note, sottonote o comandi in questo blocknote...',
'btn_clear_search' => '❌ Annulla Cerca',
'search_no_results' => 'Nessun risultato trovato',
'search_no_results_desc' => 'Nessuna nota o comando corrisponde al termine cercato in questo blocknote.',
'modal_add_note' => ' Nuova Nota a Blocchi',
'modal_edit_note' => '✏️ Modifica Nota',
'modal_add_subnote' => ' Aggiungi Sottonota',
'modal_edit_subnote' => '✏️ Modifica Sottonota',
'label_note_description' => 'Descrizione (opzionale)',
'label_subnote_title' => 'Titolo Sottonota',
'label_subnote_type' => 'Tipo di Sottonota',
'label_subnote_content' => 'Contenuto',
'type_option_text' => '📄 Testo generico',
'type_option_command' => '⚡ Comando (con pulsante Copia)',
// Profile & Settings
'profile_title' => '🎨 Impostazioni Profilo & Tema',
'profile_desc' => 'Personalizza password, colori dell\'interfaccia, lingua e pagina di avvio.',
'card_password_title' => '🔒 Cambio Password',
'card_colors_title' => '🎨 Personalizza Colori Interfaccia (HSL)',
'card_lang_title' => '🌍 Lingua dell\'Interfaccia',
'card_landing_title' => '🏠 Pagina di Primo Accesso (Landing Page)',
'label_new_pass' => 'Nuova Password',
'label_confirm_pass' => 'Conferma Nuova Password',
'label_select_lang' => 'Seleziona Lingua',
'label_select_landing' => 'Seleziona Pagina di Avvio',
'landing_option_dashboard' => '📋 Pagina Principale (Tutti i Blocknotes)',
'landing_option_bn_prefix' => '📌 Apri direttamente: ',
'btn_update_pass' => '🔑 Aggiorna Password',
'btn_save_colors' => '💾 Salva Preferenze Colori',
'btn_save_lang' => '🌍 Salva Lingua',
'btn_save_landing' => '🏠 Salva Pagina di Avvio',
// Admin Users
'admin_title' => '👑 Pannello Amministrazione Utenti',
'admin_desc' => 'Crea, gestisci e modifica i ruoli e gli accessi degli utenti.',
'btn_add_user' => ' Aggiungi Nuovo Utente',
'col_username' => 'Nome Utente',
'col_role' => 'Ruolo',
'col_bns' => 'Blocknotes Creati',
'col_created' => 'Data Registrazione',
'col_actions' => 'Azioni Admin',
'role_standard' => 'Standard User',
'role_admin' => 'Administrator',
'role_owner' => '👑 Owner (Proprietario)',
'badge_owner' => '👑 Owner',
'btn_reset_pass' => '🔑 Reset Pass',
'modal_create_user' => ' Registra Nuovo Utente',
'modal_reset_pass' => '🔑 Reset Password Utente',
// Confirm Dialogs
'confirm_delete_blocknote' => 'Sei sicuro di voler eliminare questo blocknote e tutte le sue note?',
'confirm_delete_note' => 'Sei sicuro di voler eliminare questa nota e tutte le sue sottonote?',
'confirm_delete_subnote' => 'Sei sicuro di voler eliminare questa sottonota?',
'confirm_delete_user' => 'Sei sicuro di voler eliminare questo utente?',
// Placeholders
'placeholder_bn_title' => 'Es. Comandi Server Linux, Note Progetto PHP...',
'placeholder_rename_title' => 'Nuovo titolo...',
'placeholder_import_title' => 'Es. Positive Internet',
'placeholder_paste_json' => 'Incolla qui il JSON nel vecchio o nuovo formato...',
'placeholder_note_title' => 'Es. Installazione MySQL, Server Setup...',
'placeholder_note_desc' => 'Breve descrizione di questo blocco di note...',
'placeholder_subnote_title' => 'Es. Comando creazione database, Note configurazione...',
'placeholder_subnote_content' => 'Inserisci il testo o le istruzioni del comando...',
'placeholder_site_title' => 'Es. Blocknotes, Enterprise Notes...',
'placeholder_username_new' => 'es. mario, giuseppe...',
'placeholder_password_new' => 'Inserisci nuova password',
'placeholder_password_confirm' => 'Ripeti nuova password',
// Extra Labels
'label_owner_prefix' => 'Proprietario: ',
'label_updated_prefix' => 'Ultimo aggiornamento: ',
'label_you' => 'Tu',
'label_unknown' => 'Sconosciuto',
'label_current_favicon' => 'Favicon Attuale: ',
// First Start Installer Setup
'setup_title' => '🚀 Setup Iniziale Blocknotes',
'setup_subtitle' => 'Configura il database MySQL e crea i primi utenti privilegiati (Owner & Admin).',
'setup_sec1_title' => '1. Configurazione Database MySQL',
'setup_db_host' => 'Host Database',
'setup_db_name' => 'Nome Database',
'setup_db_user' => 'Utente MySQL',
'setup_db_pass' => 'Password MySQL',
'setup_db_pass_placeholder' => 'Password (lascia vuoto se nessuna)',
'setup_sec2_title' => '2. Account Owner (Proprietario Unico)',
'setup_owner_user' => 'Username Owner',
'setup_owner_pass' => 'Password Owner',
'setup_owner_pass_placeholder' => 'Inserisci password per Owner',
'setup_sec3_title' => '3. Primo Account Amministratore (Admin)',
'setup_admin_user' => 'Username Admin',
'setup_admin_pass' => 'Password Admin',
'setup_admin_pass_placeholder' => 'Inserisci password per Admin',
'setup_btn_submit' => '⚡ Completa Installazione & Configura Sistema',
'err_setup_db_fields' => 'Compila tutti i dati di connessione al Database MySQL.',
'err_setup_owner_fields' => 'Inserisci username e password per l\'utente Owner (Proprietario).',
'err_setup_admin_fields' => 'Inserisci username e password per il primo utente Admin.',
'err_setup_same_user' => 'L\'utente Owner e l\'utente Admin devono avere nomi utente differenti.'
],
'en' => [
// Login Page
'login_title' => 'Login to Blocknotes',
'login_subtitle' => 'Manage your notes and commands',
'label_username' => 'Username',
'label_password' => 'Password',
'placeholder_username' => 'Enter username',
'placeholder_password' => 'Enter password',
'btn_submit_login' => '🔑 Login',
'login_hint_footer' => 'Default first login credential',
'login_installed_success' => '🎉 Setup completed! Login with your created credentials.',
// Navbar
'nav_blocknotes' => '📓 Blocknotes',
'nav_colors_profile' => '🎨 Colors & Profile',
'nav_admin' => '👑 User Management',
'nav_logout' => '🚪 Logout',
'nav_login' => '🔑 Login',
'nav_language' => '🌐 Language',
// Dashboard (home)
'dashboard_title_user' => 'My Blocknotes',
'dashboard_title_admin' => 'All Blocknotes (Admin View)',
'dashboard_desc_user' => 'Create, organize, and view your blocknotes.',
'dashboard_desc_admin' => 'As an Admin you can view, manage, and delete blocknotes of all users.',
'btn_new_blocknote' => ' New Blocknote',
'btn_import_json' => '📥 Import JSON',
'btn_export_json' => '💾 Export JSON',
'btn_open_edit' => '👁️ Open & Edit',
'btn_rename' => '✏️ Rename',
'btn_delete' => '🗑️ Delete',
'empty_blocknotes_title' => 'No Blocknotes found',
'empty_blocknotes_desc_user' => 'You haven\'t created any blocknote yet. Start now!',
'empty_blocknotes_desc_admin' => 'There are no blocknotes saved in the system yet.',
'modal_create_title' => ' Create New Blocknote',
'modal_rename_title' => '✏️ Rename Blocknote',
'modal_import_title' => '📥 Import Blocknote (JSON)',
'label_title' => 'Blocknote Title',
'label_file_json' => 'Upload .json File',
'label_paste_json' => 'Or paste JSON code here',
'btn_cancel' => 'Cancel',
'btn_save' => 'Save',
'btn_create' => 'Create Blocknote',
'btn_import' => 'Import Blocknote',
'note_single' => 'Note',
'note_plural' => 'Notes',
// View Blocknote
'btn_back_list' => '⬅️ Back to Blocknotes',
'btn_expand_all' => '📂 Expand All',
'btn_collapse_all' => '📁 Collapse All',
'btn_new_note' => ' New Note',
'btn_add_subnote' => ' Subnote',
'subnote_single' => 'subnote',
'subnote_plural' => 'subnotes',
'badge_text' => '📄 Text',
'badge_command' => '⚡ Command',
'btn_copy' => '📋 Copy',
'btn_copied' => '✓ Copied!',
'search_placeholder' => '🔍 Search notes, subnotes, or commands in this blocknote...',
'btn_clear_search' => '❌ Clear Search',
'search_no_results' => 'No results found',
'search_no_results_desc' => 'No notes or commands match your search term in this blocknote.',
'modal_add_note' => ' New Block Note',
'modal_edit_note' => '✏️ Edit Note',
'modal_add_subnote' => ' Add Subnote',
'modal_edit_subnote' => '✏️ Edit Subnote',
'label_note_description' => 'Description (optional)',
'label_subnote_title' => 'Subnote Title',
'label_subnote_type' => 'Subnote Type',
'label_subnote_content' => 'Content',
'type_option_text' => '📄 Generic Text',
'type_option_command' => '⚡ Command (with Copy button)',
// Profile & Settings
'profile_title' => '🎨 Profile & Theme Settings',
'profile_desc' => 'Customize password, UI colors, language, and default landing page.',
'card_password_title' => '🔒 Change Password',
'card_colors_title' => '🎨 Interface Colors (HSL)',
'card_lang_title' => '🌍 Interface Language',
'card_landing_title' => '🏠 Default Landing Page',
'label_new_pass' => 'New Password',
'label_confirm_pass' => 'Confirm New Password',
'label_select_lang' => 'Select Language',
'label_select_landing' => 'Select Landing Page',
'landing_option_dashboard' => '📋 Main Selection Page (All Blocknotes)',
'landing_option_bn_prefix' => '📌 Open directly: ',
'btn_update_pass' => '🔑 Update Password',
'btn_save_colors' => '💾 Save Color Preferences',
'btn_save_lang' => '🌍 Save Language',
'btn_save_landing' => '🏠 Save Landing Page',
// Admin Users
'admin_title' => '👑 User Administration Panel',
'admin_desc' => 'Create, manage, and edit user roles and access.',
'btn_add_user' => ' Add New User',
'col_username' => 'Username',
'col_role' => 'Role',
'col_bns' => 'Blocknotes Created',
'col_created' => 'Created Date',
'col_actions' => 'Admin Actions',
'role_standard' => 'Standard User',
'role_admin' => 'Administrator',
'role_owner' => '👑 Owner',
'badge_owner' => '👑 Owner',
'btn_reset_pass' => '🔑 Reset Pass',
'modal_create_user' => ' Register New User',
'modal_reset_pass' => '🔑 Reset User Password',
'card_site_settings_title' => '⚙️ Site Settings (Owner Only)',
'label_site_title' => 'Global Application / Site Title',
'label_favicon' => 'Site Favicon (High Resolution, min 64x64px)',
'btn_save_site_title' => '💾 Save Site Title',
'btn_upload_favicon' => '📤 Upload New Favicon',
// Confirm Dialogs
'confirm_delete_blocknote' => 'Are you sure you want to delete this blocknote and all its notes?',
'confirm_delete_note' => 'Are you sure you want to delete this note and all its subnotes?',
'confirm_delete_subnote' => 'Are you sure you want to delete this subnote?',
'confirm_delete_user' => 'Are you sure you want to delete this user?',
// Placeholders
'placeholder_bn_title' => 'E.g. Linux Server Commands, PHP Notes...',
'placeholder_rename_title' => 'New title...',
'placeholder_import_title' => 'E.g. Positive Internet',
'placeholder_paste_json' => 'Paste JSON here in old or new format...',
'placeholder_note_title' => 'E.g. MySQL Installation, Server Setup...',
'placeholder_note_desc' => 'Short description of this note block...',
'placeholder_subnote_title' => 'E.g. Database creation command...',
'placeholder_subnote_content' => 'Enter text or command instructions...',
'placeholder_site_title' => 'E.g. Blocknotes, Enterprise Notes...',
'placeholder_username_new' => 'e.g. john, mary...',
'placeholder_password_new' => 'Enter new password',
'placeholder_password_confirm' => 'Repeat new password',
// Extra Labels
'label_owner_prefix' => 'Owner: ',
'label_updated_prefix' => 'Last updated: ',
'label_you' => 'You',
'label_unknown' => 'Unknown',
'label_current_favicon' => 'Current Favicon: ',
// First Start Installer Setup
'setup_title' => '🚀 Blocknotes Initial Setup',
'setup_subtitle' => 'Configure MySQL database and create the initial privileged users (Owner & Admin).',
'setup_sec1_title' => '1. MySQL Database Configuration',
'setup_db_host' => 'Database Host',
'setup_db_name' => 'Database Name',
'setup_db_user' => 'MySQL User',
'setup_db_pass' => 'MySQL Password',
'setup_db_pass_placeholder' => 'Password (leave empty if none)',
'setup_sec2_title' => '2. Owner Account (Single Owner)',
'setup_owner_user' => 'Owner Username',
'setup_owner_pass' => 'Owner Password',
'setup_owner_pass_placeholder' => 'Enter password for Owner',
'setup_sec3_title' => '3. First Admin Account',
'setup_admin_user' => 'Admin Username',
'setup_admin_pass' => 'Admin Password',
'setup_admin_pass_placeholder' => 'Enter password for Admin',
'setup_btn_submit' => '⚡ Complete Installation & Configure System',
'err_setup_db_fields' => 'Please fill in all MySQL database connection fields.',
'err_setup_owner_fields' => 'Please enter username and password for the Owner user.',
'err_setup_admin_fields' => 'Please enter username and password for the first Admin user.',
'err_setup_same_user' => 'Owner user and Admin user must have different usernames.'
],
'es' => [
// Login Page
'login_title' => 'Iniciar sesión en Blocknotes',
'login_subtitle' => 'Gestiona tus notas y comandos',
'label_username' => 'Nombre de Usuario',
'label_password' => 'Contraseña',
'placeholder_username' => 'Introduce nombre de usuario',
'placeholder_password' => 'Introduce contraseña',
'btn_submit_login' => '🔑 Iniciar Sesión',
'login_hint_footer' => 'Credencial predeterminada para el primer acceso',
'login_installed_success' => '🎉 ¡Configuración completada! Inicia sesión con tus credenciales.',
// Navbar
'nav_blocknotes' => '📓 Blocknotes',
'nav_colors_profile' => '🎨 Colores y Perfil',
'nav_admin' => '👑 Gestión de Usuarios',
'nav_logout' => '🚪 Salir',
'nav_login' => '🔑 Iniciar Sesión',
'nav_language' => '🌐 Idioma',
// Dashboard (home)
'dashboard_title_user' => 'Mis Blocknotes',
'dashboard_title_admin' => 'Todos los Blocknotes (Vista Admin)',
'dashboard_desc_user' => 'Crea, organiza y consulta tus blocknotes.',
'dashboard_desc_admin' => 'Como Admin puedes ver, gestionar y eliminar los blocknotes de todos los usuarios.',
'btn_new_blocknote' => ' Nuevo Blocknote',
'btn_import_json' => '📥 Importar JSON',
'btn_export_json' => '💾 Exportar JSON',
'btn_open_edit' => '👁️ Abrir y Editar',
'btn_rename' => '✏️ Renombrar',
'btn_delete' => '🗑️ Eliminar',
'empty_blocknotes_title' => 'No se encontraron blocknotes',
'empty_blocknotes_desc_user' => 'Aún no has creado ningún blocknote. ¡Empieza ahora!',
'empty_blocknotes_desc_admin' => 'Aún no hay blocknotes guardados en el sistema.',
'modal_create_title' => ' Crear Nuevo Blocknote',
'modal_rename_title' => '✏️ Renombrar Blocknote',
'modal_import_title' => '📥 Importar Blocknote (JSON)',
'label_title' => 'Título del Blocknote',
'label_file_json' => 'Subir archivo .json',
'label_paste_json' => 'O pega el código JSON aquí',
'btn_cancel' => 'Cancelar',
'btn_save' => 'Guardar',
'btn_create' => 'Crear Blocknote',
'btn_import' => 'Importar Blocknote',
'note_single' => 'Nota',
'note_plural' => 'Notas',
// View Blocknote
'btn_back_list' => '⬅️ Volver a Blocknotes',
'btn_expand_all' => '📂 Expandir Todas',
'btn_collapse_all' => '📁 Contraer Todas',
'btn_new_note' => ' Nueva Nota',
'btn_add_subnote' => ' Subnota',
'subnote_single' => 'subnota',
'subnote_plural' => 'subnotas',
'badge_text' => '📄 Texto',
'badge_command' => '⚡ Comando',
'btn_copy' => '📋 Copiar',
'btn_copied' => '✓ ¡Copiado!',
'search_placeholder' => '🔍 Buscar notas, subnotas o comandos...',
'btn_clear_search' => '❌ Cancelar Búsqueda',
'search_no_results' => 'No se encontraron resultados',
'search_no_results_desc' => 'Ninguna nota o comando coincide con tu búsqueda.',
'modal_add_note' => ' Nueva Nota en Bloque',
'modal_edit_note' => '✏️ Editar Nota',
'modal_add_subnote' => ' Añadir Subnota',
'modal_edit_subnote' => '✏️ Editar Subnota',
'label_note_description' => 'Descripción (opcional)',
'label_subnote_title' => 'Título de Subnota',
'label_subnote_type' => 'Tipo de Subnota',
'label_subnote_content' => 'Contenido',
'type_option_text' => '📄 Texto genérico',
'type_option_command' => '⚡ Comando (con botón Copiar)',
// Profile & Settings
'profile_title' => '🎨 Ajustes de Perfil y Tema',
'profile_desc' => 'Personaliza contraseña, colores de interfaz, idioma y página de inicio.',
'card_password_title' => '🔒 Cambiar Contraseña',
'card_colors_title' => '🎨 Colores de Interfaz (HSL)',
'card_lang_title' => '🌍 Idioma de la Interfaz',
'card_landing_title' => '🏠 Página de Inicio Predeterminada',
'label_new_pass' => 'Nueva Contraseña',
'label_confirm_pass' => 'Confirmar Nueva Contraseña',
'label_select_lang' => 'Seleccionar Idioma',
'label_select_landing' => 'Seleccionar Página de Inicio',
'landing_option_dashboard' => '📋 Página Principal (Todos los Blocknotes)',
'landing_option_bn_prefix' => '📌 Abrir directamente: ',
'btn_update_pass' => '🔑 Actualizar Contraseña',
'btn_save_colors' => '💾 Guardar Colores',
'btn_save_lang' => '🌍 Guardar Idioma',
'btn_save_landing' => '🏠 Guardar Página de Inicio',
// Admin Users
'admin_title' => '👑 Panel de Administración de Usuarios',
'admin_desc' => 'Crea, gestiona y edita roles de usuarios y accesos.',
'btn_add_user' => ' Añadir Nuevo Usuario',
'col_username' => 'Nombre de Usuario',
'col_role' => 'Rol',
'col_bns' => 'Blocknotes Creados',
'col_created' => 'Fecha de Creación',
'col_actions' => 'Acciones de Admin',
'role_standard' => 'Usuario Estándar',
'role_admin' => 'Administrador',
'role_owner' => '👑 Propietario (Owner)',
'badge_owner' => '👑 Propietario',
'btn_reset_pass' => '🔑 Reset Pass',
'modal_create_user' => ' Registrar Nuevo Usuario',
'modal_reset_pass' => '🔑 Resetear Contraseña',
'card_site_settings_title' => '⚙️ Ajustes del Sitio (Solo Propietario)',
'label_site_title' => 'Título del Sitio / Aplicación',
'label_favicon' => 'Favicon del Sitio (Alta resolución, mín 64x64px)',
'btn_save_site_title' => '💾 Guardar Título',
'btn_upload_favicon' => '📤 Subir Nueva Favicon',
// Confirm Dialogs
'confirm_delete_blocknote' => '¿Estás seguro de que quieres eliminar este blocknote y todas sus notas?',
'confirm_delete_note' => '¿Estás seguro de que quieres eliminar esta nota y todas sus subnotas?',
'confirm_delete_subnote' => '¿Estás seguro de que quieres eliminar esta subnota?',
'confirm_delete_user' => '¿Estás seguro de que quieres eliminar este usuario?',
// Placeholders
'placeholder_bn_title' => 'Ej. Comandos Servidor Linux...',
'placeholder_rename_title' => 'Nuevo título...',
'placeholder_import_title' => 'Ej. Positive Internet',
'placeholder_paste_json' => 'Pega el JSON aquí en formato antiguo o nuevo...',
'placeholder_note_title' => 'Ej. Instalación MySQL...',
'placeholder_note_desc' => 'Breve descripción de este bloque de notas...',
'placeholder_subnote_title' => 'Ej. Comando creación base de datos...',
'placeholder_subnote_content' => 'Introduce el texto o instrucciones del comando...',
'placeholder_site_title' => 'Ej. Blocknotes...',
'placeholder_username_new' => 'ej. mario, juan...',
'placeholder_password_new' => 'Introduce nueva contraseña',
'placeholder_password_confirm' => 'Repite nueva contraseña',
// Extra Labels
'label_owner_prefix' => 'Propietario: ',
'label_updated_prefix' => 'Última actualización: ',
'label_you' => 'Tú',
'label_unknown' => 'Desconocido',
'label_current_favicon' => 'Favicon Actual: ',
// First Start Installer Setup
'setup_title' => '🚀 Configuración Inicial de Blocknotes',
'setup_subtitle' => 'Configura la base de datos MySQL y crea los primeros usuarios privilegiados (Propietario y Admin).',
'setup_sec1_title' => '1. Configuración de Base de Datos MySQL',
'setup_db_host' => 'Host de Base de Datos',
'setup_db_name' => 'Nombre de la Base de Datos',
'setup_db_user' => 'Usuario MySQL',
'setup_db_pass' => 'Contraseña MySQL',
'setup_db_pass_placeholder' => 'Contraseña (deja en blanco si no hay)',
'setup_sec2_title' => '2. Cuenta de Propietario (Único)',
'setup_owner_user' => 'Nombre de Usuario Propietario',
'setup_owner_pass' => 'Contraseña de Propietario',
'setup_owner_pass_placeholder' => 'Introduce contraseña para Propietario',
'setup_sec3_title' => '3. Primera Cuenta de Administrador (Admin)',
'setup_admin_user' => 'Nombre de Usuario Admin',
'setup_admin_pass' => 'Contraseña de Admin',
'setup_admin_pass_placeholder' => 'Introduce contraseña para Admin',
'setup_btn_submit' => '⚡ Completar Instalación y Configurar Sistema',
'err_setup_db_fields' => 'Por favor completa todos los campos de conexión a la base de datos MySQL.',
'err_setup_owner_fields' => 'Por favor introduce nombre de usuario y contraseña para el Propietario.',
'err_setup_admin_fields' => 'Por favor introduce nombre de usuario y contraseña para el primer Admin.',
'err_setup_same_user' => 'El usuario Propietario y el usuario Admin deben tener nombres diferentes.'
],
'fr' => [
// Login Page
'login_title' => 'Connexion à Blocknotes',
'login_subtitle' => 'Gérez vos notes et vos commandes',
'label_username' => 'Nom d\'Utilisateur',
'label_password' => 'Mot de Passe',
'placeholder_username' => 'Entrez nom d\'utilisateur',
'placeholder_password' => 'Entrez mot de passe',
'btn_submit_login' => '🔑 Connexion',
'login_hint_footer' => 'Identifiant par défaut pour le premier accès',
'login_installed_success' => '🎉 Configuration terminée! Connectez-vous avec vos identifiants.',
// Navbar
'nav_blocknotes' => '📓 Blocknotes',
'nav_colors_profile' => '🎨 Couleurs & Profil',
'nav_admin' => '👑 Gestion des Utilisateurs',
'nav_logout' => '🚪 Déconnexion',
'nav_login' => '🔑 Connexion',
'nav_language' => '🌐 Langue',
// Dashboard (home)
'dashboard_title_user' => 'Mes Blocknotes',
'dashboard_title_admin' => 'Tous les Blocknotes (Vue Admin)',
'dashboard_desc_user' => 'Créez, organisez et consultez vos blocknotes.',
'dashboard_desc_admin' => 'En tant qu\'Admin vous pouvez afficher, gérer et supprimer les blocknotes de tous les utilisateurs.',
'btn_new_blocknote' => ' Nouveau Blocknote',
'btn_import_json' => '📥 Importer JSON',
'btn_export_json' => '💾 Exporter JSON',
'btn_open_edit' => '👁️ Ouvrir & Modifier',
'btn_rename' => '✏️ Renommer',
'btn_delete' => '🗑️ Supprimer',
'empty_blocknotes_title' => 'Aucun blocknote trouvé',
'empty_blocknotes_desc_user' => 'Vous n\'avez pas encore créé de blocknote. Commencez dès maintenant!',
'empty_blocknotes_desc_admin' => 'Il n\'y a encore aucun blocknote enregistré dans le système.',
'modal_create_title' => ' Créer un Nouveau Blocknote',
'modal_rename_title' => '✏️ Renommer le Blocknote',
'modal_import_title' => '📥 Importer Blocknote (JSON)',
'label_title' => 'Titre du Blocknote',
'label_file_json' => 'Téléverser le fichier .json',
'label_paste_json' => 'Ou collez le code JSON ici',
'btn_cancel' => 'Annuler',
'btn_save' => 'Enregistrer',
'btn_create' => 'Créer Blocknote',
'btn_import' => 'Importer Blocknote',
'note_single' => 'Note',
'note_plural' => 'Notes',
// View Blocknote
'btn_back_list' => '⬅️ Retour aux Blocknotes',
'btn_expand_all' => '📂 Tout Développer',
'btn_collapse_all' => '📁 Tout Réduire',
'btn_new_note' => ' Nouvelle Note',
'btn_add_subnote' => ' Sous-note',
'subnote_single' => 'sous-note',
'subnote_plural' => 'sous-notes',
'badge_text' => '📄 Texte',
'badge_command' => '⚡ Commande',
'btn_copy' => '📋 Copier',
'btn_copied' => '✓ Copié!',
'search_placeholder' => '🔍 Rechercher notes, sous-notes ou commandes...',
'btn_clear_search' => '❌ Annuler la Recherche',
'search_no_results' => 'Aucun résultat trouvé',
'search_no_results_desc' => 'Aucune note ou commande ne correspond à votre recherche.',
'modal_add_note' => ' Nouvelle Note par Bloc',
'modal_edit_note' => '✏️ Modifier la Note',
'modal_add_subnote' => ' Ajouter une Sous-note',
'modal_edit_subnote' => '✏️ Modifier la Sous-note',
'label_note_description' => 'Description (facultatif)',
'label_subnote_title' => 'Titre de la Sous-note',
'label_subnote_type' => 'Type de Sous-note',
'label_subnote_content' => 'Contenu',
'type_option_text' => '📄 Texte générique',
'type_option_command' => '⚡ Commande (avec bouton Copier)',
// Profile & Settings
'profile_title' => '🎨 Paramètres Profil & Thème',
'profile_desc' => 'Personnalisez votre mot de passe, couleurs, langue et page d\'accueil.',
'card_password_title' => '🔒 Changer le Mot de Passe',
'card_colors_title' => '🎨 Couleurs de l\'Interface (HSL)',
'card_lang_title' => '🌍 Langue de l\'Interface',
'card_landing_title' => '🏠 Page d\'Accueil par Défaut',
'label_new_pass' => 'Nouveau Mot de Passe',
'label_confirm_pass' => 'Confirmer le Mot de Passe',
'label_select_lang' => 'Sélectionner la Langue',
'label_select_landing' => 'Sélectionner la Page d\'Accueil',
'landing_option_dashboard' => '📋 Page Principale (Tous les Blocknotes)',
'landing_option_bn_prefix' => '📌 Ouvrir directement: ',
'btn_update_pass' => '🔑 Mettre à Jour',
'btn_save_colors' => '💾 Enregistrer les Couleurs',
'btn_save_lang' => '🌍 Enregistrer la Langue',
'btn_save_landing' => '🏠 Enregistrer la Page d\'Accueil',
// Admin Users
'admin_title' => '👑 Panneau d\'Administration Utilisateurs',
'admin_desc' => 'Créez, gérez et modifiez les rôles et accès des utilisateurs.',
'btn_add_user' => ' Ajouter un Utilisateur',
'col_username' => 'Nom d\'Utilisateur',
'col_role' => 'Rôle',
'col_bns' => 'Blocknotes Créés',
'col_created' => 'Date de Création',
'col_actions' => 'Actions Admin',
'role_standard' => 'Utilisateur Standard',
'role_admin' => 'Administrateur',
'role_owner' => '👑 Propriétaire (Owner)',
'badge_owner' => '👑 Propriétaire',
'btn_reset_pass' => '🔑 Réinit Pass',
'modal_create_user' => ' Enregistrer un Utilisateur',
'modal_reset_pass' => '🔑 Réinitialiser le Mot de Passe',
'card_site_settings_title' => '⚙️ Paramètres du Site (Propriétaire Uniquement)',
'label_site_title' => 'Titre Global du Site',
'label_favicon' => 'Favicon du Site (Haute résolution, min 64x64px)',
'btn_save_site_title' => '💾 Enregistrer le Titre',
'btn_upload_favicon' => '📤 Téléverser la Favicon',
// Confirm Dialogs
'confirm_delete_blocknote' => 'Êtes-vous sûr de vouloir supprimer ce blocknote et toutes ses notes?',
'confirm_delete_note' => 'Êtes-vous sûr de vouloir supprimer cette note et toutes ses sous-notes?',
'confirm_delete_subnote' => 'Êtes-vous sûr de vouloir supprimer cette sous-note?',
'confirm_delete_user' => 'Êtes-vous sûr de vouloir supprimer cet utilisateur?',
// Placeholders
'placeholder_bn_title' => 'Ex. Commandes Serveur Linux...',
'placeholder_rename_title' => 'Nouveau titre...',
'placeholder_import_title' => 'Ex. Positive Internet',
'placeholder_paste_json' => 'Collez le JSON ici au format ancien ou nouveau...',
'placeholder_note_title' => 'Ex. Installation MySQL...',
'placeholder_note_desc' => 'Brève description de ce bloc de notes...',
'placeholder_subnote_title' => 'Ex. Commande création base de données...',
'placeholder_subnote_content' => 'Entrez le texte ou les instructions de la commande...',
'placeholder_site_title' => 'Ex. Blocknotes...',
'placeholder_username_new' => 'ex. pierre, marie...',
'placeholder_password_new' => 'Entrez nouveau mot de passe',
'placeholder_password_confirm' => 'Répétez le mot de passe',
// Extra Labels
'label_owner_prefix' => 'Propriétaire: ',
'label_updated_prefix' => 'Dernière mise à jour: ',
'label_you' => 'Vous',
'label_unknown' => 'Inconnu',
'label_current_favicon' => 'Favicon Actuelle: ',
// First Start Installer Setup
'setup_title' => '🚀 Configuration Initiale de Blocknotes',
'setup_subtitle' => 'Configurez la base de données MySQL et créez les premiers utilisateurs privilégiés (Propriétaire et Admin).',
'setup_sec1_title' => '1. Configuration de la Base de Données MySQL',
'setup_db_host' => 'Hôte de la Base de Données',
'setup_db_name' => 'Nom de la Base de Données',
'setup_db_user' => 'Utilisateur MySQL',
'setup_db_pass' => 'Mot de Passe MySQL',
'setup_db_pass_placeholder' => 'Mot de passe (laissez vide si aucun)',
'setup_sec2_title' => '2. Compte Propriétaire (Unique)',
'setup_owner_user' => 'Nom d\'Utilisateur Propriétaire',
'setup_owner_pass' => 'Mot de Passe Propriétaire',
'setup_owner_pass_placeholder' => 'Entrez le mot de passe pour le Propriétaire',
'setup_sec3_title' => '3. Premier Compte Administrateur (Admin)',
'setup_admin_user' => 'Nom d\'Utilisateur Admin',
'setup_admin_pass' => 'Mot de Passe Admin',
'setup_admin_pass_placeholder' => 'Entrez le mot de passe pour Admin',
'setup_btn_submit' => '⚡ Terminer l\'Installation et Configurer le Système',
'err_setup_db_fields' => 'Veuillez remplir tous les champs de connexion à la base de données MySQL.',
'err_setup_owner_fields' => 'Veuillez entrer le nom d\'utilisateur et le mot de passe pour le Propriétaire.',
'err_setup_admin_fields' => 'Veuillez entrer le nom d\'utilisateur et le mot de passe pour le premier Admin.',
'err_setup_same_user' => 'L\'utilisateur Propriétaire et l\'utilisateur Admin doivent avoir des noms différents.'
]
];
}
/**
* Translate key to current active language string.
*/
function __($key, $default = '') {
$lang = get_current_lang();
$translations = get_translations();
if (isset($translations[$lang][$key])) {
return $translations[$lang][$key];
}
if (isset($translations['it'][$key])) {
return $translations['it'][$key];
}
if (isset($translations['en'][$key])) {
return $translations['en'][$key];
}
return !empty($default) ? $default : $key;
}
+89
View File
@@ -0,0 +1,89 @@
<?php
/**
* Global Site Settings & High-Resolution Favicon Management (Owner Exclusive)
*/
/**
* Get site setting value by key.
*/
function get_site_setting($key, $default = '') {
global $conn;
if (!isset($conn)) return $default;
$stmt = $conn->prepare("SELECT setting_value FROM site_settings WHERE setting_key = ? LIMIT 1");
$stmt->bind_param("s", $key);
$stmt->execute();
$res = $stmt->get_result();
if ($row = $res->fetch_assoc()) {
$stmt->close();
return $row['setting_value'];
}
$stmt->close();
return $default;
}
/**
* Update or insert site setting.
*/
function update_site_setting($key, $value) {
global $conn;
if (!isset($conn)) return false;
$stmt = $conn->prepare("INSERT INTO site_settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
$stmt->bind_param("ss", $key, $value);
$res = $stmt->execute();
$stmt->close();
return $res;
}
/**
* Upload and validate high-resolution site Favicon.
*/
function upload_site_favicon($file) {
if (empty($file) || $file['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'message' => 'Errore nel caricamento del file favicon.'];
}
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$allowed_extensions = ['png', 'ico', 'webp', 'svg', 'jpeg', 'jpg'];
if (!in_array($ext, $allowed_extensions)) {
return ['success' => false, 'message' => 'Formato favicon non supportato. Formati ammessi: PNG, ICO, WEBP, SVG, JPG.'];
}
// High resolution dimension check for raster formats (PNG, WEBP, JPG, ICO)
if (in_array($ext, ['png', 'webp', 'jpeg', 'jpg', 'ico'])) {
$image_info = @getimagesize($file['tmp_name']);
if ($image_info) {
$width = $image_info[0];
$height = $image_info[1];
// Minimum high resolution requirement: 64x64px
if ($width < 64 || $height < 64) {
return [
'success' => false,
'message' => "Risoluzione favicon troppo bassa ({$width}x{$height}px). Per garantire la massima qualità visiva, carica un'immagine ad alta risoluzione (almeno 64x64px)."
];
}
}
}
// Ensure assets/img directory exists
$img_dir = ROOT_DIR . "assets/img/";
if (!is_dir($img_dir)) {
@mkdir($img_dir, 0777, true);
}
$new_filename = "favicon_" . time() . "." . $ext;
$target_file = $img_dir . $new_filename;
$relative_path = "assets/img/" . $new_filename;
if (move_uploaded_file($file['tmp_name'], $target_file)) {
update_site_setting('site_favicon', $relative_path);
return ['success' => true, 'message' => 'Favicon aggiornata con successo!'];
}
return ['success' => false, 'message' => 'Impossibile salvare il file favicon su disco.'];
}
+270
View File
@@ -0,0 +1,270 @@
<?php
require_admin();
$msg_success = "";
$msg_error = "";
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'create_user') {
$res = admin_create_user($_POST['username'] ?? '', $_POST['password'] ?? '', $_POST['role'] ?? 'user');
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
} elseif ($action === 'delete_user') {
$res = admin_delete_user($_POST['user_id'] ?? 0);
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
} elseif ($action === 'update_role') {
$res = admin_update_user_role($_POST['user_id'] ?? 0, $_POST['role'] ?? 'user');
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
} elseif ($action === 'reset_password') {
$res = update_user_password($_POST['user_id'] ?? 0, $_POST['new_password'] ?? '');
if ($res['success']) $msg_success = "Password resettata con successo!"; else $msg_error = $res['message'];
} elseif ($action === 'update_site_title' && is_owner()) {
$new_title = trim($_POST['site_title'] ?? '');
if (!empty($new_title)) {
update_site_setting('site_title', $new_title);
$msg_success = "Titolo del sito aggiornato con successo!";
} else {
$msg_error = "Il titolo del sito non può essere vuoto.";
}
} elseif ($action === 'upload_favicon' && is_owner()) {
$res = upload_site_favicon($_FILES['favicon_file'] ?? null);
if ($res['success']) $msg_success = $res['message']; else $msg_error = $res['message'];
}
}
// Fetch all users and all blocknotes to count per user
$users_list = get_all_users();
$all_blocknotes = get_all_blocknotes();
// Map blocknote count per username
$bn_counts = [];
foreach ($all_blocknotes as $bn) {
$uname = strtolower($bn['username'] ?? '');
if ($uname) {
$bn_counts[$uname] = ($bn_counts[$uname] ?? 0) + 1;
}
}
$site_title = get_site_setting('site_title', '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>
+368
View File
@@ -0,0 +1,368 @@
<?php
require_once __DIR__ . '/../config/init.php';
$raw_json = <<<'JSON'
[
{
"title": "Login Egguburt",
"description": "Login details",
"codes": [
{
"sub_title": "Eggburt",
"code": "I want to Admin Positive Servers!"
},
{
"sub_title": "Timpani",
"code": "I want to administer Timpani."
},
{
"sub_title": "## How to connect in Timpani ##",
"code": "From Eggburt: ssh timpani@timpops\r\nPassword: 99flak3\r\ntimpss 'servername'"
}
]
},
{
"title": "Varnish",
"description": "Varnish collection of commands",
"codes": [
{
"sub_title": "Varnish Domain Clean Cache",
"code": "varnishadm \"ban req.http.host ~ DOMAIN\""
},
{
"sub_title": "Check the varnish syntax command:",
"code": "varnishd -C -f /etc/varnish/default.vcl"
}
]
},
{
"title": "Find Spam IP",
"description": "findspam commands",
"codes": [
{
"sub_title": "Spam in fw-a",
"code": "tail -n3000 /var/log/nginx/access.log | awk '{print $1}' | sort -nr | uniq -c | sort -nr | head -n15"
},
{
"sub_title": "Spam in Home Dir",
"code": "tail -n3000 /home/*/logs/*access*log | awk '{print $1}' | sort -nr | uniq -c | sort -nr | head -n15"
},
{
"sub_title": "Spam in App (apache2)",
"code": "tail -n3000 /var/log/apache2/access.log | awk '{print $1}' | sort -nr | uniq -c | sort -nr | head -n15"
},
{
"sub_title": "Check all kind of logs in nginx",
"code": "tail -n3000 /var/log/nginx/*access*log* | awk '{print $1}' | sort -nr | uniq -c | sort -nr | head -n15"
}
]
},
{
"title": "Clean Disks Space",
"description": "Cleaning Diskspaces for each kind of VMs",
"codes": [
{
"sub_title": "## Usually for App VM ##",
"code": "apt clean; journalctl --vacuum-size=8M; logrotate -vf /etc/logrotate.d/rsyslog; logrotate -vf /etc/logrotate.d/apache2; logrotate -vf /etc/logrotate.d/nginx; logrotate -vf /etc/logrotate.d/varnish;"
},
{
"sub_title": "## Usually for firewall VM ##",
"code": "apt clean; journalctl --vacuum-size=8M; logrotate -vf /etc/logrotate.d/rsyslog; logrotate -vf /etc/logrotate.d/nginx; logrotate -vf /etc/logrotate.d/varnish;"
},
{
"sub_title": "## PHP ##",
"code": "logrotate -vf /etc/logrotate.d/php5.6-fpm; logrotate -vf /etc/logrotate.d/php7.3-fpm; logrotate -vf /etc/logrotate.d/php7.4-fpm; logrotate -vf /etc/logrotate.d/php8.1-fpm; logrotate -vf /etc/logrotate.d/php8.2-fpm;"
},
{
"sub_title": "## ONLY BASIC ##",
"code": "apt clean; journalctl --vacuum-size=8M; logrotate -vf /etc/logrotate.d/rsyslog;"
},
{
"sub_title": "VM + only apache2",
"code": "apt clean; journalctl --vacuum-size=8M; logrotate -vf /etc/logrotate.d/rsyslog; logrotate -vf /etc/logrotate.d/apache2;"
}
]
},
{
"title": "Add findspam Script",
"description": "This is the findspam from the repository.",
"codes": [
{
"sub_title": "Command:",
"code": "apt install -y jq; echo \"\" && echo \"Checking the reachability of the server...\"; ping -c 2 -W 2 scripts.svm.picl.co.uk > /dev/null 2>&1 && echo \"Server reachable. Proceeding with script update.\" || { echo \"Server unreachable. Update canceled.\"; exit 1; }; for i in updatebspamscripts findspam blockspamip spamwhat nobanip unblockspams; do echo \"Updating script $i...\"; curl -s -o /usr/local/sbin/$i https://scripts.svm.picl.co.uk/scripts/$i.sh && chmod 700 /usr/local/sbin/$i && echo \"Script '$i' updated.\" || echo \"Error updating script '$i'.\"; done"
}
]
},
{
"title": "Create New Database",
"description": "Command to create new database and database use",
"codes": [
{
"sub_title": "## Create Databases ##",
"code": "CREATE DATABASE unixuser;"
},
{
"sub_title": "# Create User For DB #",
"code": "grant all privileges on `unixuser`.* to `unixuser`@'%' identified by 'password database';"
}
]
},
{
"title": "Rsync From Server to the other",
"description": "Command to rsync between the servers",
"codes": [
{
"sub_title": "## Rsync Command ##",
"code": "rsync -avzP /home/user/public_html/ [email protected]:/home/user/public_html/"
}
]
},
{
"title": "WP-CLI Commands",
"description": "Usefully commands for wp-cli. Note that you need to change \"from-website\" and \"to-website\" with the correct domain.",
"codes": [
{
"sub_title": "## Export DB ##",
"code": "wp db export"
},
{
"sub_title": "## Change Links for wordpress ##",
"code": "wp search-replace 'from-website.Com' 'to-website.com' --all-tables"
},
{
"sub_title": "Add New User (change the '###*' strings with the correct informations)",
"code": "wp user create ###USERNAME ###EMAIL@DOMAIN --role=administrator --user_pass=\"###PASSWORD\""
},
{
"sub_title": "Install WP-CLI (Use this as root user)",
"code": "curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar; chmod +x wp-cli.phar; sudo mv wp-cli.phar /usr/local/bin/wp"
},
{
"sub_title": "Install varnish-cache plugin:",
"code": "wp plugin install varnish-http-purge\r\nwp plugin activate varnish-http-purge"
},
{
"sub_title": "Configuration to add in wp-config.php for the cache plugin",
"code": "define('VHP_VARNISH_IP','varnish-vip:6081');"
},
{
"sub_title": "(Run as Root) Checksums verify WP CLI on all /home folders",
"code": "for u in /home/*; do user=$(basename \"$u\"); for d in httpdocs public_html; do if [ -d \"$u/$d\" ] && [ -f \"$u/$d/wp-config.php\" ]; then echo \"=== $user ($d) ===\"; sudo -u \"$user\" -H wp --path=\"$u/$d\" core verify-checksums; echo; fi; done; done"
},
{
"sub_title": "verify checksum for a single website",
"code": "wp core verify-checksums"
},
{
"sub_title": "restore corefiles",
"code": "wp core download --version=$(wp core version) --force"
},
{
"sub_title": "Create posops user",
"code": "wp user create posops [email protected] --role=administrator --user_pass='P3dsfj7nYLaMxxXk'"
},
{
"sub_title": "Cambiare la password di un utente",
"code": "wp user update posops --user_pass='P3dsfj7nYLaMxxXk'"
}
]
},
{
"title": "Find Killig Process Error",
"description": "Command to check what has been killed in the VMs / Machine",
"codes": [
{
"sub_title": "## How to check errors ##",
"code": "grep -E '(reaper|oom)' /var/log/syslog"
}
]
},
{
"title": "Generate SSH Key",
"description": "Command to generate ssh key",
"codes": [
{
"sub_title": "## ED25519 ##",
"code": "ssh-keygen -t ed25519"
},
{
"sub_title": "## RSA ##",
"code": "ssh-keygen -t rsa -b 4096"
}
]
},
{
"title": "User Basic Auth",
"description": "How to create / add user for the basicauth.",
"codes": [
{
"sub_title": "## CREATE A FILE / USER (cancel existing one) ##",
"code": "htpasswd -c /etc/nginx/.htpasswd-name nuovo_utente"
},
{
"sub_title": "## ADD EXTRA USER ##",
"code": "htpasswd /etc/nginx/.htpasswd-name username"
},
{
"sub_title": "Block to add",
"code": "auth_basic \"Restricted Access\";\r\n auth_basic_user_file /etc/nginx/.htpasswd;"
},
{
"sub_title": "Ownership and privileges",
"code": "htpassloc=\"etc/nginx/.htpasswd\";\r\nchown www-data:www-data $htpassloc;\r\nchmod 644 $htpassloc"
}
]
},
{
"title": "Check DB Size",
"description": "Command to check the dimension of the databases; (needs to be specified the databases names)",
"codes": [
{
"sub_title": "## CHECK DB SIZE ##",
"code": "SELECT table_schema AS \"Database\", ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS \"Dimension (MB)\" FROM information_schema.tables WHERE table_schema IN (\"dbname1 \", \"dbname2\", \"dbname3\", \"dbname4\") GROUP BY table_schema;"
}
]
},
{
"title": "Remove Files and keep the newest one",
"description": "Change the variable keep in base to how much newest file you need to keep!",
"codes": [
{
"sub_title": "## REMOVE OLDEST FILES CODE ##",
"code": "count=0; keep=4; echo \"### FILE THAT WILL BE REMOVED: ###\"; echo \"-------------------------------\"; for i in $(ls -t); do if [ $count -lt $keep ]; then echo \"Keeping: $i\"; else echo \"Removing $i\"; fi; ((count++)); done; echo \"-------------------------------\"; echo \"Do you wish to continue?\"; echo \"y - Yes\"; echo \"default - no\"; read check; check=${check,,}; if [ \"$check\" == \"y\" ]; then echo \"#### REMOVING FILE ####\"; echo \"-------------------------------\"; count=0; for i in $(ls -t); do if [ $count -lt $keep ]; then echo \"Keeping: $i\"; else echo \"Removed $i\"; rm \"$i\"; fi; ((count++)); done; echo \"-------------------------------\"; else echo \"Not Removed\"; fi\r\n"
}
]
},
{
"title": "Add Diskspace Command",
"description": "After increased the diskspace of the hard disk on proxmox, we can run this command to add the 100% of the diskspace to the root partition.",
"codes": [
{
"sub_title": "Command:",
"code": "parted /dev/vda resizepart 1 100%; pvresize /dev/vda1; lvextend -l +100%FREE /dev/vgmain/root; resize2fs /dev/vgmain/root"
}
]
},
{
"title": "New Certboot Certificate",
"description": "Install new certboot Certificate",
"codes": [
{
"sub_title": "Command:",
"code": "certbot certonly --webroot -w /var/www/html -d ###DOMAIN"
}
]
},
{
"title": "Extract pfx key",
"description": "Use this command to extract the key and the pem files from a .pfx file.\r\nChange the variables before to run the command.",
"codes": [
{
"sub_title": "Command:",
"code": "PFX=\"FILE_NAME\";\r\nPASS=\"PASSWORD_PFX\";\r\nopenssl pkcs12 -in \"$PFX\" -passin pass:\"$PASS\" -nocerts -nodes -out server.key && \\\r\nopenssl pkcs12 -in \"$PFX\" -passin pass:\"$PASS\" -nokeys | \\\r\nsed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' > fullchain.pem\r\n"
}
]
},
{
"title": "check if the ssh-key is valid",
"description": "With this command we can check if a ssh key is valid or not",
"codes": [
{
"sub_title": "Command:",
"code": "FILENAME=\"path/to/the/file\";\r\nssh-keygen -l -f $FILENAME;\r\n"
}
]
},
{
"title": "checking valid rules for studiorep-fw",
"description": "This command is checking which of the following rules has been accepted for the php-rules",
"codes": [
{
"sub_title": "Command:",
"code": "php-fpm8.3 -tt 2>&1 | grep -A50 \"\\[POOL_NAME\\]\""
}
]
},
{
"title": "Add new VM to spa",
"description": "Use this to add the new element to the spa command. Specify the Public IP, the port and the hostname",
"codes": [
{
"sub_title": "Command:",
"code": "sudoedit /home/posops/ansible/hosts"
}
]
},
{
"title": "PHP-FPM Scripts",
"description": "Script Collection for PHP-FPM",
"codes": [
{
"sub_title": "Restarting all php-fpm existing",
"code": "for i in /etc/php/*; do version=$(basename \"$i\"); service=\"php${version}-fpm\"; if systemctl list-units --type=service --all | grep -q \"$service\"; then echo \"[ $service ]\"; systemctl restart \"$service\"; systemctl status \"$service\" | grep Active; fi; done"
},
{
"sub_title": "Check the php-version for the vhost",
"code": "VERSION=8.3; for i in *; do socket=$(grep -oP 'SetHandler\\s+\"proxy:unix:\\K[^|\"]+' \"$i\"); if [ -n \"$socket\" ]; then domain=$(basename \"$socket\" .sock); match=$(grep -iRl \"$domain\" /etc/php/*/fpm/pool.d/); if [ -n \"$match\" ]; then phpver=$(echo \"$match\" | sed -n 's|/etc/php/\\([^/]*\\)/fpm/.*|\\1|p' | sort -u); if echo \"$phpver\" | grep -q \"^$VERSION$\"; then echo \"[ $domain ]\"; echo \"php-fpm $phpver\"; fi; fi; fi; done"
},
{
"sub_title": "Check the php-version for the vhost",
"code": "exver=\"7.4\" instver=\"8.4\" ; for i in $(dpkg -l *php* | awk '{print $2}' | grep $exver); do apt install php$instver${i:6} -y; done"
}
]
},
{
"title": "ownership to 755 folder and 644 files",
"description": "Changing the ownership from 755 to the folders and 644 to the files (sub-directories included) with find + xarg in the current director.",
"codes": [
{
"sub_title": "Command",
"code": "find . -type d -print0 | xargs -0 chmod 755;\r\nfind . -type f -print0 | xargs -0 chmod 644"
}
]
}
]
JSON;
$legacy_data = json_decode($raw_json, true);
if (!is_array($legacy_data)) {
echo "ERROR: Failed to decode legacy JSON: " . json_last_error_msg() . "\n";
exit(1);
}
$bn_id = 'bn_positive_internet';
$new_bn = [
'id' => $bn_id,
'title' => 'Positive Internet',
'username' => 'admin',
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
'notes' => []
];
foreach ($legacy_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'] ?? ''
];
}
}
$new_bn['notes'][] = [
'id' => $note_id,
'title' => $legacy_note['title'] ?? 'Nota',
'description' => $legacy_note['description'] ?? '',
'subnotes' => $subnotes
];
}
save_blocknote($new_bn);
echo "SUCCESS! Converted and saved Positive Internet blocknote with " . count($new_bn['notes']) . " notes.\n";
+9 -7
View File
@@ -1,9 +1,11 @@
<footer class="main-footer">
<div class="footer-credit">&copy; <year>year</year> - Company Name. All right reserved.</div>
<div class="webmaster_footer">Website designed by Simone Cusano - SimoLinuxDesign.org</div>
</footer>
<?php include(INCLUDES_FOLD . "js_footer.php"); ?>
<?php displayDebug(); ?>
</div> <!-- End .main-wrapper -->
<footer class="main-footer">
<div class="footer-container">
<div class="footer-credit">&copy; <?= date('Y') ?> - Blocknotes System. Tutti i diritti riservati.</div>
<div class="webmaster_footer">Sistema Blocknotes basato su PHP, JSON &amp; MySQL</div>
</div>
</footer>
<?php include(INCLUDES_FOLD . "js_footer.php"); ?>
<?php displayDebug(); ?>
</body>
</html>
+115 -11
View File
@@ -1,20 +1,124 @@
<?php
// Handle quick language switch if requested
if (isset($_GET['set_lang'])) {
set_active_lang($_GET['set_lang']);
$redirect_url = strtok($_SERVER["REQUEST_URI"], '?');
$get_params = $_GET;
unset($get_params['set_lang']);
if (!empty($get_params)) {
$redirect_url .= '?' . http_build_query($get_params);
}
header("Location: " . $redirect_url);
exit;
}
$current_lang = get_current_lang();
$site_title = get_site_setting('site_title', 'Blocknotes');
$site_favicon = get_site_setting('site_favicon', 'assets/img/favicon.png');
?>
<!DOCTYPE html>
<html lang="en">
<html lang="<?= htmlspecialchars($current_lang) ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="<?= htmlspecialchars($site_favicon) ?>">
<?php include(INCLUDES_FOLD . "head_links.php"); ?>
<title><?php echo ucfirst($GLOBALS["current_page"] ?? "Home"); ?></title>
<?php
// Inject dynamic HSL theme colors from session if set
$user = current_user();
$pri = $user['theme_pri'] ?? 98;
$sec = $user['theme_sec'] ?? 207;
$thi = $user['theme_thi'] ?? 280;
?>
<style id="dynamic-theme-vars">
:root {
--pri: <?= (int)$pri ?>;
--sec: <?= (int)$sec ?>;
--thi: <?= (int)$thi ?>;
}
</style>
<title><?php
$cp = $GLOBALS["current_page"] ?? "home";
if ($cp === 'home') echo __('dashboard_title_user');
elseif ($cp === 'view_blocknote') echo __('nav_blocknotes');
elseif ($cp === 'profile') echo __('profile_title');
elseif ($cp === 'admin_users') echo __('admin_title');
elseif ($cp === 'login') echo __('nav_login');
else echo ucfirst($cp);
?> - <?= htmlspecialchars($site_title) ?></title>
</head>
<body>
<div id="debug-target-container"></div>
<header>
<ul class="main-nav">
<li><a href="#">Home</a></li>
<li><a href="#">Page1</a></li>
<li><a href="#">Page2</a></li>
<li><a href="#">Page3</a></li>
<li><a href="#">Page4</a></li>
</ul>
</header>
<header class="navbar-header">
<div class="nav-container">
<a href="index.php?page=home&view=all" class="brand-logo">
<?php if (!empty($site_favicon) && file_exists(ROOT_DIR . $site_favicon)): ?>
<img src="<?= htmlspecialchars($site_favicon) ?>" alt="Favicon" style="width:28px; height:28px; object-fit:contain; border-radius:4px;">
<?php else: ?>
<span class="logo-icon">📝</span>
<?php endif; ?>
<span class="logo-text"><?= htmlspecialchars($site_title) ?></span>
</a>
<nav>
<ul class="main-nav">
<?php if (is_logged_in()): ?>
<li>
<a href="index.php?page=home&view=all" class="<?= ($cp === 'home' || $cp === 'view_blocknote') ? 'active' : '' ?>">
<?= __('nav_blocknotes') ?>
</a>
</li>
<li>
<a href="index.php?page=profile" class="<?= ($cp === 'profile') ? 'active' : '' ?>">
<?= __('nav_colors_profile') ?>
</a>
</li>
<?php if (is_admin()): ?>
<li class="admin-nav-item">
<a href="index.php?page=admin_users" class="admin-link <?= ($cp === 'admin_users') ? 'active' : '' ?>">
<?= __('nav_admin') ?>
</a>
</li>
<?php endif; ?>
<li class="user-meta-item">
<?php if (is_owner()): ?>
<span class="user-badge badge-owner" style="background: rgba(234, 179, 8, 0.25); border-color: rgba(234, 179, 8, 0.5); color: #fde047; box-shadow: 0 0 10px rgba(234,179,8,0.3);">
👑 <?= htmlspecialchars($user['username']) ?> (<?= __('badge_owner') ?>)
</span>
<?php else: ?>
<span class="user-badge <?= is_admin() ? 'badge-admin' : 'badge-user' ?>">
👤 <?= htmlspecialchars($user['username']) ?> (<?= is_admin() ? 'Admin' : 'User' ?>)
</span>
<?php endif; ?>
</li>
<!-- Language Quick Selector -->
<li class="lang-selector-item">
<select onchange="location.href=this.value;" style="background: rgba(0,0,0,0.3); color: #fff; border: 1px solid rgba(255,255,255,0.15); border-radius: 6px; padding: 0.3rem 0.5rem; font-size: 0.82rem; cursor: pointer;">
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'it'])) ?>" <?= ($current_lang === 'it') ? 'selected' : '' ?>>🇮🇹 IT</option>
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'en'])) ?>" <?= ($current_lang === 'en') ? 'selected' : '' ?>>🇬🇧 EN</option>
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'es'])) ?>" <?= ($current_lang === 'es') ? 'selected' : '' ?>>🇪🇸 ES</option>
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'fr'])) ?>" <?= ($current_lang === 'fr') ? 'selected' : '' ?>>🇫🇷 FR</option>
</select>
</li>
<li><a href="index.php?page=logout" class="nav-logout-btn"><?= __('nav_logout') ?></a></li>
<?php else: ?>
<!-- Guest Language Quick Selector -->
<li class="lang-selector-item">
<select onchange="location.href=this.value;" style="background: rgba(0,0,0,0.3); color: #fff; border: 1px solid rgba(255,255,255,0.15); border-radius: 6px; padding: 0.3rem 0.5rem; font-size: 0.82rem; cursor: pointer;">
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'it'])) ?>" <?= ($current_lang === 'it') ? 'selected' : '' ?>>🇮🇹 IT</option>
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'en'])) ?>" <?= ($current_lang === 'en') ? 'selected' : '' ?>>🇬🇧 EN</option>
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'es'])) ?>" <?= ($current_lang === 'es') ? 'selected' : '' ?>>🇪🇸 ES</option>
<option value="index.php?<?= http_build_query(array_merge($_GET, ['set_lang' => 'fr'])) ?>" <?= ($current_lang === 'fr') ? 'selected' : '' ?>>🇫🇷 FR</option>
</select>
</li>
<li><a href="index.php?page=login" class="active"><?= __('nav_login') ?></a></li>
<?php endif; ?>
</ul>
</nav>
</div>
</header>
<div class="main-wrapper">