First commit CMS

This commit is contained in:
Adrian Hinz 2026-01-19 18:33:03 +01:00
commit 8349706c36
16 changed files with 3673 additions and 0 deletions

48
.htaccess Normal file
View File

@ -0,0 +1,48 @@
# Włącz mod_rewrite
RewriteEngine On
RewriteBase /cms/
# Przekierowanie do HTTPS (opcjonalne, odkomentuj jeśli używasz SSL)
# RewriteCond %{HTTPS} off
# RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Jeśli plik lub katalog istnieje, wyświetl go
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# W przeciwnym razie przekieruj do index.php
RewriteRule ^([a-zA-Z0-9-_]+)/?$ index.php?page=$1 [L,QSA]
# Zabezpieczenia
<FilesMatch "\.(htaccess|htpasswd|ini|log|sh|sql)$">
Order Allow,Deny
Deny from all
</FilesMatch>
# Ochrona plików konfiguracyjnych
<Files "config/database.php">
Order Allow,Deny
Deny from all
</Files>
# Włącz kompresję
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript
</IfModule>
# Cache przeglądarki
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
</IfModule>
# Zwiększ limit uploadów (jeśli możliwe)
php_value upload_max_filesize 10M
php_value post_max_size 10M
php_value max_execution_time 300
php_value max_input_time 300

384
admin/categories.php Normal file
View File

@ -0,0 +1,384 @@
<?php
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
$message = '';
$db = getDB();
// Obsługa akcji
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action'])) {
switch ($_POST['action']) {
case 'save':
$slug = !empty($_POST['slug']) ? $_POST['slug'] : createSlug($_POST['name']);
if (isset($_POST['id']) && $_POST['id']) {
// Aktualizacja
$stmt = $db->prepare("
UPDATE categories
SET name = ?, slug = ?, description = ?, parent_id = ?
WHERE id = ?
");
$parentId = !empty($_POST['parent_id']) ? $_POST['parent_id'] : null;
if ($stmt->execute([$_POST['name'], $slug, $_POST['description'], $parentId, $_POST['id']])) {
$message = 'Kategoria została zaktualizowana';
logActivity('category_updated', 'category', $_POST['id']);
}
} else {
// Nowa kategoria
$stmt = $db->prepare("
INSERT INTO categories (name, slug, description, parent_id)
VALUES (?, ?, ?, ?)
");
$parentId = !empty($_POST['parent_id']) ? $_POST['parent_id'] : null;
if ($stmt->execute([$_POST['name'], $slug, $_POST['description'], $parentId])) {
$message = 'Kategoria została dodana';
logActivity('category_created', 'category', $db->lastInsertId());
}
}
break;
case 'delete':
$stmt = $db->prepare("DELETE FROM categories WHERE id = ?");
if ($stmt->execute([$_POST['id']])) {
$message = 'Kategoria została usunięta';
logActivity('category_deleted', 'category', $_POST['id']);
}
break;
}
}
}
// Pobierz wszystkie kategorie
$stmt = $db->query("
SELECT c.*, parent.name as parent_name,
(SELECT COUNT(*) FROM page_categories WHERE category_id = c.id) as page_count
FROM categories c
LEFT JOIN categories parent ON c.parent_id = parent.id
ORDER BY c.name
");
$categories = $stmt->fetchAll();
// Tryb edycji
$editCategory = null;
if (isset($_GET['edit'])) {
$stmt = $db->prepare("SELECT * FROM categories WHERE id = ?");
$stmt->execute([$_GET['edit']]);
$editCategory = $stmt->fetch();
}
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kategorie - Panel CMS</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
}
.header {
background: #2c3e50;
color: white;
padding: 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
height: 60px;
}
.sidebar {
position: fixed;
left: 0;
top: 60px;
width: 250px;
background: white;
height: calc(100vh - 60px);
border-right: 1px solid #e0e0e0;
overflow-y: auto;
}
.sidebar nav a {
display: block;
padding: 15px 20px;
color: #333;
text-decoration: none;
border-left: 3px solid transparent;
}
.sidebar nav a:hover, .sidebar nav a.active {
background: #f0f4ff;
border-left-color: #667eea;
color: #667eea;
}
.main-content {
margin-left: 250px;
padding: 30px;
}
.btn {
display: inline-block;
padding: 10px 20px;
background: #667eea;
color: white;
text-decoration: none;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
}
.btn:hover { background: #5568d3; }
.btn-sm { padding: 6px 12px; font-size: 13px; }
.btn-danger { background: #e74c3c; }
.btn-danger:hover { background: #c0392b; }
.card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
margin-bottom: 20px;
}
.card-header {
padding: 20px;
border-bottom: 1px solid #e0e0e0;
}
.card-body { padding: 20px; }
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
.form-group input, .form-group textarea, .form-group select {
width: 100%;
padding: 10px;
border: 2px solid #e1e8ed;
border-radius: 5px;
font-size: 14px;
}
.form-group textarea {
min-height: 100px;
font-family: inherit;
}
.form-group small {
display: block;
margin-top: 5px;
color: #666;
font-size: 13px;
}
table {
width: 100%;
border-collapse: collapse;
}
table th, table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e0e0e0;
}
table th {
background: #f8f9fa;
font-weight: 600;
}
.message {
padding: 12px 20px;
background: #d4edda;
color: #155724;
border-radius: 5px;
margin-bottom: 20px;
}
.badge {
display: inline-block;
padding: 4px 10px;
background: #667eea;
color: white;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
.category-tree {
padding-left: 20px;
}
</style>
</head>
<body>
<div class="header">
<h1>Panel CMS</h1>
<a href="index.php" class="btn btn-sm"> Powrót</a>
</div>
<div class="sidebar">
<nav>
<a href="index.php">📊 Dashboard</a>
<a href="pages.php">📄 Strony</a>
<a href="media.php">🖼️ Media</a>
<a href="menus.php">🔗 Menu</a>
<a href="categories.php" class="active">📁 Kategorie</a>
<?php if (hasRole('admin')): ?>
<a href="users.php">👥 Użytkownicy</a>
<a href="settings.php">⚙️ Ustawienia</a>
<?php endif; ?>
</nav>
</div>
<div class="main-content">
<h1 style="margin-bottom: 30px;">Zarządzanie kategoriami</h1>
<?php if ($message): ?>
<div class="message"><?php echo escape($message); ?></div>
<?php endif; ?>
<!-- Formularz dodawania/edycji -->
<div class="card">
<div class="card-header">
<h2><?php echo $editCategory ? 'Edytuj kategorię' : 'Dodaj nową kategorię'; ?></h2>
</div>
<div class="card-body">
<form method="POST">
<input type="hidden" name="action" value="save">
<?php if ($editCategory): ?>
<input type="hidden" name="id" value="<?php echo $editCategory['id']; ?>">
<?php endif; ?>
<div class="form-group">
<label>Nazwa kategorii *</label>
<input type="text" name="name" required
value="<?php echo escape($editCategory['name'] ?? ''); ?>">
</div>
<div class="form-group">
<label>Slug (adres URL)</label>
<input type="text" name="slug"
value="<?php echo escape($editCategory['slug'] ?? ''); ?>">
<small>Zostaw puste, aby wygenerować automatycznie z nazwy</small>
</div>
<div class="form-group">
<label>Opis</label>
<textarea name="description"><?php echo escape($editCategory['description'] ?? ''); ?></textarea>
<small>Opcjonalny opis kategorii</small>
</div>
<div class="form-group">
<label>Kategoria nadrzędna</label>
<select name="parent_id">
<option value="">-- Brak (kategoria główna) --</option>
<?php foreach ($categories as $cat): ?>
<?php if (!$editCategory || $cat['id'] != $editCategory['id']): ?>
<option value="<?php echo $cat['id']; ?>"
<?php echo ($editCategory['parent_id'] ?? '') == $cat['id'] ? 'selected' : ''; ?>>
<?php echo escape($cat['name']); ?>
</option>
<?php endif; ?>
<?php endforeach; ?>
</select>
<small>Wybierz kategorię nadrzędną, aby utworzyć podkategorię</small>
</div>
<button type="submit" class="btn">
<?php echo $editCategory ? 'Zaktualizuj kategorię' : 'Dodaj kategorię'; ?>
</button>
<?php if ($editCategory): ?>
<a href="categories.php" class="btn" style="background: #6c757d;">Anuluj</a>
<?php endif; ?>
</form>
</div>
</div>
<!-- Lista kategorii -->
<div class="card">
<div class="card-header">
<h2>Wszystkie kategorie (<?php echo count($categories); ?>)</h2>
</div>
<div class="card-body">
<?php if (empty($categories)): ?>
<p style="color: #999; text-align: center; padding: 20px;">
Brak kategorii. Dodaj pierwszą kategorię powyżej.
</p>
<?php else: ?>
<table>
<thead>
<tr>
<th>Nazwa</th>
<th>Slug</th>
<th>Kategoria nadrzędna</th>
<th>Liczba stron</th>
<th>Data utworzenia</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
<?php foreach ($categories as $category): ?>
<tr>
<td>
<?php if ($category['parent_id']): ?>
<span style="color: #999;"></span>
<?php endif; ?>
<strong><?php echo escape($category['name']); ?></strong>
</td>
<td>
<code style="font-size: 12px; background: #f5f5f5; padding: 2px 6px; border-radius: 3px;">
<?php echo escape($category['slug']); ?>
</code>
</td>
<td>
<?php if ($category['parent_name']): ?>
<?php echo escape($category['parent_name']); ?>
<?php else: ?>
<span style="color: #999;">--</span>
<?php endif; ?>
</td>
<td>
<?php if ($category['page_count'] > 0): ?>
<span class="badge"><?php echo $category['page_count']; ?></span>
<?php else: ?>
<span style="color: #999;">0</span>
<?php endif; ?>
</td>
<td><?php echo formatDate($category['created_at'], 'd.m.Y'); ?></td>
<td>
<a href="?edit=<?php echo $category['id']; ?>" class="btn btn-sm">Edytuj</a>
<form method="POST" style="display: inline;">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?php echo $category['id']; ?>">
<button type="submit" class="btn btn-sm btn-danger"
onclick="return confirm('Czy na pewno chcesz usunąć tę kategorię?')">
Usuń
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
</div>
</body>
</html>

265
admin/index.php Normal file
View File

@ -0,0 +1,265 @@
<?php
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
$user = getCurrentUser();
$db = getDB();
// Statystyki
$stats = [
'pages' => $db->query("SELECT COUNT(*) as count FROM pages")->fetch()['count'],
'published' => $db->query("SELECT COUNT(*) as count FROM pages WHERE status = 'published'")->fetch()['count'],
'users' => $db->query("SELECT COUNT(*) as count FROM users")->fetch()['count'],
'media' => $db->query("SELECT COUNT(*) as count FROM media")->fetch()['count']
];
// Ostatnie strony
$recentPages = getAllPages('published', 5);
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Panel administracyjny - CMS</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
}
.header {
background: #2c3e50;
color: white;
padding: 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
height: 60px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.header h1 {
font-size: 20px;
}
.user-menu {
display: flex;
align-items: center;
gap: 15px;
}
.sidebar {
position: fixed;
left: 0;
top: 60px;
width: 250px;
background: white;
height: calc(100vh - 60px);
border-right: 1px solid #e0e0e0;
overflow-y: auto;
}
.sidebar nav a {
display: block;
padding: 15px 20px;
color: #333;
text-decoration: none;
border-left: 3px solid transparent;
transition: all 0.3s;
}
.sidebar nav a:hover {
background: #f5f5f5;
border-left-color: #667eea;
}
.sidebar nav a.active {
background: #f0f4ff;
border-left-color: #667eea;
color: #667eea;
font-weight: 600;
}
.main-content {
margin-left: 250px;
padding: 30px;
min-height: calc(100vh - 60px);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: white;
padding: 25px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.stat-card h3 {
color: #666;
font-size: 14px;
font-weight: 500;
margin-bottom: 10px;
}
.stat-card .number {
font-size: 32px;
font-weight: 700;
color: #667eea;
}
.card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
margin-bottom: 20px;
}
.card-header {
padding: 20px;
border-bottom: 1px solid #e0e0e0;
}
.card-header h2 {
font-size: 18px;
color: #333;
}
.card-body {
padding: 20px;
}
.page-list {
list-style: none;
}
.page-item {
padding: 15px 0;
border-bottom: 1px solid #f0f0f0;
}
.page-item:last-child {
border-bottom: none;
}
.page-item h4 {
color: #333;
margin-bottom: 5px;
}
.page-item small {
color: #999;
}
.btn {
display: inline-block;
padding: 10px 20px;
background: #667eea;
color: white;
text-decoration: none;
border-radius: 5px;
font-weight: 500;
transition: background 0.3s;
}
.btn:hover {
background: #5568d3;
}
.btn-sm {
padding: 6px 12px;
font-size: 13px;
}
</style>
</head>
<body>
<div class="header">
<h1>🚀 Panel CMS</h1>
<div class="user-menu">
<span>Witaj, <strong><?php echo escape($user['username']); ?></strong></span>
<a href="logout.php" class="btn btn-sm">Wyloguj</a>
</div>
</div>
<div class="sidebar">
<nav>
<a href="index.php" class="active">📊 Dashboard</a>
<a href="pages.php">📄 Strony</a>
<a href="media.php">🖼️ Media</a>
<a href="menus.php">🔗 Menu</a>
<a href="categories.php">📁 Kategorie</a>
<?php if (hasRole('admin')): ?>
<a href="users.php">👥 Użytkownicy</a>
<a href="settings.php">⚙️ Ustawienia</a>
<?php endif; ?>
</nav>
</div>
<div class="main-content">
<h1 style="margin-bottom: 30px;">Dashboard</h1>
<div class="stats-grid">
<div class="stat-card">
<h3>Wszystkie strony</h3>
<div class="number"><?php echo $stats['pages']; ?></div>
</div>
<div class="stat-card">
<h3>Opublikowane</h3>
<div class="number"><?php echo $stats['published']; ?></div>
</div>
<div class="stat-card">
<h3>Użytkownicy</h3>
<div class="number"><?php echo $stats['users']; ?></div>
</div>
<div class="stat-card">
<h3>Pliki</h3>
<div class="number"><?php echo $stats['media']; ?></div>
</div>
</div>
<div class="card">
<div class="card-header">
<h2>Ostatnie strony</h2>
</div>
<div class="card-body">
<?php if (empty($recentPages)): ?>
<p style="color: #999;">Brak opublikowanych stron</p>
<?php else: ?>
<ul class="page-list">
<?php foreach ($recentPages as $page): ?>
<li class="page-item">
<h4><?php echo escape($page['title']); ?></h4>
<small>
Autor: <?php echo escape($page['author_name']); ?> |
Data: <?php echo formatDate($page['created_at']); ?>
</small>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<div style="margin-top: 20px;">
<a href="pages.php" class="btn">Zobacz wszystkie strony</a>
</div>
</div>
</div>
</div>
</body>
</html>

157
admin/login.php Normal file
View File

@ -0,0 +1,157 @@
<?php
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/functions.php';
// Jeśli już zalogowany, przekieruj do panelu
if (isLoggedIn()) {
header('Location: index.php');
exit;
}
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$result = login($username, $password);
if ($result['success']) {
header('Location: index.php');
exit;
} else {
$error = $result['message'];
}
}
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Logowanie - Panel CMS</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.login-container {
background: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
width: 100%;
max-width: 400px;
}
.login-container h1 {
color: #333;
margin-bottom: 10px;
font-size: 28px;
}
.login-container p {
color: #666;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #333;
font-weight: 500;
}
.form-group input {
width: 100%;
padding: 12px;
border: 2px solid #e1e8ed;
border-radius: 5px;
font-size: 14px;
transition: border-color 0.3s;
}
.form-group input:focus {
outline: none;
border-color: #667eea;
}
.btn {
width: 100%;
padding: 12px;
background: #667eea;
color: white;
border: none;
border-radius: 5px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: background 0.3s;
}
.btn:hover {
background: #5568d3;
}
.error {
background: #fee;
color: #c33;
padding: 12px;
border-radius: 5px;
margin-bottom: 20px;
border-left: 4px solid #c33;
}
.footer {
text-align: center;
margin-top: 20px;
color: #666;
font-size: 14px;
}
</style>
</head>
<body>
<div class="login-container">
<h1>Panel CMS</h1>
<p>Zaloguj się do systemu</p>
<?php if ($error): ?>
<div class="error"><?php echo escape($error); ?></div>
<?php endif; ?>
<form method="POST" action="">
<div class="form-group">
<label for="username">Nazwa użytkownika</label>
<input type="text" id="username" name="username" required autofocus>
</div>
<div class="form-group">
<label for="password">Hasło</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit" class="btn">Zaloguj się</button>
</form>
<div class="footer">
<small>Domyślne dane: admin / admin123</small>
</div>
</div>
</body>
</html>

8
admin/logout.php Normal file
View File

@ -0,0 +1,8 @@
<?php
require_once __DIR__ . '/../includes/auth.php';
logout();
header('Location: login.php?message=logged_out');
exit;
?>

356
admin/media.php Normal file
View File

@ -0,0 +1,356 @@
<?php
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
$message = '';
$error = '';
$db = getDB();
// Obsługa uploadu
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
$result = uploadFile($_FILES['file']);
if ($result['success']) {
$message = 'Plik został przesłany pomyślnie';
logActivity('media_uploaded', 'media', $result['id']);
} else {
$error = $result['message'];
}
}
// Obsługa usuwania
if (isset($_POST['delete_media'])) {
$mediaId = $_POST['media_id'];
// Pobierz informacje o pliku
$stmt = $db->prepare("SELECT * FROM media WHERE id = ?");
$stmt->execute([$mediaId]);
$media = $stmt->fetch();
if ($media) {
// Usuń plik z dysku
if (file_exists($media['file_path'])) {
unlink($media['file_path']);
}
// Usuń z bazy
$stmt = $db->prepare("DELETE FROM media WHERE id = ?");
if ($stmt->execute([$mediaId])) {
$message = 'Plik został usunięty';
logActivity('media_deleted', 'media', $mediaId);
}
}
}
// Pobierz wszystkie media
$stmt = $db->query("
SELECT m.*, u.username as uploaded_by_name
FROM media m
LEFT JOIN users u ON m.uploaded_by = u.id
ORDER BY m.created_at DESC
");
$mediaFiles = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Zarządzanie mediami - Panel CMS</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
}
.header {
background: #2c3e50;
color: white;
padding: 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
height: 60px;
}
.sidebar {
position: fixed;
left: 0;
top: 60px;
width: 250px;
background: white;
height: calc(100vh - 60px);
border-right: 1px solid #e0e0e0;
overflow-y: auto;
}
.sidebar nav a {
display: block;
padding: 15px 20px;
color: #333;
text-decoration: none;
border-left: 3px solid transparent;
}
.sidebar nav a:hover, .sidebar nav a.active {
background: #f0f4ff;
border-left-color: #667eea;
color: #667eea;
}
.main-content {
margin-left: 250px;
padding: 30px;
}
.btn {
display: inline-block;
padding: 10px 20px;
background: #667eea;
color: white;
text-decoration: none;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
}
.btn:hover { background: #5568d3; }
.btn-sm { padding: 6px 12px; font-size: 13px; }
.btn-danger { background: #e74c3c; }
.btn-danger:hover { background: #c0392b; }
.card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
margin-bottom: 20px;
}
.card-header {
padding: 20px;
border-bottom: 1px solid #e0e0e0;
}
.card-body { padding: 20px; }
.upload-area {
border: 2px dashed #667eea;
border-radius: 8px;
padding: 40px;
text-align: center;
background: #f8f9ff;
cursor: pointer;
transition: all 0.3s;
}
.upload-area:hover {
background: #f0f4ff;
border-color: #5568d3;
}
.upload-area input[type="file"] {
display: none;
}
.media-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 20px;
margin-top: 20px;
}
.media-item {
background: white;
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
transition: transform 0.2s;
}
.media-item:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.media-thumbnail {
width: 100%;
height: 150px;
object-fit: cover;
background: #f5f5f5;
}
.media-info {
padding: 12px;
}
.media-info h4 {
font-size: 14px;
margin-bottom: 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.media-info small {
display: block;
color: #666;
font-size: 12px;
margin-bottom: 4px;
}
.media-actions {
display: flex;
gap: 5px;
margin-top: 10px;
}
.message {
padding: 12px 20px;
background: #d4edda;
color: #155724;
border-radius: 5px;
margin-bottom: 20px;
}
.error {
padding: 12px 20px;
background: #f8d7da;
color: #721c24;
border-radius: 5px;
margin-bottom: 20px;
}
.file-icon {
display: flex;
align-items: center;
justify-content: center;
height: 150px;
background: #f5f5f5;
font-size: 48px;
}
</style>
</head>
<body>
<div class="header">
<h1>Panel CMS</h1>
<a href="index.php" class="btn btn-sm"> Powrót</a>
</div>
<div class="sidebar">
<nav>
<a href="index.php">📊 Dashboard</a>
<a href="pages.php">📄 Strony</a>
<a href="media.php" class="active">🖼️ Media</a>
<a href="settings.php">⚙️ Ustawienia</a>
</nav>
</div>
<div class="main-content">
<h1 style="margin-bottom: 30px;">Zarządzanie mediami</h1>
<?php if ($message): ?>
<div class="message"><?php echo escape($message); ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error"><?php echo escape($error); ?></div>
<?php endif; ?>
<!-- Upload -->
<div class="card">
<div class="card-header">
<h2>Prześlij nowy plik</h2>
</div>
<div class="card-body">
<form method="POST" enctype="multipart/form-data" id="uploadForm">
<div class="upload-area" onclick="document.getElementById('fileInput').click()">
<div style="font-size: 48px; margin-bottom: 10px;">📁</div>
<p style="color: #666; margin-bottom: 10px;">
Kliknij lub przeciągnij pliki tutaj
</p>
<small style="color: #999;">
Dozwolone typy: JPG, PNG, GIF (max 5MB)
</small>
<input type="file" id="fileInput" name="file" accept="image/*"
onchange="document.getElementById('uploadForm').submit()">
</div>
</form>
</div>
</div>
<!-- Biblioteka mediów -->
<div class="card">
<div class="card-header">
<h2>Biblioteka mediów (<?php echo count($mediaFiles); ?> plików)</h2>
</div>
<div class="card-body">
<?php if (empty($mediaFiles)): ?>
<p style="color: #999; text-align: center; padding: 40px;">
Brak plików w bibliotece. Prześlij pierwszy plik powyżej.
</p>
<?php else: ?>
<div class="media-grid">
<?php foreach ($mediaFiles as $media): ?>
<div class="media-item">
<?php if (strpos($media['mime_type'], 'image/') === 0): ?>
<img src="<?php echo UPLOAD_URL . escape($media['filename']); ?>"
alt="<?php echo escape($media['alt_text'] ?? $media['original_name']); ?>"
class="media-thumbnail">
<?php else: ?>
<div class="file-icon">📄</div>
<?php endif; ?>
<div class="media-info">
<h4 title="<?php echo escape($media['original_name']); ?>">
<?php echo escape($media['original_name']); ?>
</h4>
<small>
<?php echo round($media['file_size'] / 1024, 2); ?> KB
</small>
<small>
<?php echo formatDate($media['created_at'], 'd.m.Y'); ?>
</small>
<small>
przez <?php echo escape($media['uploaded_by_name']); ?>
</small>
<div class="media-actions">
<button class="btn btn-sm"
onclick="copyToClipboard('<?php echo UPLOAD_URL . escape($media['filename']); ?>')"
title="Kopiuj URL">
📋
</button>
<form method="POST" style="display: inline;"
onsubmit="return confirm('Czy na pewno chcesz usunąć ten plik?')">
<input type="hidden" name="delete_media" value="1">
<input type="hidden" name="media_id" value="<?php echo $media['id']; ?>">
<button type="submit" class="btn btn-sm btn-danger" title="Usuń">
🗑️
</button>
</form>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
</div>
<script>
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
alert('URL skopiowany do schowka: ' + text);
}).catch(err => {
console.error('Błąd kopiowania: ', err);
});
}
</script>
</body>
</html>

500
admin/menus.php Normal file
View File

@ -0,0 +1,500 @@
<?php
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
$message = '';
$db = getDB();
// Obsługa akcji
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action'])) {
switch ($_POST['action']) {
case 'create_menu':
$stmt = $db->prepare("INSERT INTO menus (name, location) VALUES (?, ?)");
if ($stmt->execute([$_POST['name'], $_POST['location']])) {
$message = 'Menu zostało utworzone';
logActivity('menu_created', 'menu', $db->lastInsertId());
}
break;
case 'delete_menu':
$stmt = $db->prepare("DELETE FROM menus WHERE id = ?");
if ($stmt->execute([$_POST['menu_id']])) {
$message = 'Menu zostało usunięte';
logActivity('menu_deleted', 'menu', $_POST['menu_id']);
}
break;
case 'add_menu_item':
$stmt = $db->prepare("
INSERT INTO menu_items (menu_id, title, url, page_id, parent_id, sort_order, target, css_class)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
");
$pageId = !empty($_POST['page_id']) ? $_POST['page_id'] : null;
$parentId = !empty($_POST['parent_id']) ? $_POST['parent_id'] : null;
if ($stmt->execute([
$_POST['menu_id'],
$_POST['title'],
$_POST['url'],
$pageId,
$parentId,
$_POST['sort_order'] ?? 0,
$_POST['target'] ?? '_self',
$_POST['css_class'] ?? ''
])) {
$message = 'Element menu został dodany';
logActivity('menu_item_created', 'menu_item', $db->lastInsertId());
}
break;
case 'delete_menu_item':
$stmt = $db->prepare("DELETE FROM menu_items WHERE id = ?");
if ($stmt->execute([$_POST['item_id']])) {
$message = 'Element menu został usunięty';
logActivity('menu_item_deleted', 'menu_item', $_POST['item_id']);
}
break;
}
}
}
// Pobierz wszystkie menu
$menus = $db->query("SELECT * FROM menus ORDER BY name")->fetchAll();
// Pobierz wszystkie strony do wyboru
$pages = $db->query("SELECT id, title FROM pages WHERE status = 'published' ORDER BY title")->fetchAll();
// Wybrane menu do edycji
$selectedMenuId = $_GET['menu_id'] ?? ($menus[0]['id'] ?? null);
$menuItems = [];
if ($selectedMenuId) {
$stmt = $db->prepare("
SELECT mi.*, p.title as page_title
FROM menu_items mi
LEFT JOIN pages p ON mi.page_id = p.id
WHERE mi.menu_id = ?
ORDER BY mi.sort_order, mi.id
");
$stmt->execute([$selectedMenuId]);
$menuItems = $stmt->fetchAll();
}
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Zarządzanie menu - Panel CMS</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
}
.header {
background: #2c3e50;
color: white;
padding: 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
height: 60px;
}
.sidebar {
position: fixed;
left: 0;
top: 60px;
width: 250px;
background: white;
height: calc(100vh - 60px);
border-right: 1px solid #e0e0e0;
overflow-y: auto;
}
.sidebar nav a {
display: block;
padding: 15px 20px;
color: #333;
text-decoration: none;
border-left: 3px solid transparent;
}
.sidebar nav a:hover, .sidebar nav a.active {
background: #f0f4ff;
border-left-color: #667eea;
color: #667eea;
}
.main-content {
margin-left: 250px;
padding: 30px;
}
.btn {
display: inline-block;
padding: 10px 20px;
background: #667eea;
color: white;
text-decoration: none;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
}
.btn:hover { background: #5568d3; }
.btn-sm { padding: 6px 12px; font-size: 13px; }
.btn-danger { background: #e74c3c; }
.btn-danger:hover { background: #c0392b; }
.card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
margin-bottom: 20px;
}
.card-header {
padding: 20px;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
}
.card-body { padding: 20px; }
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
.form-group input, .form-group select {
width: 100%;
padding: 10px;
border: 2px solid #e1e8ed;
border-radius: 5px;
font-size: 14px;
}
.form-group small {
display: block;
margin-top: 5px;
color: #666;
font-size: 13px;
}
table {
width: 100%;
border-collapse: collapse;
}
table th, table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e0e0e0;
}
table th {
background: #f8f9fa;
font-weight: 600;
}
.message {
padding: 12px 20px;
background: #d4edda;
color: #155724;
border-radius: 5px;
margin-bottom: 20px;
}
.menu-tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.menu-tab {
padding: 10px 20px;
background: white;
border: 2px solid #e0e0e0;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s;
}
.menu-tab.active {
background: #667eea;
color: white;
border-color: #667eea;
}
.menu-tab:hover {
border-color: #667eea;
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.menu-item-row {
background: #f8f9fa;
padding: 10px;
margin-bottom: 5px;
border-radius: 5px;
display: flex;
justify-content: space-between;
align-items: center;
}
.menu-item-row.child {
margin-left: 30px;
background: #e9ecef;
}
</style>
</head>
<body>
<div class="header">
<h1>Panel CMS</h1>
<a href="index.php" class="btn btn-sm"> Powrót</a>
</div>
<div class="sidebar">
<nav>
<a href="index.php">📊 Dashboard</a>
<a href="pages.php">📄 Strony</a>
<a href="media.php">🖼️ Media</a>
<a href="menus.php" class="active">🔗 Menu</a>
<a href="categories.php">📁 Kategorie</a>
<?php if (hasRole('admin')): ?>
<a href="users.php">👥 Użytkownicy</a>
<a href="settings.php">⚙️ Ustawienia</a>
<?php endif; ?>
</nav>
</div>
<div class="main-content">
<h1 style="margin-bottom: 30px;">Zarządzanie menu</h1>
<?php if ($message): ?>
<div class="message"><?php echo escape($message); ?></div>
<?php endif; ?>
<!-- Tworzenie nowego menu -->
<div class="card">
<div class="card-header">
<h2>Utwórz nowe menu</h2>
</div>
<div class="card-body">
<form method="POST">
<input type="hidden" name="action" value="create_menu">
<div class="grid-2">
<div class="form-group">
<label>Nazwa menu</label>
<input type="text" name="name" required placeholder="np. Menu główne">
</div>
<div class="form-group">
<label>Lokalizacja</label>
<select name="location" required>
<option value="header">Nagłówek (header)</option>
<option value="footer">Stopka (footer)</option>
<option value="sidebar">Pasek boczny (sidebar)</option>
<option value="mobile">Menu mobilne (mobile)</option>
</select>
</div>
</div>
<button type="submit" class="btn">Utwórz menu</button>
</form>
</div>
</div>
<?php if (!empty($menus)): ?>
<!-- Zakładki menu -->
<div class="menu-tabs">
<?php foreach ($menus as $menu): ?>
<a href="?menu_id=<?php echo $menu['id']; ?>"
class="menu-tab <?php echo $selectedMenuId == $menu['id'] ? 'active' : ''; ?>">
<?php echo escape($menu['name']); ?> (<?php echo escape($menu['location']); ?>)
</a>
<?php endforeach; ?>
</div>
<?php if ($selectedMenuId): ?>
<!-- Dodawanie elementu menu -->
<div class="card">
<div class="card-header">
<h2>Dodaj element do menu</h2>
</div>
<div class="card-body">
<form method="POST">
<input type="hidden" name="action" value="add_menu_item">
<input type="hidden" name="menu_id" value="<?php echo $selectedMenuId; ?>">
<div class="grid-2">
<div class="form-group">
<label>Tytuł</label>
<input type="text" name="title" required placeholder="Tekst wyświetlany w menu">
</div>
<div class="form-group">
<label>Link do strony</label>
<select name="page_id">
<option value="">-- Wybierz stronę lub podaj URL --</option>
<?php foreach ($pages as $page): ?>
<option value="<?php echo $page['id']; ?>">
<?php echo escape($page['title']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="grid-2">
<div class="form-group">
<label>Własny URL (opcjonalnie)</label>
<input type="text" name="url" placeholder="https://example.com">
<small>Zostaw puste jeśli wybrano stronę powyżej</small>
</div>
<div class="form-group">
<label>Element nadrzędny</label>
<select name="parent_id">
<option value="">-- Brak (element główny) --</option>
<?php foreach ($menuItems as $item): ?>
<?php if (!$item['parent_id']): ?>
<option value="<?php echo $item['id']; ?>">
<?php echo escape($item['title']); ?>
</option>
<?php endif; ?>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="grid-2">
<div class="form-group">
<label>Kolejność</label>
<input type="number" name="sort_order" value="0" min="0">
</div>
<div class="form-group">
<label>Cel otwarcia</label>
<select name="target">
<option value="_self">Ta sama karta</option>
<option value="_blank">Nowa karta</option>
</select>
</div>
</div>
<button type="submit" class="btn">Dodaj element</button>
</form>
</div>
</div>
<!-- Lista elementów menu -->
<div class="card">
<div class="card-header">
<h2>Elementy menu</h2>
<form method="POST" style="display: inline;">
<input type="hidden" name="action" value="delete_menu">
<input type="hidden" name="menu_id" value="<?php echo $selectedMenuId; ?>">
<button type="submit" class="btn btn-sm btn-danger"
onclick="return confirm('Czy na pewno chcesz usunąć to menu i wszystkie jego elementy?')">
Usuń całe menu
</button>
</form>
</div>
<div class="card-body">
<?php if (empty($menuItems)): ?>
<p style="color: #999; text-align: center; padding: 20px;">
To menu nie ma jeszcze żadnych elementów
</p>
<?php else: ?>
<?php
// Najpierw wyświetl elementy główne
foreach ($menuItems as $item):
if ($item['parent_id']) continue;
?>
<div class="menu-item-row">
<div>
<strong><?php echo escape($item['title']); ?></strong><br>
<small style="color: #666;">
<?php if ($item['page_id']): ?>
Strona: <?php echo escape($item['page_title']); ?>
<?php else: ?>
URL: <?php echo escape($item['url']); ?>
<?php endif; ?>
| Kolejność: <?php echo $item['sort_order']; ?>
</small>
</div>
<form method="POST" style="display: inline;">
<input type="hidden" name="action" value="delete_menu_item">
<input type="hidden" name="item_id" value="<?php echo $item['id']; ?>">
<button type="submit" class="btn btn-sm btn-danger"
onclick="return confirm('Usunąć ten element?')">
Usuń
</button>
</form>
</div>
<?php
// Wyświetl elementy podrzędne
foreach ($menuItems as $child):
if ($child['parent_id'] == $item['id']):
?>
<div class="menu-item-row child">
<div>
<strong><?php echo escape($child['title']); ?></strong><br>
<small style="color: #666;">
<?php if ($child['page_id']): ?>
Strona: <?php echo escape($child['page_title']); ?>
<?php else: ?>
URL: <?php echo escape($child['url']); ?>
<?php endif; ?>
</small>
</div>
<form method="POST" style="display: inline;">
<input type="hidden" name="action" value="delete_menu_item">
<input type="hidden" name="item_id" value="<?php echo $child['id']; ?>">
<button type="submit" class="btn btn-sm btn-danger"
onclick="return confirm('Usunąć ten element?')">
Usuń
</button>
</form>
</div>
<?php
endif;
endforeach;
?>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<?php else: ?>
<div class="card">
<div class="card-body" style="text-align: center; padding: 40px; color: #999;">
Nie masz jeszcze żadnych menu. Utwórz pierwsze menu powyżej.
</div>
</div>
<?php endif; ?>
</div>
</body>
</html>

352
admin/pages.php Normal file
View File

@ -0,0 +1,352 @@
<?php
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/functions.php';
requireLogin();
$message = '';
$db = getDB();
// Obsługa akcji
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action'])) {
switch ($_POST['action']) {
case 'delete':
if (isset($_POST['id'])) {
deletePage($_POST['id']);
logActivity('page_deleted', 'page', $_POST['id']);
$message = 'Strona została usunięta';
}
break;
case 'save':
$data = [
'id' => $_POST['id'] ?? null,
'title' => $_POST['title'],
'slug' => $_POST['slug'],
'content' => $_POST['content'],
'meta_description' => $_POST['meta_description'],
'status' => $_POST['status'],
'template' => $_POST['template'],
'author_id' => $_SESSION['user_id']
];
if (savePage($data)) {
$action = $data['id'] ? 'page_updated' : 'page_created';
logActivity($action, 'page', $data['id']);
$message = 'Strona została zapisana';
} else {
$message = 'Błąd podczas zapisywania strony';
}
break;
}
}
}
// Pobierz wszystkie strony
$stmt = $db->query("
SELECT p.*, u.username as author_name
FROM pages p
LEFT JOIN users u ON p.author_id = u.id
ORDER BY p.created_at DESC
");
$pages = $stmt->fetchAll();
// Tryb edycji
$editPage = null;
if (isset($_GET['edit'])) {
$stmt = $db->prepare("SELECT * FROM pages WHERE id = ?");
$stmt->execute([$_GET['edit']]);
$editPage = $stmt->fetch();
}
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Zarządzanie stronami - Panel CMS</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
}
.header {
background: #2c3e50;
color: white;
padding: 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
height: 60px;
}
.sidebar {
position: fixed;
left: 0;
top: 60px;
width: 250px;
background: white;
height: calc(100vh - 60px);
border-right: 1px solid #e0e0e0;
overflow-y: auto;
}
.sidebar nav a {
display: block;
padding: 15px 20px;
color: #333;
text-decoration: none;
border-left: 3px solid transparent;
}
.sidebar nav a:hover, .sidebar nav a.active {
background: #f0f4ff;
border-left-color: #667eea;
color: #667eea;
}
.main-content {
margin-left: 250px;
padding: 30px;
}
.btn {
display: inline-block;
padding: 10px 20px;
background: #667eea;
color: white;
text-decoration: none;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
}
.btn:hover { background: #5568d3; }
.btn-sm { padding: 6px 12px; font-size: 13px; }
.btn-danger { background: #e74c3c; }
.btn-danger:hover { background: #c0392b; }
.card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
margin-bottom: 20px;
}
.card-header {
padding: 20px;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
}
.card-body { padding: 20px; }
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
.form-group input, .form-group textarea, .form-group select {
width: 100%;
padding: 10px;
border: 2px solid #e1e8ed;
border-radius: 5px;
font-size: 14px;
}
.form-group textarea {
min-height: 200px;
font-family: inherit;
}
table {
width: 100%;
border-collapse: collapse;
}
table th, table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e0e0e0;
}
table th {
background: #f8f9fa;
font-weight: 600;
}
.status-badge {
display: inline-block;
padding: 4px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
.status-published {
background: #d4edda;
color: #155724;
}
.status-draft {
background: #fff3cd;
color: #856404;
}
.message {
padding: 12px 20px;
background: #d4edda;
color: #155724;
border-radius: 5px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="header">
<h1>Panel CMS</h1>
<a href="index.php" class="btn btn-sm"> Powrót</a>
</div>
<div class="sidebar">
<nav>
<a href="index.php">📊 Dashboard</a>
<a href="pages.php" class="active">📄 Strony</a>
<a href="media.php">🖼️ Media</a>
<a href="menus.php">🔗 Menu</a>
</nav>
</div>
<div class="main-content">
<h1 style="margin-bottom: 30px;">Zarządzanie stronami</h1>
<?php if ($message): ?>
<div class="message"><?php echo escape($message); ?></div>
<?php endif; ?>
<!-- Formularz dodawania/edycji -->
<div class="card">
<div class="card-header">
<h2><?php echo $editPage ? 'Edytuj stronę' : 'Dodaj nową stronę'; ?></h2>
</div>
<div class="card-body">
<form method="POST">
<input type="hidden" name="action" value="save">
<?php if ($editPage): ?>
<input type="hidden" name="id" value="<?php echo $editPage['id']; ?>">
<?php endif; ?>
<div class="form-group">
<label>Tytuł strony</label>
<input type="text" name="title" required
value="<?php echo escape($editPage['title'] ?? ''); ?>">
</div>
<div class="form-group">
<label>Slug (adres URL)</label>
<input type="text" name="slug"
value="<?php echo escape($editPage['slug'] ?? ''); ?>">
<small style="color: #666;">Zostaw puste, aby wygenerować automatycznie</small>
</div>
<div class="form-group">
<label>Treść</label>
<textarea name="content"><?php echo escape($editPage['content'] ?? ''); ?></textarea>
</div>
<div class="form-group">
<label>Meta opis</label>
<input type="text" name="meta_description"
value="<?php echo escape($editPage['meta_description'] ?? ''); ?>">
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<div class="form-group">
<label>Status</label>
<select name="status">
<option value="draft" <?php echo ($editPage['status'] ?? '') === 'draft' ? 'selected' : ''; ?>>Szkic</option>
<option value="published" <?php echo ($editPage['status'] ?? '') === 'published' ? 'selected' : ''; ?>>Opublikowana</option>
</select>
</div>
<div class="form-group">
<label>Szablon</label>
<select name="template">
<option value="default">Domyślny</option>
<option value="fullwidth">Pełna szerokość</option>
</select>
</div>
</div>
<button type="submit" class="btn">
<?php echo $editPage ? 'Zaktualizuj stronę' : 'Dodaj stronę'; ?>
</button>
<?php if ($editPage): ?>
<a href="pages.php" class="btn" style="background: #6c757d;">Anuluj</a>
<?php endif; ?>
</form>
</div>
</div>
<!-- Lista stron -->
<div class="card">
<div class="card-header">
<h2>Wszystkie strony</h2>
</div>
<div class="card-body">
<table>
<thead>
<tr>
<th>Tytuł</th>
<th>Slug</th>
<th>Autor</th>
<th>Status</th>
<th>Data utworzenia</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
<?php foreach ($pages as $page): ?>
<tr>
<td><?php echo escape($page['title']); ?></td>
<td><?php echo escape($page['slug']); ?></td>
<td><?php echo escape($page['author_name']); ?></td>
<td>
<span class="status-badge status-<?php echo $page['status']; ?>">
<?php echo $page['status'] === 'published' ? 'Opublikowana' : 'Szkic'; ?>
</span>
</td>
<td><?php echo formatDate($page['created_at'], 'd.m.Y'); ?></td>
<td>
<a href="?edit=<?php echo $page['id']; ?>" class="btn btn-sm">Edytuj</a>
<form method="POST" style="display: inline;">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?php echo $page['id']; ?>">
<button type="submit" class="btn btn-sm btn-danger"
onclick="return confirm('Czy na pewno chcesz usunąć tę stronę?')">
Usuń
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>

353
admin/settings.php Normal file
View File

@ -0,0 +1,353 @@
<?php
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/functions.php';
requireRole('admin'); // Tylko administrator
$message = '';
$db = getDB();
// Obsługa zapisu ustawień
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_settings'])) {
foreach ($_POST as $key => $value) {
if ($key !== 'save_settings' && $key !== 'csrf_token') {
updateSetting($key, $value);
}
}
logActivity('settings_updated');
$message = 'Ustawienia zostały zapisane';
}
// Pobierz wszystkie ustawienia
$stmt = $db->query("SELECT setting_key, setting_value FROM settings ORDER BY setting_key");
$settings = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ustawienia - Panel CMS</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
}
.header {
background: #2c3e50;
color: white;
padding: 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
height: 60px;
}
.sidebar {
position: fixed;
left: 0;
top: 60px;
width: 250px;
background: white;
height: calc(100vh - 60px);
border-right: 1px solid #e0e0e0;
overflow-y: auto;
}
.sidebar nav a {
display: block;
padding: 15px 20px;
color: #333;
text-decoration: none;
border-left: 3px solid transparent;
}
.sidebar nav a:hover, .sidebar nav a.active {
background: #f0f4ff;
border-left-color: #667eea;
color: #667eea;
}
.main-content {
margin-left: 250px;
padding: 30px;
}
.btn {
display: inline-block;
padding: 10px 20px;
background: #667eea;
color: white;
text-decoration: none;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
}
.btn:hover { background: #5568d3; }
.btn-sm { padding: 6px 12px; font-size: 13px; }
.card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
margin-bottom: 20px;
}
.card-header {
padding: 20px;
border-bottom: 1px solid #e0e0e0;
}
.card-body { padding: 20px; }
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
.form-group input, .form-group select, .form-group textarea {
width: 100%;
padding: 10px;
border: 2px solid #e1e8ed;
border-radius: 5px;
font-size: 14px;
}
.form-group small {
display: block;
margin-top: 5px;
color: #666;
font-size: 13px;
}
.settings-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.message {
padding: 12px 20px;
background: #d4edda;
color: #155724;
border-radius: 5px;
margin-bottom: 20px;
}
.settings-section {
margin-bottom: 30px;
}
.settings-section h3 {
color: #2c3e50;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #667eea;
}
@media (max-width: 768px) {
.settings-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="header">
<h1>Panel CMS</h1>
<a href="index.php" class="btn btn-sm"> Powrót</a>
</div>
<div class="sidebar">
<nav>
<a href="index.php">📊 Dashboard</a>
<a href="pages.php">📄 Strony</a>
<a href="media.php">🖼️ Media</a>
<a href="settings.php" class="active">⚙️ Ustawienia</a>
</nav>
</div>
<div class="main-content">
<h1 style="margin-bottom: 30px;">Ustawienia systemu</h1>
<?php if ($message): ?>
<div class="message"><?php echo escape($message); ?></div>
<?php endif; ?>
<form method="POST">
<input type="hidden" name="save_settings" value="1">
<!-- Ogólne -->
<div class="card">
<div class="card-header">
<h2>Ogólne ustawienia</h2>
</div>
<div class="card-body">
<div class="settings-section">
<h3>Podstawowe informacje</h3>
<div class="settings-grid">
<div class="form-group">
<label>Nazwa strony</label>
<input type="text" name="site_name"
value="<?php echo escape($settings['site_name'] ?? ''); ?>">
<small>Nazwa wyświetlana w nagłówku i tytule strony</small>
</div>
<div class="form-group">
<label>Email kontaktowy</label>
<input type="email" name="site_email"
value="<?php echo escape($settings['site_email'] ?? ''); ?>">
<small>Główny adres email strony</small>
</div>
</div>
<div class="form-group">
<label>Opis strony</label>
<textarea name="site_description" rows="3"><?php echo escape($settings['site_description'] ?? ''); ?></textarea>
<small>Krótki opis strony (używany w meta tagach)</small>
</div>
</div>
<div class="settings-section">
<h3>Regionalne</h3>
<div class="settings-grid">
<div class="form-group">
<label>Strefa czasowa</label>
<select name="timezone">
<option value="Europe/Warsaw" <?php echo ($settings['timezone'] ?? '') === 'Europe/Warsaw' ? 'selected' : ''; ?>>
Europe/Warsaw (UTC+1/+2)
</option>
<option value="UTC" <?php echo ($settings['timezone'] ?? '') === 'UTC' ? 'selected' : ''; ?>>
UTC (UTC+0)
</option>
<option value="Europe/London" <?php echo ($settings['timezone'] ?? '') === 'Europe/London' ? 'selected' : ''; ?>>
Europe/London (UTC+0/+1)
</option>
<option value="America/New_York" <?php echo ($settings['timezone'] ?? '') === 'America/New_York' ? 'selected' : ''; ?>>
America/New_York (UTC-5/-4)
</option>
</select>
</div>
<div class="form-group">
<label>Format daty</label>
<select name="date_format">
<option value="Y-m-d H:i:s" <?php echo ($settings['date_format'] ?? '') === 'Y-m-d H:i:s' ? 'selected' : ''; ?>>
2025-01-15 14:30:00
</option>
<option value="d.m.Y H:i" <?php echo ($settings['date_format'] ?? '') === 'd.m.Y H:i' ? 'selected' : ''; ?>>
15.01.2025 14:30
</option>
<option value="d/m/Y" <?php echo ($settings['date_format'] ?? '') === 'd/m/Y' ? 'selected' : ''; ?>>
15/01/2025
</option>
<option value="M d, Y" <?php echo ($settings['date_format'] ?? '') === 'M d, Y' ? 'selected' : ''; ?>>
Jan 15, 2025
</option>
</select>
</div>
</div>
</div>
</div>
</div>
<!-- Wyświetlanie -->
<div class="card">
<div class="card-header">
<h2>Wyświetlanie treści</h2>
</div>
<div class="card-body">
<div class="settings-grid">
<div class="form-group">
<label>Elementów na stronę</label>
<input type="number" name="items_per_page" min="5" max="100"
value="<?php echo escape($settings['items_per_page'] ?? '10'); ?>">
<small>Liczba elementów wyświetlanych na jednej stronie w listach</small>
</div>
</div>
</div>
</div>
<!-- Użytkownicy -->
<div class="card">
<div class="card-header">
<h2>Użytkownicy i bezpieczeństwo</h2>
</div>
<div class="card-body">
<div class="form-group">
<label style="display: flex; align-items: center; gap: 10px;">
<input type="checkbox" name="allow_registration" value="1"
<?php echo ($settings['allow_registration'] ?? '0') == '1' ? 'checked' : ''; ?>
style="width: auto;">
Zezwól na rejestrację nowych użytkowników
</label>
<small>Gdy wyłączone, tylko administrator może tworzyć konta</small>
</div>
</div>
</div>
<!-- System -->
<div class="card">
<div class="card-header">
<h2>Informacje systemowe</h2>
</div>
<div class="card-body">
<div class="settings-grid">
<div>
<strong>Wersja PHP:</strong> <?php echo phpversion(); ?>
</div>
<div>
<strong>Wersja MySQL:</strong>
<?php
$version = $db->query('SELECT VERSION()')->fetchColumn();
echo $version;
?>
</div>
<div>
<strong>Zainstalowane strony:</strong>
<?php echo $db->query('SELECT COUNT(*) FROM pages')->fetchColumn(); ?>
</div>
<div>
<strong>Zarejestrowani użytkownicy:</strong>
<?php echo $db->query('SELECT COUNT(*) FROM users')->fetchColumn(); ?>
</div>
</div>
<hr style="margin: 20px 0; border: none; border-top: 1px solid #e0e0e0;">
<div class="settings-grid">
<div>
<strong>Ścieżka do uploadu:</strong><br>
<code style="font-size: 12px; color: #666;"><?php echo UPLOAD_DIR; ?></code>
</div>
<div>
<strong>Max rozmiar pliku:</strong>
<?php echo round(MAX_UPLOAD_SIZE / 1024 / 1024, 2); ?> MB
</div>
</div>
</div>
</div>
<div style="position: sticky; bottom: 20px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 -2px 10px rgba(0,0,0,0.1);">
<button type="submit" class="btn" style="font-size: 16px; padding: 12px 30px;">
💾 Zapisz wszystkie ustawienia
</button>
</div>
</form>
</div>
</body>
</html>

468
admin/users.php Normal file
View File

@ -0,0 +1,468 @@
<?php
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/functions.php';
requireRole('admin'); // Tylko administratorzy
$message = '';
$error = '';
$db = getDB();
// Obsługa akcji
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action'])) {
switch ($_POST['action']) {
case 'create':
// Walidacja
if (strlen($_POST['username']) < 3) {
$error = 'Nazwa użytkownika musi mieć co najmniej 3 znaki';
} elseif (!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
$error = 'Nieprawidłowy adres email';
} elseif (strlen($_POST['password']) < 6) {
$error = 'Hasło musi mieć co najmniej 6 znaków';
} else {
// Sprawdź czy użytkownik już istnieje
$stmt = $db->prepare("SELECT id FROM users WHERE username = ? OR email = ?");
$stmt->execute([$_POST['username'], $_POST['email']]);
if ($stmt->fetch()) {
$error = 'Użytkownik o tej nazwie lub emailu już istnieje';
} else {
// Utwórz użytkownika
$hashedPassword = password_hash($_POST['password'], PASSWORD_DEFAULT);
$stmt = $db->prepare("
INSERT INTO users (username, email, password, role, status)
VALUES (?, ?, ?, ?, ?)
");
if ($stmt->execute([
$_POST['username'],
$_POST['email'],
$hashedPassword,
$_POST['role'],
$_POST['status']
])) {
$message = 'Użytkownik został utworzony';
logActivity('user_created', 'user', $db->lastInsertId());
} else {
$error = 'Błąd podczas tworzenia użytkownika';
}
}
}
break;
case 'update':
$stmt = $db->prepare("
UPDATE users
SET username = ?, email = ?, role = ?, status = ?
WHERE id = ?
");
if ($stmt->execute([
$_POST['username'],
$_POST['email'],
$_POST['role'],
$_POST['status'],
$_POST['id']
])) {
// Jeśli podano nowe hasło
if (!empty($_POST['password'])) {
$hashedPassword = password_hash($_POST['password'], PASSWORD_DEFAULT);
$stmt = $db->prepare("UPDATE users SET password = ? WHERE id = ?");
$stmt->execute([$hashedPassword, $_POST['id']]);
}
$message = 'Użytkownik został zaktualizowany';
logActivity('user_updated', 'user', $_POST['id']);
} else {
$error = 'Błąd podczas aktualizacji użytkownika';
}
break;
case 'delete':
// Nie pozwól usunąć własnego konta
if ($_POST['id'] == $_SESSION['user_id']) {
$error = 'Nie możesz usunąć własnego konta';
} else {
$stmt = $db->prepare("DELETE FROM users WHERE id = ?");
if ($stmt->execute([$_POST['id']])) {
$message = 'Użytkownik został usunięty';
logActivity('user_deleted', 'user', $_POST['id']);
}
}
break;
}
}
}
// Pobierz wszystkich użytkowników
$stmt = $db->query("
SELECT u.*,
(SELECT COUNT(*) FROM pages WHERE author_id = u.id) as page_count
FROM users u
ORDER BY u.created_at DESC
");
$users = $stmt->fetchAll();
// Tryb edycji
$editUser = null;
if (isset($_GET['edit'])) {
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_GET['edit']]);
$editUser = $stmt->fetch();
}
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Użytkownicy - Panel CMS</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
}
.header {
background: #2c3e50;
color: white;
padding: 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
height: 60px;
}
.sidebar {
position: fixed;
left: 0;
top: 60px;
width: 250px;
background: white;
height: calc(100vh - 60px);
border-right: 1px solid #e0e0e0;
overflow-y: auto;
}
.sidebar nav a {
display: block;
padding: 15px 20px;
color: #333;
text-decoration: none;
border-left: 3px solid transparent;
}
.sidebar nav a:hover, .sidebar nav a.active {
background: #f0f4ff;
border-left-color: #667eea;
color: #667eea;
}
.main-content {
margin-left: 250px;
padding: 30px;
}
.btn {
display: inline-block;
padding: 10px 20px;
background: #667eea;
color: white;
text-decoration: none;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
}
.btn:hover { background: #5568d3; }
.btn-sm { padding: 6px 12px; font-size: 13px; }
.btn-danger { background: #e74c3c; }
.btn-danger:hover { background: #c0392b; }
.card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
margin-bottom: 20px;
}
.card-header {
padding: 20px;
border-bottom: 1px solid #e0e0e0;
}
.card-body { padding: 20px; }
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
.form-group input, .form-group select {
width: 100%;
padding: 10px;
border: 2px solid #e1e8ed;
border-radius: 5px;
font-size: 14px;
}
.form-group small {
display: block;
margin-top: 5px;
color: #666;
font-size: 13px;
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
table {
width: 100%;
border-collapse: collapse;
}
table th, table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e0e0e0;
}
table th {
background: #f8f9fa;
font-weight: 600;
}
.message {
padding: 12px 20px;
background: #d4edda;
color: #155724;
border-radius: 5px;
margin-bottom: 20px;
}
.error {
padding: 12px 20px;
background: #f8d7da;
color: #721c24;
border-radius: 5px;
margin-bottom: 20px;
}
.badge {
display: inline-block;
padding: 4px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
.badge-admin { background: #667eea; color: white; }
.badge-editor { background: #3498db; color: white; }
.badge-user { background: #95a5a6; color: white; }
.badge-active { background: #27ae60; color: white; }
.badge-inactive { background: #e74c3c; color: white; }
@media (max-width: 768px) {
.grid-2 { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="header">
<h1>Panel CMS</h1>
<a href="index.php" class="btn btn-sm"> Powrót</a>
</div>
<div class="sidebar">
<nav>
<a href="index.php">📊 Dashboard</a>
<a href="pages.php">📄 Strony</a>
<a href="media.php">🖼️ Media</a>
<a href="menus.php">🔗 Menu</a>
<a href="categories.php">📁 Kategorie</a>
<a href="users.php" class="active">👥 Użytkownicy</a>
<a href="settings.php">⚙️ Ustawienia</a>
</nav>
</div>
<div class="main-content">
<h1 style="margin-bottom: 30px;">Zarządzanie użytkownikami</h1>
<?php if ($message): ?>
<div class="message"><?php echo escape($message); ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error"><?php echo escape($error); ?></div>
<?php endif; ?>
<!-- Formularz dodawania/edycji -->
<div class="card">
<div class="card-header">
<h2><?php echo $editUser ? 'Edytuj użytkownika' : 'Dodaj nowego użytkownika'; ?></h2>
</div>
<div class="card-body">
<form method="POST">
<input type="hidden" name="action" value="<?php echo $editUser ? 'update' : 'create'; ?>">
<?php if ($editUser): ?>
<input type="hidden" name="id" value="<?php echo $editUser['id']; ?>">
<?php endif; ?>
<div class="grid-2">
<div class="form-group">
<label>Nazwa użytkownika *</label>
<input type="text" name="username" required
value="<?php echo escape($editUser['username'] ?? ''); ?>">
</div>
<div class="form-group">
<label>Email *</label>
<input type="email" name="email" required
value="<?php echo escape($editUser['email'] ?? ''); ?>">
</div>
</div>
<div class="grid-2">
<div class="form-group">
<label>Hasło <?php echo $editUser ? '(pozostaw puste, aby nie zmieniać)' : '*'; ?></label>
<input type="password" name="password" <?php echo $editUser ? '' : 'required'; ?>>
<small>Minimum 6 znaków</small>
</div>
<div class="form-group">
<label>Rola *</label>
<select name="role" required>
<option value="user" <?php echo ($editUser['role'] ?? '') === 'user' ? 'selected' : ''; ?>>
Użytkownik
</option>
<option value="editor" <?php echo ($editUser['role'] ?? '') === 'editor' ? 'selected' : ''; ?>>
Edytor
</option>
<option value="admin" <?php echo ($editUser['role'] ?? '') === 'admin' ? 'selected' : ''; ?>>
Administrator
</option>
</select>
<small>
Użytkownik: może przeglądać<br>
Edytor: może edytować strony<br>
Administrator: pełny dostęp
</small>
</div>
</div>
<div class="form-group">
<label>Status *</label>
<select name="status" required>
<option value="active" <?php echo ($editUser['status'] ?? '') === 'active' ? 'selected' : ''; ?>>
Aktywny
</option>
<option value="inactive" <?php echo ($editUser['status'] ?? '') === 'inactive' ? 'selected' : ''; ?>>
Nieaktywny
</option>
</select>
</div>
<button type="submit" class="btn">
<?php echo $editUser ? 'Zaktualizuj użytkownika' : 'Dodaj użytkownika'; ?>
</button>
<?php if ($editUser): ?>
<a href="users.php" class="btn" style="background: #6c757d;">Anuluj</a>
<?php endif; ?>
</form>
</div>
</div>
<!-- Lista użytkowników -->
<div class="card">
<div class="card-header">
<h2>Wszyscy użytkownicy (<?php echo count($users); ?>)</h2>
</div>
<div class="card-body">
<table>
<thead>
<tr>
<th>Nazwa użytkownika</th>
<th>Email</th>
<th>Rola</th>
<th>Status</th>
<th>Liczba stron</th>
<th>Ostatnie logowanie</th>
<th>Data rejestracji</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td>
<strong><?php echo escape($user['username']); ?></strong>
<?php if ($user['id'] == $_SESSION['user_id']): ?>
<span style="color: #667eea; font-size: 12px;">(Ty)</span>
<?php endif; ?>
</td>
<td><?php echo escape($user['email']); ?></td>
<td>
<span class="badge badge-<?php echo $user['role']; ?>">
<?php
$roles = ['user' => 'Użytkownik', 'editor' => 'Edytor', 'admin' => 'Administrator'];
echo $roles[$user['role']];
?>
</span>
</td>
<td>
<span class="badge badge-<?php echo $user['status']; ?>">
<?php echo $user['status'] === 'active' ? 'Aktywny' : 'Nieaktywny'; ?>
</span>
</td>
<td>
<?php if ($user['page_count'] > 0): ?>
<span class="badge" style="background: #667eea;">
<?php echo $user['page_count']; ?>
</span>
<?php else: ?>
<span style="color: #999;">0</span>
<?php endif; ?>
</td>
<td>
<?php if ($user['last_login']): ?>
<?php echo formatDate($user['last_login'], 'd.m.Y H:i'); ?>
<?php else: ?>
<span style="color: #999;">Nigdy</span>
<?php endif; ?>
</td>
<td><?php echo formatDate($user['created_at'], 'd.m.Y'); ?></td>
<td>
<a href="?edit=<?php echo $user['id']; ?>" class="btn btn-sm">Edytuj</a>
<?php if ($user['id'] != $_SESSION['user_id']): ?>
<form method="POST" style="display: inline;">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?php echo $user['id']; ?>">
<button type="submit" class="btn btn-sm btn-danger"
onclick="return confirm('Czy na pewno chcesz usunąć tego użytkownika?')">
Usuń
</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

41
cms_install.php Normal file
View File

@ -0,0 +1,41 @@
<?php
/**
* Skrypt instalacyjny CMS
* Uruchom ten plik TYLKO RAZ po zainstalowaniu bazy danych
*/
require_once __DIR__ . '/config/database.php';
// Sprawdź czy admin już istnieje
$db = getDB();
$stmt = $db->query("SELECT COUNT(*) as count FROM users WHERE username = 'admin'");
$result = $stmt->fetch();
if ($result['count'] > 0) {
die('Administrator już istnieje! Jeśli zapomniałeś hasła, usuń użytkownika z bazy i uruchom ten skrypt ponownie.');
}
// Utwórz użytkownika admin z hasłem admin123
$password = password_hash('admin123', PASSWORD_DEFAULT);
$stmt = $db->prepare("
INSERT INTO users (username, email, password, role, status)
VALUES ('admin', 'admin@example.com', ?, 'admin', 'active')
");
if ($stmt->execute([$password])) {
echo "<h1>✅ Instalacja zakończona pomyślnie!</h1>";
echo "<p>Użytkownik administratora został utworzony:</p>";
echo "<ul>";
echo "<li><strong>Nazwa użytkownika:</strong> admin</li>";
echo "<li><strong>Hasło:</strong> admin123</li>";
echo "</ul>";
echo "<p><strong>WAŻNE:</strong> Zmień hasło po pierwszym zalogowaniu!</p>";
echo "<p><a href='admin/login.php'>Przejdź do panelu logowania →</a></p>";
echo "<hr>";
echo "<p style='color: red;'><strong>USUŃ TEN PLIK (install.php) PO INSTALACJI!</strong></p>";
} else {
echo "<h1>❌ Błąd instalacji</h1>";
echo "<p>Nie udało się utworzyć użytkownika administratora.</p>";
}
?>

63
config/database.php Normal file
View File

@ -0,0 +1,63 @@
<?php
// Konfiguracja bazy danych
define('DB_HOST', 'localhost');
define('DB_NAME', 'cms_db');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_CHARSET', 'utf8mb4');
// Konfiguracja aplikacji
define('SITE_URL', 'http://localhost/cms');
define('ADMIN_URL', SITE_URL . '/admin');
define('UPLOAD_DIR', __DIR__ . '/../assets/uploads/');
define('UPLOAD_URL', SITE_URL . '/assets/uploads/');
define('MAX_UPLOAD_SIZE', 5242880); // 5MB
// Bezpieczeństwo
define('SESSION_LIFETIME', 3600); // 1 godzina
define('HASH_ALGO', PASSWORD_DEFAULT);
class Database {
private static $instance = null;
private $connection;
private function __construct() {
try {
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
$this->connection = new PDO($dsn, DB_USER, DB_PASS, $options);
} catch (PDOException $e) {
die("Błąd połączenia z bazą danych: " . $e->getMessage());
}
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function getConnection() {
return $this->connection;
}
// Zapobieganie klonowaniu
private function __clone() {}
// Zapobieganie deserializacji
public function __wakeup() {
throw new Exception("Nie można deserializować singletona");
}
}
// Inicjalizacja połączenia
function getDB() {
return Database::getInstance()->getConnection();
}
?>

190
includes/auth.php Normal file
View File

@ -0,0 +1,190 @@
<?php
session_start();
require_once __DIR__ . '/../config/database.php';
// Logowanie użytkownika
function login($username, $password) {
$db = getDB();
$stmt = $db->prepare("SELECT * FROM users WHERE username = ? AND status = 'active'");
$stmt->execute([$username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
// Regeneruj ID sesji dla bezpieczeństwa
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['role'] = $user['role'];
$_SESSION['logged_in'] = true;
$_SESSION['last_activity'] = time();
// Aktualizuj ostatnie logowanie
$updateStmt = $db->prepare("UPDATE users SET last_login = NOW() WHERE id = ?");
$updateStmt->execute([$user['id']]);
logActivity('login');
return ['success' => true, 'user' => $user];
}
return ['success' => false, 'message' => 'Nieprawidłowa nazwa użytkownika lub hasło'];
}
// Wylogowanie użytkownika
function logout() {
logActivity('logout');
$_SESSION = [];
if (isset($_COOKIE[session_name()])) {
setcookie(session_name(), '', time() - 3600, '/');
}
session_destroy();
}
// Sprawdzenie czy użytkownik jest zalogowany
function isLoggedIn() {
if (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) {
return false;
}
// Sprawdź timeout sesji
if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > SESSION_LIFETIME)) {
logout();
return false;
}
$_SESSION['last_activity'] = time();
return true;
}
// Sprawdzenie roli użytkownika
function hasRole($role) {
if (!isLoggedIn()) {
return false;
}
$roles = ['user' => 1, 'editor' => 2, 'admin' => 3];
$userRole = $_SESSION['role'] ?? 'user';
return $roles[$userRole] >= $roles[$role];
}
// Wymuszenie logowania
function requireLogin() {
if (!isLoggedIn()) {
header('Location: ' . ADMIN_URL . '/login.php');
exit;
}
}
// Wymuszenie roli
function requireRole($role) {
requireLogin();
if (!hasRole($role)) {
http_response_code(403);
die('Brak uprawnień do tej strony');
}
}
// Rejestracja nowego użytkownika
function register($username, $email, $password) {
$db = getDB();
// Sprawdź czy rejestracja jest włączona
if (!getSetting('allow_registration', '0')) {
return ['success' => false, 'message' => 'Rejestracja jest wyłączona'];
}
// Walidacja
if (strlen($username) < 3) {
return ['success' => false, 'message' => 'Nazwa użytkownika musi mieć co najmniej 3 znaki'];
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return ['success' => false, 'message' => 'Nieprawidłowy adres email'];
}
if (strlen($password) < 6) {
return ['success' => false, 'message' => 'Hasło musi mieć co najmniej 6 znaków'];
}
// Sprawdź czy użytkownik już istnieje
$stmt = $db->prepare("SELECT id FROM users WHERE username = ? OR email = ?");
$stmt->execute([$username, $email]);
if ($stmt->fetch()) {
return ['success' => false, 'message' => 'Użytkownik o tej nazwie lub emailu już istnieje'];
}
// Hashuj hasło
$hashedPassword = password_hash($password, HASH_ALGO);
// Dodaj użytkownika
$stmt = $db->prepare("
INSERT INTO users (username, email, password, role)
VALUES (?, ?, ?, 'user')
");
if ($stmt->execute([$username, $email, $hashedPassword])) {
logActivity('user_registered', 'user', $db->lastInsertId());
return ['success' => true, 'message' => 'Rejestracja zakończona pomyślnie'];
}
return ['success' => false, 'message' => 'Wystąpił błąd podczas rejestracji'];
}
// Zmiana hasła
function changePassword($userId, $oldPassword, $newPassword) {
$db = getDB();
$stmt = $db->prepare("SELECT password FROM users WHERE id = ?");
$stmt->execute([$userId]);
$user = $stmt->fetch();
if (!$user || !password_verify($oldPassword, $user['password'])) {
return ['success' => false, 'message' => 'Nieprawidłowe aktualne hasło'];
}
if (strlen($newPassword) < 6) {
return ['success' => false, 'message' => 'Nowe hasło musi mieć co najmniej 6 znaków'];
}
$hashedPassword = password_hash($newPassword, HASH_ALGO);
$stmt = $db->prepare("UPDATE users SET password = ? WHERE id = ?");
if ($stmt->execute([$hashedPassword, $userId])) {
logActivity('password_changed');
return ['success' => true, 'message' => 'Hasło zostało zmienione'];
}
return ['success' => false, 'message' => 'Nie udało się zmienić hasła'];
}
// Pobierz aktualnego użytkownika
function getCurrentUser() {
if (!isLoggedIn()) {
return null;
}
$db = getDB();
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
return $stmt->fetch();
}
// CSRF Token
function generateCSRFToken() {
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function validateCSRFToken($token) {
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}
?>

244
includes/functions.php Normal file
View File

@ -0,0 +1,244 @@
<?php
require_once __DIR__ . '/../config/database.php';
// Zabezpieczenie przed XSS
function escape($string) {
return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
}
// Generowanie slug z tytułu
function createSlug($text) {
$text = mb_strtolower($text, 'UTF-8');
// Polskie znaki
$replacements = [
'ą' => 'a', 'ć' => 'c', 'ę' => 'e', 'ł' => 'l',
'ń' => 'n', 'ó' => 'o', 'ś' => 's', 'ź' => 'z',
'ż' => 'z', 'Ą' => 'a', 'Ć' => 'c', 'Ę' => 'e',
'Ł' => 'l', 'Ń' => 'n', 'Ó' => 'o', 'Ś' => 's',
'Ź' => 'z', 'Ż' => 'z'
];
$text = strtr($text, $replacements);
$text = preg_replace('/[^a-z0-9-]/', '-', $text);
$text = preg_replace('/-+/', '-', $text);
$text = trim($text, '-');
return $text;
}
// Pobranie strony po slug
function getPageBySlug($slug) {
$db = getDB();
$stmt = $db->prepare("
SELECT p.*, u.username as author_name
FROM pages p
LEFT JOIN users u ON p.author_id = u.id
WHERE p.slug = ? AND p.status = 'published'
");
$stmt->execute([$slug]);
return $stmt->fetch();
}
// Pobranie wszystkich stron
function getAllPages($status = 'published', $limit = null) {
$db = getDB();
$sql = "SELECT p.*, u.username as author_name
FROM pages p
LEFT JOIN users u ON p.author_id = u.id
WHERE p.status = ?
ORDER BY p.created_at DESC";
if ($limit) {
$sql .= " LIMIT " . intval($limit);
}
$stmt = $db->prepare($sql);
$stmt->execute([$status]);
return $stmt->fetchAll();
}
// Pobranie ustawień
function getSetting($key, $default = '') {
$db = getDB();
$stmt = $db->prepare("SELECT setting_value FROM settings WHERE setting_key = ?");
$stmt->execute([$key]);
$result = $stmt->fetch();
return $result ? $result['setting_value'] : $default;
}
// Aktualizacja ustawienia
function updateSetting($key, $value) {
$db = getDB();
$stmt = $db->prepare("
INSERT INTO settings (setting_key, setting_value)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE setting_value = ?
");
return $stmt->execute([$key, $value, $value]);
}
// Zapisywanie strony
function savePage($data) {
$db = getDB();
// Jeśli nie ma slug, generuj z tytułu
if (empty($data['slug'])) {
$data['slug'] = createSlug($data['title']);
}
if (isset($data['id']) && $data['id']) {
// Aktualizacja
$stmt = $db->prepare("
UPDATE pages SET
title = ?,
slug = ?,
content = ?,
meta_description = ?,
meta_keywords = ?,
template = ?,
status = ?,
featured_image = ?
WHERE id = ?
");
return $stmt->execute([
$data['title'],
$data['slug'],
$data['content'],
$data['meta_description'] ?? '',
$data['meta_keywords'] ?? '',
$data['template'] ?? 'default',
$data['status'] ?? 'draft',
$data['featured_image'] ?? null,
$data['id']
]);
} else {
// Nowa strona
$stmt = $db->prepare("
INSERT INTO pages (title, slug, content, meta_description, meta_keywords, template, author_id, status, featured_image)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
");
return $stmt->execute([
$data['title'],
$data['slug'],
$data['content'],
$data['meta_description'] ?? '',
$data['meta_keywords'] ?? '',
$data['template'] ?? 'default',
$data['author_id'],
$data['status'] ?? 'draft',
$data['featured_image'] ?? null
]);
}
}
// Usuwanie strony
function deletePage($id) {
$db = getDB();
$stmt = $db->prepare("DELETE FROM pages WHERE id = ?");
return $stmt->execute([$id]);
}
// Pobranie menu
function getMenu($location) {
$db = getDB();
$stmt = $db->prepare("
SELECT mi.*
FROM menu_items mi
JOIN menus m ON mi.menu_id = m.id
WHERE m.location = ?
ORDER BY mi.sort_order ASC
");
$stmt->execute([$location]);
return $stmt->fetchAll();
}
// Upload pliku
function uploadFile($file, $allowedTypes = ['image/jpeg', 'image/png', 'image/gif']) {
if (!isset($file['error']) || $file['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'message' => 'Błąd podczas przesyłania pliku'];
}
if (!in_array($file['type'], $allowedTypes)) {
return ['success' => false, 'message' => 'Niedozwolony typ pliku'];
}
if ($file['size'] > MAX_UPLOAD_SIZE) {
return ['success' => false, 'message' => 'Plik jest za duży'];
}
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
$filename = uniqid() . '.' . $extension;
$filepath = UPLOAD_DIR . $filename;
if (!is_dir(UPLOAD_DIR)) {
mkdir(UPLOAD_DIR, 0755, true);
}
if (move_uploaded_file($file['tmp_name'], $filepath)) {
// Zapisz do bazy
$db = getDB();
$stmt = $db->prepare("
INSERT INTO media (filename, original_name, file_path, file_type, file_size, mime_type, uploaded_by)
VALUES (?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$filename,
$file['name'],
$filepath,
$extension,
$file['size'],
$file['type'],
$_SESSION['user_id'] ?? 1
]);
return [
'success' => true,
'filename' => $filename,
'url' => UPLOAD_URL . $filename,
'id' => $db->lastInsertId()
];
}
return ['success' => false, 'message' => 'Nie udało się zapisać pliku'];
}
// Logowanie aktywności
function logActivity($action, $entityType = null, $entityId = null) {
$db = getDB();
$stmt = $db->prepare("
INSERT INTO activity_logs (user_id, action, entity_type, entity_id, ip_address, user_agent)
VALUES (?, ?, ?, ?, ?, ?)
");
return $stmt->execute([
$_SESSION['user_id'] ?? null,
$action,
$entityType,
$entityId,
$_SERVER['REMOTE_ADDR'] ?? null,
$_SERVER['HTTP_USER_AGENT'] ?? null
]);
}
// Formatowanie daty
function formatDate($date, $format = 'd.m.Y H:i') {
return date($format, strtotime($date));
}
// Pobieranie kategorii strony
function getPageCategories($pageId) {
$db = getDB();
$stmt = $db->prepare("
SELECT c.*
FROM categories c
JOIN page_categories pc ON c.id = pc.category_id
WHERE pc.page_id = ?
");
$stmt->execute([$pageId]);
return $stmt->fetchAll();
}
?>

244
index.php Normal file
View File

@ -0,0 +1,244 @@
<?php
require_once __DIR__ . '/includes/functions.php';
// Pobierz slug z URL
$slug = $_GET['page'] ?? 'home';
// Pobierz stronę
$page = getPageBySlug($slug);
// Jeśli strona nie istnieje, pokaż 404
if (!$page) {
http_response_code(404);
$pageTitle = 'Strona nie znaleziona';
$pageContent = '<h1>404 - Strona nie znaleziona</h1><p>Przepraszamy, ale szukana strona nie istnieje.</p>';
} else {
$pageTitle = $page['title'];
$pageContent = $page['content'];
$metaDescription = $page['meta_description'] ?? '';
}
// Pobierz ustawienia
$siteName = getSetting('site_name', 'Moja Strona');
$menu = getMenu('header');
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo escape($pageTitle . ' - ' . $siteName); ?></title>
<?php if ($metaDescription): ?>
<meta name="description" content="<?php echo escape($metaDescription); ?>">
<?php endif; ?>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
line-height: 1.6;
color: #333;
}
/* Nagłówek */
header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 0;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header-top {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-size: 28px;
font-weight: 700;
text-decoration: none;
color: white;
}
nav {
background: rgba(0,0,0,0.1);
}
nav ul {
list-style: none;
display: flex;
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
nav ul li a {
display: block;
padding: 15px 20px;
color: white;
text-decoration: none;
transition: background 0.3s;
}
nav ul li a:hover {
background: rgba(255,255,255,0.1);
}
/* Główna treść */
main {
max-width: 1200px;
margin: 40px auto;
padding: 0 20px;
}
.content {
background: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
}
.content h1 {
color: #2c3e50;
margin-bottom: 20px;
font-size: 36px;
}
.content h2 {
color: #34495e;
margin: 30px 0 15px;
font-size: 28px;
}
.content h3 {
color: #34495e;
margin: 25px 0 12px;
font-size: 22px;
}
.content p {
margin-bottom: 15px;
line-height: 1.8;
}
.content img {
max-width: 100%;
height: auto;
border-radius: 8px;
margin: 20px 0;
}
.content ul, .content ol {
margin: 15px 0 15px 30px;
}
.content li {
margin-bottom: 8px;
}
/* Stopka */
footer {
background: #2c3e50;
color: white;
text-align: center;
padding: 30px 20px;
margin-top: 60px;
}
footer p {
margin-bottom: 10px;
}
footer a {
color: #667eea;
text-decoration: none;
}
footer a:hover {
text-decoration: underline;
}
/* Responsywność */
@media (max-width: 768px) {
.header-top {
flex-direction: column;
gap: 15px;
}
nav ul {
flex-direction: column;
}
nav ul li a {
padding: 12px 20px;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.content {
padding: 20px;
}
.content h1 {
font-size: 28px;
}
}
/* Strona 404 */
.error-404 {
text-align: center;
padding: 60px 20px;
}
.error-404 h1 {
font-size: 72px;
color: #667eea;
margin-bottom: 20px;
}
</style>
</head>
<body>
<header>
<div class="header-top">
<a href="/" class="logo"><?php echo escape($siteName); ?></a>
<div>
<a href="<?php echo ADMIN_URL; ?>/login.php" style="color: white; text-decoration: none; padding: 10px 20px; background: rgba(255,255,255,0.2); border-radius: 5px;">
Panel administracyjny
</a>
</div>
</div>
<?php if (!empty($menu)): ?>
<nav>
<ul>
<?php foreach ($menu as $item): ?>
<li>
<a href="<?php echo escape($item['url'] ?? '?page=' . $item['page_id']); ?>">
<?php echo escape($item['title']); ?>
</a>
</li>
<?php endforeach; ?>
</ul>
</nav>
<?php endif; ?>
</header>
<main>
<div class="content <?php echo !$page ? 'error-404' : ''; ?>">
<?php echo $pageContent; ?>
</div>
</main>
<footer>
<p>&copy; <?php echo date('Y'); ?> <?php echo escape($siteName); ?>. Wszystkie prawa zastrzeżone.</p>
<p>Powered by Custom CMS</p>
</footer>
</body>
</html>