Compare commits

...

71 Commits

Author SHA1 Message Date
Felix Ivo
5ef5de9b98 fixed document upload location 2025-07-08 10:51:57 +02:00
Felix Ivo
92e162283e fixed directory location 2025-07-07 15:08:56 +02:00
Felix Ivo
26fb9b54b6 fixed upoad folder missing 2025-07-07 14:52:46 +02:00
Felix Ivo
b4fcc4892c fixed sorting 2025-07-07 14:50:48 +02:00
Felix Ivo
58e0f1eafd Merge branch 'main' of http://git.pb.bib.de/PBBFA23CIV/EIANotesApp 2025-07-07 14:42:47 +02:00
Felix Ivo
9bca8fd1d1 added admin page 2025-07-07 14:42:34 +02:00
bb9424232c Merge branch 'main' of http://git.pb.bib.de/PBBFA23CIV/EIANotesApp 2025-07-07 14:39:42 +02:00
e8766ecc26 added priority to notes 2025-07-07 14:28:11 +02:00
Felix Ivo
c5ebde8b20 fix filepath link 2025-07-07 10:54:32 +02:00
Felix Ivo
4ae6971b9c added gitignore 2025-07-07 10:50:51 +02:00
Felix Ivo
0799db48f0 add uploaded files to edit, note details, welcome page 2025-07-07 10:42:12 +02:00
Felix Ivo
9db4d93ce3 comment out test_write file 2025-07-07 10:27:19 +02:00
Felix Ivo
aabd6288fe Merge branch 'main' of http://git.pb.bib.de/PBBFA23CIV/EIANotesApp 2025-07-07 10:21:31 +02:00
Felix Ivo
48f1fb8923 added upload to createNotes 2025-07-07 10:21:15 +02:00
ce59837500 Deutsch --> Englisch 2025-07-03 16:57:26 +02:00
3c9d90ebb8 Merge branch 'main' of http://git.pb.bib.de/PBBFA23CIV/EIANotesApp 2025-06-30 11:11:58 +02:00
Felix Ivo
871ffe01d0 removed isEditMode 2025-06-30 10:37:43 +02:00
36ea386e04 Merge branch 'main' of http://git.pb.bib.de/PBBFA23CIV/EIANotesApp 2025-06-30 10:37:32 +02:00
31fff91725 removed comment 2025-06-30 10:36:48 +02:00
Felix Ivo
b92f7c1054 sanitized login form 2025-06-30 10:35:03 +02:00
Felix Ivo
6dbca7cbd4 Merge branch 'main' of http://git.pb.bib.de/PBBFA23CIV/EIANotesApp 2025-06-30 10:26:48 +02:00
Felix Ivo
ba9ec6b430 Willkommen statistik 2025-06-30 10:26:39 +02:00
63899ffa8f Merge branch 'main' of http://git.pb.bib.de/PBBFA23CIV/EIANotesApp 2025-06-30 10:11:06 +02:00
5c381740ff removed comment 2025-06-30 10:11:01 +02:00
Felix Ivo
fc17a1a312 fixed edit note 2025-06-30 10:10:42 +02:00
5eb3e30114 Merge branch 'main' of http://git.pb.bib.de/PBBFA23CIV/EIANotesApp 2025-06-27 11:02:11 +02:00
ab757adfbd Redirect and ErrorMessage for RegisterUser 2025-06-27 11:02:01 +02:00
Felix Ivo
95b7634901 fixed delete note 2025-06-27 10:54:24 +02:00
Felix Ivo
5a63cc66c5 redirects work for create and edit 2025-06-27 10:29:34 +02:00
1a5a069844 Redirect to login when clicking notes and no user logged in 2025-06-27 10:03:45 +02:00
Felix Ivo
24c8f38c4d added create and eedit note functionality (flawed) 2025-06-27 09:56:46 +02:00
Felix Ivo
1e9705aa13 fixed isAdmin 2025-06-23 14:19:04 +02:00
Felix Ivo
df103f9b86 enabled admin user column 2025-06-23 14:11:37 +02:00
Felix Ivo
5741783dbe give nav bar style 2025-06-23 13:56:24 +02:00
6cceae17d0 Merge branch 'main' of http://git.pb.bib.de/PBBFA23CIV/EIANotesApp 2025-06-23 11:13:55 +02:00
3d4e1532c7 Register 2025-06-23 11:13:48 +02:00
Felix Ivo
80cb9f2818 fixed margins 2025-06-23 11:12:35 +02:00
Felix Ivo
bcde6649b5 enabled live markdown preview 2025-06-23 11:06:11 +02:00
Felix Ivo
e54a8f241e fixed parsedown import, submit buttons are now shown 2025-06-23 11:04:08 +02:00
Felix Ivo
21aa81dbd3 removed user specified hardcode 2025-06-23 10:52:30 +02:00
Felix Ivo
7d1d48199a Merge branch 'main' of http://git.pb.bib.de/PBBFA23CIV/EIANotesApp 2025-06-23 10:49:07 +02:00
Felix Ivo
d21da71585 edit and create notes first push 2025-06-23 10:48:57 +02:00
ba55304182 Logout fertig 2025-06-23 10:33:53 +02:00
032029ce7f base Logout implementiert 2025-06-23 10:17:10 +02:00
ff1234d561 Merge branch 'main' of http://git.pb.bib.de/PBBFA23CIV/EIANotesApp 2025-06-23 09:58:54 +02:00
9a4626e854 x 2025-06-23 09:58:45 +02:00
Felix Ivo
b06536baf6 added note detail view 2025-06-16 15:11:33 +02:00
Felix Ivo
a4d6aeea18 Merge branch 'main' of http://git.pb.bib.de/PBBFA23CIV/EIANotesApp 2025-06-16 14:33:08 +02:00
Felix Ivo
a2fd5f011b fixed header 2025-06-16 14:33:00 +02:00
97137da6b9 Merge branch 'main' of http://git.pb.bib.de/PBBFA23CIV/EIANotesApp 2025-06-16 14:26:37 +02:00
246f0c9e5a wip Login 2025-06-16 14:26:30 +02:00
Felix Ivo
75742157b7 removed ajax form handling for login 2025-06-16 14:22:51 +02:00
Felix Ivo
cb7d71f4a7 moved javascript include to header 2025-06-16 14:21:32 +02:00
Felix Ivo
c0bd9b7e8a Merge branch 'main' of http://git.pb.bib.de/PBBFA23CIV/EIANotesApp 2025-06-16 13:56:04 +02:00
Felix Ivo
020bdc8c89 user id and role is handled by session 2025-06-16 13:55:54 +02:00
a3cfd431c3 UserModel Change 2025-06-16 13:55:33 +02:00
f27d92c544 Merge branch 'main' of http://git.pb.bib.de/PBBFA23CIV/EIANotesApp 2025-06-16 13:50:20 +02:00
2f57dd9e5c Login WIP 2025-06-16 13:50:12 +02:00
Felix Ivo
2d133598e8 Merge branch 'main' of http://git.pb.bib.de/PBBFA23CIV/EIANotesApp 2025-06-16 11:17:47 +02:00
Felix Ivo
67d32fcc96 admin view, user specified table 2025-06-16 11:17:33 +02:00
ba6edc6d6b added userform 2025-06-16 10:59:15 +02:00
Felix Ivo
e62a0ec2af added sorting functionality 2025-06-16 10:56:45 +02:00
Felix Ivo
fd08f04600 added sorting functionality, changed style 2025-06-16 10:49:26 +02:00
Felix Ivo
c46d90a40c removed gitignore 2025-06-16 10:39:29 +02:00
Felix Ivo
0b4228fdd2 added .gitignore for Database.php 2025-06-16 10:38:34 +02:00
Felix Ivo
be8a0990e8 prepare sql 2025-06-16 10:26:41 +02:00
Felix Ivo
5248f1c59c prepare sql 2025-06-16 10:25:46 +02:00
Felix Ivo
b6d51cbc37 somewhat functional notes table (needs improvement) 2025-06-16 10:21:41 +02:00
Felix Ivo
8c13989d47 added parsedown for markdown 2025-06-16 10:06:31 +02:00
Felix Ivo
da82f93e99 removed gallery, added notizen button 2025-06-16 10:01:41 +02:00
Felix Ivo
3862b68d9f added notes page 2025-06-13 08:06:38 +02:00
22 changed files with 1621 additions and 232 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
uploads

View File

@@ -1,145 +1,356 @@
/*
Created on : 04.01.2018, 15:39:10
Author : reich
*/
*, *:before, *:after {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
}
background-color: #f0f2f5;
color: #333;
display: flex;
flex-direction: column;
min-height: 100vh;
}
body {
font-size: 18px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.top-bar {
background-color: #6A5ACD;
color: white;
padding: 15px 30px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.wrapper {
max-width: 1000px;
margin: 0 auto;
}
.top-bar h1 {
margin: 0 30px 0 0;
font-size: 1.5em;
}
/*** Allgemeine Definitionen ***/
h1 {
/* margin: 10px; */
color: #F2F2F2;
font-size: 48px;
font-weight: 100;
text-align: center;
}
.top-nav {
display: flex;
align-items: center;
justify-content: center;
margin-right: auto;
}
h1 span {
margin-left: -2px;
color: #FDBD02;
font-size: 24px;
font-weight: 600;
}
h2 {
padding: 10px 0 10px 0;
text-align: center;
color: #011448;
font-size: 36px;
font-weight: 100;
}
main {
background-color: #e6e6e6;
color: #000;
padding-bottom: 20px;
}
/*** Standard-Button ***/
.button {
text-align: right;
}
.button a {
display: inline-block;
background: #FDBD02;
color:#303E64;
border: none;
width: 100px;
margin: 5px;
padding: 2px;
border-radius: 5px;
cursor:pointer;
font-size: 12px;
text-decoration: none;
text-align: center;
}
.button a:hover {
color:#fff;
}
.clear {
clear: both;
}
/*** Header-Bereich mit Navigationsleiste ***/
header {
width: 100%;
background-color: #303E64;
}
nav {
text-align: center;
position: sticky;
top: 0;
background-color: #303E64;
}
nav ul {
list-style-type: none;
padding: 0;
display: inline-block;
}
nav li {
float: left;
text-align: center;
}
nav li a {
display: block;
width: 120px;
height: 35px;
border: 1px solid #000;
background-color: #e6e6e6;
color: #000;
text-decoration: none;
margin: 5px;
text-align: center;
line-height: 35px;
}
nav li a:hover {
background-color: #F2C608;
}
#metanavi {
color: lightskyblue;
font-weight: bold;
margin-bottom: 5px;
}
.container {
.top-nav ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
}
.item-4-12 {
flex: 0 0 33.3333333333%;
.top-nav li {
margin-right: 15px;
}
img {
.top-nav li a {
color: white;
text-decoration: none;
padding: 8px 12px;
border-radius: 4px;
background-color: transparent;
cursor: pointer;
font-size: 0.9em;
}
.user-info {
display: flex;
align-items: center;
}
.user-info span {
margin-right: 15px;
}
.user-info a, .user-info button {
color: white;
text-decoration: none;
padding: 8px 12px;
border-radius: 4px;
background-color: transparent;
border: 1px solid white;
cursor: pointer;
font-size: 0.9em;
margin-left: 10px;
}
.user-info a:hover, .user-info button:hover {
background-color: rgba(255,255,255,0.1);
}
.icon-button {
background: none;
border: none;
color: white;
font-size: 1.5em;
cursor: pointer;
padding: 0 5px;
}
.icon-button:hover {
opacity: 0.8;
}
.container {
flex-grow: 1;
width: 90%;
max-width: 1200px;
margin: 30px auto;
padding: 20px;
background-color: white;
box-shadow: 0 0 15px rgba(0,0,0,0.1);
border-radius: 8px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.page-header h2 {
margin: 0;
color: #333;
}
.button, button {
background-color: #6A5ACD;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1em;
text-decoration: none;
display: inline-block;
transition: background-color 0.2s ease-in-out;
}
button.secondary {
background-color: #777;
}
button.danger {
background-color: #e74c3c;
}
.button:hover, button:hover {
opacity: 0.85;
}
/* Form Styles */
.form-container {
max-width: 600px;
margin: 20px auto;
padding: 20px;
background-color: #f9f9f9;
border-radius: 8px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input[type="text"],
.form-group input[type="password"],
.form-group textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
font-size: 1em;
}
[class*="item-"] {
padding: 0 10px 0 10px;
.form-group textarea {
min-height: 150px;
resize: vertical;
}
.form-actions {
margin-top: 20px;
text-align: right;
}
.form-actions button {
margin-left: 10px;
}
/* Table Styles */
.notes-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.notes-table th, .notes-table td {
border: 1px solid #e0e0e0;
padding: 12px 15px;
text-align: left;
vertical-align: top;
}
.notes-table th {
background-color: #f9f9f9;
font-weight: bold;
cursor: pointer;
}
.notes-table th:hover {
background-color: #f0f0f0;
}
.notes-table th .sort-icon {
margin-left: 5px;
font-size: 0.8em;
}
.notes-table tr:nth-child(even) {
background-color: #fbfbfb;
}
.notes-table tr:hover {
background-color: #f5f5f5;
}
.actions-cell button, .actions-cell a {
margin-right: 5px;
padding: 5px 8px;
font-size: 0.9em;
}
/* Markdown Preview */
.markdown-preview {
border: 1px solid #ddd;
padding: 15px;
min-height: 100px;
background-color: #fff;
border-radius: 4px;
margin-top: 10px;
}
.markdown-preview h1, .markdown-preview h2, .markdown-preview h3 {
margin-top: 0.5em;
margin-bottom: 0.25em;
}
.markdown-preview p {
margin-bottom: 0.5em;
line-height: 1.6;
}
.markdown-preview ul, .markdown-preview ol {
margin-left: 20px;
margin-bottom: 0.5em;
}
.markdown-preview pre {
background-color: #f5f5f5;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
}
.markdown-preview code {
font-family: "Courier New", Courier, monospace;
}
.markdown-preview blockquote {
border-left: 3px solid #ccc;
padding-left: 10px;
margin-left: 0;
color: #666;
}
/* Drag and Drop Area */
#drop-zone {
border: 2px dashed #ccc;
padding: 20px;
text-align: center;
margin-bottom: 20px;
border-radius: 8px;
background-color: #f9f9f9;
}
#drop-zone.drag-over {
border-color: #6A5ACD;
background-color: #e8e4f5;
}
/* Alerts */
.alert {
padding: 15px;
margin-bottom: 20px;
border: 1px solid transparent;
border-radius: 4px;
}
.alert-success {
color: #155724;
background-color: #d4edda;
border-color: #c3e6cb;
}
.alert-danger {
color: #721c24;
background-color: #f8d7da;
border-color: #f5c6cb;
}
.alert-info {
color: #0c5460;
background-color: #d1ecf1;
border-color: #bee5eb;
}
#password-strength {
font-size: 0.9em;
margin-top: 5px;
}
.password-strength-meter {
height: 5px;
background-color: #eee;
border-radius: 5px;
margin-top: 3px;
}
.password-strength-meter div {
height: 100%;
width: 0;
background-color: red;
border-radius: 5px;
transition: width 0.3s ease, background-color 0.3s ease;
}
#password-strength ul {
list-style-type: none;
padding-left: 0;
font-size: 0.9em;
margin-top: 5px;
}
#password-strength li {
margin-bottom: 3px;
}
#password-strength li.valid {
color: green;
}
#password-strength li.invalid {
color: red;
}
.notes-table th .sort-icon {
margin-left: 5px;
font-size: 0.8em;
color: #6A5ACD;
display: inline-block;
}
.style_low {
background-color: darkseagreen;
font-weight: bold;
}
.style_mid {
background-color: moccasin;
font-weight: bold;
}
.style_high {
background-color: lightcoral;
font-weight: bold;
}

View File

@@ -1,27 +0,0 @@
<?php
namespace ppa\Controller;
use ppa\Model\GalleryModel;
use ppa\Library\View;
class GalleryController
{
private $galleryModel;
protected $view;
public function __construct($view)
{
$this->galleryModel = new GalleryModel();
$this->view = $view;
}
public function showPhotos()
{
$this->view->setVars([
"photos" => $this->galleryModel->selectPhotos()
]);
}
}

View File

@@ -0,0 +1,141 @@
<?php
namespace ppa\Controller;
use ppa\Model\NotesModel;
use ppa\Library\View;
class NotesController
{
private $notesModel;
protected $view;
public function __construct($view)
{
$this->notesModel = new NotesModel();
$this->view = $view;
}
public function showNotes()
{
# Redirect zum Login wenn kein User eingeloggt ist
if(!Isset($_SESSION['role']))
{
header("Location: ?controller=User&do=showUserLoginForm");
}
$sortBy = $_GET['sort_by'] ?? 'updated_at';
$sortOrder = strtoupper($_GET['sort_order'] ?? 'DESC');
$isAdmin = $_SESSION['role'] === 'admin';
$userid = $_SESSION['user_id'];
$this->view->setVars([
"notes" => $this->notesModel->selectNotesForUser($userid, $isAdmin, $sortBy, $sortOrder)
]);
}
public function showNoteDetails()
{
$noteId = $_GET['id'];
$note = $this->notesModel->getNoteById($noteId);
$this->view->setVars([
"note" => $note
]);
}
public function createNote()
{
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Process form submission
$note = $this->notesModel->createNote(
$_POST['title'],
$_POST['content'],
$_SESSION['user_id'],
$_POST['priority']
);
if ($note) {
// Redirect to show notes page after successful creation
header('Location: ?controller=Notes&page=showNotes&do=showNotes');
exit();
} else {
// If creation failed, show error message and stay on the form
$this->view->setVars([
'error' => 'Failed to create note. Please try again.'
]);
}
}
}
public function editNote()
{
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Process form submission
$noteId = $_GET['id'];
$note = $this->notesModel->editNote(
$noteId,
$_POST['title'],
$_POST['content'],
$_SESSION['user_id'],
$_POST['priority']
);
if ($note) {
// Redirect to show notes page after successful update
header('Location: ?controller=Notes&page=showNotes&do=showNotes');
exit();
} else {
// If update failed, show error message and stay on the form
$this->view->setVars([
'error' => 'Failed to update note. Please try again.'
]);
}
}
}
public function deleteNote()
{
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['note_id'])) {
$noteId = $_POST['note_id'];
$this->notesModel->deleteNote($noteId, $_SESSION['user_id']);
}
header("Location: ?controller=Notes&page=showNotes&do=showNotes");
exit();
}
public function fileManager()
{
# Redirect zum Login wenn kein User eingeloggt ist
if(!Isset($_SESSION['role']))
{
header("Location: ?controller=User&do=showUserLoginForm");
}
# Redirect zum Welcome wenn kein User kein Admin ist
if(!Isset($_SESSION['role']) || $_SESSION['role'] !== 'admin')
{
header("Location: ?controller=Welcome&do=showWelcome");
}
$sortBy = $_GET['sort_by'] ?? 'uploaded_at';
$sortOrder = strtoupper($_GET['sort_order'] ?? 'DESC');
$isAdmin = $_SESSION['role'] === 'admin';
$userid = $_SESSION['user_id'];
$files = $this->notesModel->selectFiles($userid, true, $sortBy, $sortOrder);
$this->view->setVars([
"files" => $files
]);
}
public function deleteFile()
{
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['file_id'])) {
$fileId = $_POST['file_id'];
$this->notesModel->deleteFile($fileId, $_SESSION['user_id']);
}
header("Location: ?controller=Notes&do=fileManager");
exit();
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace ppa\Controller;
use ppa\Model\UserModel;
use ppa\Library\View;
class UserController
{
private $userModel;
protected $view;
public function __construct($view)
{
$this->userModel = new UserModel();
$this->view = $view;
}
public function loginUser()
{
$erg = array();
$erg = $this->userModel->loginUser($this->sanitize($_POST["username"]), $this->sanitize($_POST["password"]) );
if ($erg["success"] == true) {
header("Location: ?controller=Welcome&do=showWelcome");
exit();
}
else {
$this->view->setDoMethodName("showUserLoginForm");
$this->showUserLoginForm();
}
}
public function logoutUser()
{
$this->userModel->logoutUser();
header("Location: ?controller=User&do=showUserLoginForm");
exit();
}
public function registerUser()
{
$erg = array();
$erg = $this->userModel->registerUser($this->sanitize($_POST["username"]), $this->sanitize($_POST["password"]) );
if ($erg["success"] == true) {
header("Location: ?controller=User&do=showUserLoginForm");
exit();
}
else {
$this->view->setDoMethodName("showUserRegisterForm");
$message = $this->sanitize($erg['message']);
echo "<script type='text/javascript'>alert(\"$message\");</script>";
$this->view->setVars([
"errmsg" => $message
]);
$this->showUserRegisterForm();
}
}
public function showUserLoginForm()
{
}
public function showUserRegisterForm()
{
}
function sanitize($data, $flags = ENT_QUOTES, $encoding = 'UTF-8') {
return htmlspecialchars((string)$data, $flags, $encoding);
}
}

View File

@@ -2,13 +2,11 @@
namespace ppa\Controller;
/**
* Description of Welcome
*
* @author reich
*/
class WelcomeController
{
private $notesModel;
private $view;
public function setView(\ppa\Library\View $view)
{
$this->view = $view;
@@ -16,5 +14,8 @@ class WelcomeController
function showWelcome()
{
if ($this->notesModel === null) {
$this->notesModel = new \ppa\Model\NotesModel();
}
}
}

164
JavaScript/script.js Normal file
View File

@@ -0,0 +1,164 @@
document.addEventListener('DOMContentLoaded', () => {
// --- Password Strength Checker for Registration ---
const passwordInput = document.getElementById('password');
const passwordStrengthDiv = document.getElementById('password-strength');
const passwordCriteriaUl = passwordStrengthDiv ? passwordStrengthDiv.querySelector('ul') : null;
if (passwordInput && passwordStrengthDiv && passwordCriteriaUl) {
const criteria = [
{ regex: /.{8,}/, message: "At least 8 characters", id: "len" },
{ regex: /[A-Z]/, message: "An uppercase letter", id: "upper" },
{ regex: /[a-z]/, message: "A lowercase letter", id: "lower" },
{ regex: /[0-9]/, message: "A number", id: "num" },
{ regex: /[^A-Za-z0-9]/, message: "A special character", id: "special" }
];
// Populate criteria list
criteria.forEach(c => {
const li = document.createElement('li');
li.id = `crit-${c.id}`;
li.textContent = `${c.message}`;
li.classList.add('invalid');
passwordCriteriaUl.appendChild(li);
});
passwordInput.addEventListener('input', () => {
const password = passwordInput.value;
let strengthScore = 0;
criteria.forEach(c => {
const li = document.getElementById(`crit-${c.id}`);
if (c.regex.test(password)) {
li.textContent = `${c.message}`;
li.classList.remove('invalid');
li.classList.add('valid');
strengthScore++;
} else {
li.textContent = `${c.message}`;
li.classList.remove('valid');
li.classList.add('invalid');
}
});
const strengthBar = document.querySelector('.password-strength-meter div');
if (strengthBar) {
strengthBar.style.width = (strengthScore / criteria.length * 100) + '%';
if (strengthScore < 2) strengthBar.style.backgroundColor = 'red';
else if (strengthScore < 4) strengthBar.style.backgroundColor = 'orange';
else strengthBar.style.backgroundColor = 'green';
}
if (password.length === 0) {
if (strengthBar) strengthBar.style.width = '0%';
}
});
// Trigger initial check if there's a value (e.g. browser autofill)
if(passwordInput.value) passwordInput.dispatchEvent(new Event('input'));
}
// --- Drag and Drop File Upload ---
const dropZone = document.getElementById('drop-zone');
const noteTitleInput = document.getElementById('title');
const noteContentInput = document.getElementById('content');
const markdownPreview = document.getElementById('markdown-preview');
if (dropZone && noteTitleInput && noteContentInput) {
dropZone.addEventListener('dragover', (event) => {
event.preventDefault();
dropZone.classList.add('drag-over');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('drag-over');
});
dropZone.addEventListener('drop', (event) => {
event.preventDefault();
dropZone.classList.remove('drag-over');
const files = event.dataTransfer.files;
if (files.length > 0) {
const file = files[0];
if (file.type === 'text/plain' || file.type === 'text/markdown' || file.name.endsWith('.md') || file.name.endsWith('.txt')) {
const reader = new FileReader();
reader.onload = (e) => {
noteTitleInput.value = file.name.replace(/\.(md|txt)$/i, '');
noteContentInput.value = e.target.result;
if (window.updateMarkdownPreview) updateMarkdownPreview();
};
reader.readAsText(file);
} else {
displayMessage('Please drop a .txt or .markdown file.', 'info');
}
}
});
}
if (noteContentInput && markdownPreview && typeof showdown !== 'undefined') {
const converter = new showdown.Converter({sanitize: true});
window.updateMarkdownPreview = function() {
markdownPreview.innerHTML = converter.makeHtml(noteContentInput.value);
}
noteContentInput.addEventListener('input', window.updateMarkdownPreview);
if(noteContentInput.value) window.updateMarkdownPreview();
} else if (noteContentInput && markdownPreview) {
noteContentInput.addEventListener('input', () => {
markdownPreview.textContent = "Preview updates on save (or include Showdown.js library for live preview).";
});
if(noteContentInput.value) markdownPreview.textContent = "Preview updates on save (or include Showdown.js library for live preview).";
}
// --- Table Sorting ---
document.querySelectorAll('.notes-table th[data-sort]').forEach(headerCell => {
headerCell.addEventListener('click', () => {
const sortBy = headerCell.dataset.sort;
const currentUrl = new URL(window.location.href);
const currentSortBy = currentUrl.searchParams.get('sort_by');
const currentSortOrder = currentUrl.searchParams.get('sort_order') || 'asc'; // Default to asc if not set
let newSortOrder = 'asc';
if (sortBy === currentSortBy && currentSortOrder.toLowerCase() === 'asc') {
newSortOrder = 'desc';
}
// If different column, or current is desc, start with asc for the new column
currentUrl.searchParams.set('sort_by', sortBy);
currentUrl.searchParams.set('sort_order', newSortOrder);
window.location.href = currentUrl.toString();
});
});
}); // End DOMContentLoaded
function displayMessage(message, type = 'info') {
const existingAlert = document.querySelector('#alert-container .alert');
if (existingAlert) {
existingAlert.remove();
}
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type}`;
alertDiv.innerHTML = message; // Use innerHTML if message contains HTML (e.g. list from server)
let alertContainer = document.getElementById('alert-container');
if (!alertContainer) {
alertContainer = document.createElement('div');
alertContainer.id = 'alert-container';
alertContainer.style.position = 'fixed';
alertContainer.style.top = '70px';
alertContainer.style.left = '50%';
alertContainer.style.transform = 'translateX(-50%)';
alertContainer.style.zIndex = '1000';
alertContainer.style.width = 'auto'; // Fit content
alertContainer.style.minWidth = '300px';
alertContainer.style.maxWidth = '90%';
document.body.prepend(alertContainer);
}
alertContainer.appendChild(alertDiv);
setTimeout(() => {
alertDiv.style.opacity = '0';
alertDiv.style.transition = 'opacity 0.5s ease-out';
setTimeout(() => {
if (alertDiv.parentNode) alertDiv.remove();
}, 500);
}, 5000);
}

View File

@@ -7,10 +7,10 @@ abstract class Database {
/**
* Zugangsdaten für die Datenbank
*/
private $dbName = "fotofreunde"; //Datenbankname
private $linkName = "localhost"; //Datenbank-Server
private $user = "root"; //Benutzername
private $pw = "root"; //Passwort
private $dbName = "pbbfa23civ_notes"; //Datenbankname
private $linkName = "mysql.pb.bib.de"; //Datenbank-Server
private $user = "pbbfa23civ"; //Benutzername
private $pw = "8HEFfSy85CFh"; //Passwort
/**
* Stellt eine Verbindung zur Datenbank her

View File

@@ -1,28 +0,0 @@
<?php
namespace ppa\Model;
use ppa\Model\ParticipantModel;
class GalleryModel extends Database
{
public function selectPhotos()
{
$sql = "SELECT title, filename, category.description, firstname, gallery.created
, gallery.description as alt
FROM gallery
JOIN category ON category.id = categoryId
JOIN user ON user.id = userId
ORDER BY gallery.created DESC";
$pdo = $this->linkDB();
try {
$res = $pdo->query($sql);
} catch (\PDOException $e) {
new \ppa\Library\ErrorMsg("Ihre Anfrage konnte nicht verarbeitet werden", $e);
die;
}
return $res->fetchAll(\PDO::FETCH_ASSOC);
}
}

296
Model/NotesModel.php Normal file
View File

@@ -0,0 +1,296 @@
<?php
namespace ppa\Model;
use ppa\Model\ParticipantModel;
use PDOException;
class NotesModel extends Database
{
public function selectNotesForUser($userid, $isAdmin = false, $sortBy = 'updated_at', $sortOrder = 'DESC')
{
$pdo = $this->linkDB();
$erg = array();
// Whitelist of allowed sort columns
$allowedSortColumns = ['id', 'title', 'owner_username', 'updated_at', 'priority'];
$allowedSortOrders = ['ASC', 'DESC'];
$sortBy = in_array($sortBy, $allowedSortColumns) ? $sortBy : 'updated_at';
$sortOrder = in_array(strtoupper($sortOrder), $allowedSortOrders) ? strtoupper($sortOrder) : 'DESC';
try {
if ($isAdmin) {
$sql = "SELECT n.id, n.title, n.content, n.created_at, n.updated_at, u.username AS owner_username, p.name AS priority
FROM notes n
JOIN priority p ON n.priority = p.id
JOIN users u ON n.user_id = u.id
ORDER BY {$sortBy} {$sortOrder}";
$stmt = $pdo->prepare($sql);
$stmt->execute();
} else {
$sql = "SELECT n.id, n.title, n.content, n.created_at, n.updated_at, p.name AS priority
FROM notes n
JOIN priority p ON n.priority = p.id
WHERE user_id = :userid
ORDER BY {$sortBy} {$sortOrder}";
$stmt = $pdo->prepare($sql);
$stmt->execute(['userid' => $userid]);
}
$erg = $stmt->fetchAll(\PDO::FETCH_ASSOC);
return $erg;
} catch (PDOException $e) {
error_log("Database Error in selectNotesForUser: " . $e->getMessage());
return false;
}
}
public function selectFiles($userid, $isAdmin = false, $sortBy = 'updated_at', $sortOrder = 'DESC')
{
$pdo = $this->linkDB();
$erg = array();
// Whitelist of allowed sort columns
$allowedSortColumns = ['id', 'original_filename', 'stored_filename', 'note_id', 'owner_username', 'uploaded_at', 'file_size'];
$allowedSortOrders = ['ASC', 'DESC'];
$sortBy = in_array($sortBy, $allowedSortColumns) ? $sortBy : 'uploaded_at';
$sortOrder = in_array(strtoupper($sortOrder), $allowedSortOrders) ? strtoupper($sortOrder) : 'DESC';
try {
if ($isAdmin) {
$sql = "SELECT f.*, n.title AS note_title, u.username AS owner_username
FROM files f
JOIN notes n ON f.note_id = n.id
JOIN users u ON n.user_id = u.id
ORDER BY {$sortBy} {$sortOrder}";
$stmt = $pdo->prepare($sql);
$stmt->execute();
}
$erg = $stmt->fetchAll(\PDO::FETCH_ASSOC);
return $erg;
} catch (PDOException $e) {
error_log("Database Error in selectFiles: " . $e->getMessage());
return false;
}
}
function getNoteById($noteId) {
$pdo = $this->linkDB();
if (!$pdo) return null;
try {
if ($_SESSION['role'] === 'admin') { // Admin can fetch any note
$stmt = $pdo->prepare("SELECT n.*, u.username as owner_username FROM notes n JOIN users u ON n.user_id = u.id WHERE n.id = ?");
$stmt->execute([$noteId]);
} else { // Regular user can only fetch their own notes
$stmt = $pdo->prepare("SELECT * FROM notes WHERE id = ? AND user_id = ?");
$stmt->execute([$noteId, $_SESSION['user_id']]);
}
return $stmt->fetch();
} catch (PDOException $e) {
error_log("Get Note Error: " . $e->getMessage());
return null;
}
}
function createNote($title, $content, $userId, $priority) {
$pdo = $this->linkDB();
if (!$pdo) return ['success' => false, 'message' => 'Database error.'];
if (empty(trim($title))) return ['success' => false, 'message' => 'Title is required.'];
try {
$stmt = $pdo->prepare("INSERT INTO notes (user_id, title, content, priority) VALUES (?, ?, ?, ?)");
$stmt->execute([$userId, trim($title), $content, $priority]); // user_id is current session user
$noteId = $pdo->lastInsertId();
$uploadResult = $this->uploadFiles($noteId);
if (!$uploadResult['success']) {
return $uploadResult;
}
return ['success' => true, 'message' => 'Note created successfully.'];
} catch (PDOException $e) {
error_log("Create Note Error: " . $e->getMessage());
return ['success' => false, 'message' => 'Failed to create note.'];
}
}
function editNote($noteId, $title, $content, $userId, $priority) {
$pdo = $this->linkDB();
if (!$pdo) return ['success' => false, 'message' => 'Database error.'];
if (empty(trim($title))) return ['success' => false, 'message' => 'Title is required.'];
try {
if ($this->isAdmin()) { // Admin can update any note, user_id for record not changed
$stmt = $pdo->prepare("UPDATE notes SET title = ?, content = ?, priority = ? WHERE id = ?");
$params = [trim($title), $content, $priority, $noteId];
} else { // User can only update their own note
$stmt = $pdo->prepare("UPDATE notes SET title = ?, content = ?, priority = ? WHERE id = ? AND user_id = ?");
$params = [trim($title), $content, $priority, $noteId, $userId];
}
$stmt->execute($params);
$uploadResult = $this->uploadFiles($noteId);
if (!$uploadResult['success']) {
return $uploadResult;
}
if ($stmt->rowCount() > 0) {
return ['success' => true, 'message' => 'Note updated successfully.'];
}
// Check if note exists if rowCount is 0
$checkStmt = $this->isAdmin() ? $pdo->prepare("SELECT id FROM notes WHERE id=?") : $pdo->prepare("SELECT id FROM notes WHERE id=? AND user_id=?");
$checkParams = $this->isAdmin() ? [$noteId] : [$noteId, $userId];
$checkStmt->execute($checkParams);
if ($checkStmt->fetch()) {
return ['success' => true, 'message' => 'No changes made to the note.'];
}
return ['success' => false, 'message' => 'Note not found or permission denied.'];
} catch (PDOException $e) {
error_log("Update Note Error: " . $e->getMessage());
return ['success' => false, 'message' => 'Failed to update note.'];
}
}
function deleteNote($noteId, $userId) {
$pdo = $this->linkDB();
if (!$pdo) return ['success' => false, 'message' => 'Database error.'];
try {
if ($this->isAdmin()) { // Admin can delete any note
$stmt = $pdo->prepare("DELETE FROM notes WHERE id = ?");
$params = [$noteId];
} else { // User can only delete their own note
$stmt = $pdo->prepare("DELETE FROM notes WHERE id = ? AND user_id = ?");
$params = [$noteId, $userId];
}
$stmt->execute($params);
if ($stmt->rowCount() > 0) {
return ['success' => true, 'message' => 'Note deleted successfully.'];
}
return ['success' => false, 'message' => 'Note not found or permission denied.'];
} catch (PDOException $e) {
error_log("Delete Note Error: " . $e->getMessage());
return ['success' => false, 'message' => 'Failed to delete note.'];
}
}
function getUploadedFiles($noteId) {
$pdo = $this->linkDB();
if (!$pdo) return [];
try {
$stmt = $pdo->prepare("SELECT * FROM files WHERE note_id = ?");
$stmt->execute([$noteId]);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
} catch (PDOException $e) {
error_log("Get Uploaded Files Error: " . $e->getMessage());
return [];
}
}
function getFileCount() {
$pdo = $this->linkDB();
if (!$pdo) return 0;
try {
$stmt = $pdo->prepare("SELECT COUNT(*) FROM files");
$stmt->execute();
return $stmt->fetchColumn();
} catch (PDOException $e) {
error_log("Get Files Count Error: " . $e->getMessage());
return 0;
}
}
function getNoteCount() {
$pdo = $this->linkDB();
if (!$pdo) return 0;
try {
$stmt = $pdo->prepare("SELECT COUNT(*) FROM notes");
$stmt->execute();
return $stmt->fetchColumn();
} catch (PDOException $e) {
error_log("Get Notes Count Error: " . $e->getMessage());
return 0;
}
}
function isLoggedIn() {
return isset($_SESSION['user_id']);
}
function isAdmin() {
return $this->isLoggedIn() && isset($_SESSION['role']) && $_SESSION['role'] === 'admin';
}
public function uploadFiles($noteId) {
$pdo = $this->linkDB();
if (!$pdo) return ['success' => false, 'message' => 'Database error.'];
$uploadDir = __DIR__ . '/../Uploads/';
$uploadedFileNames = [];
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0777, true);
}
if (isset($_FILES['attachments']) && !empty($_FILES['attachments']['name'][0])) {
$files = $_FILES['attachments'];
foreach ($files['name'] as $key => $name) {
if ($files['error'][$key] === UPLOAD_ERR_OK) {
$tmpName = $files['tmp_name'][$key];
$safeFilename = basename($name);
$uniqueFilename = time() . '-' . preg_replace('/[^A-Za-z0-9.\-]/', '_', $safeFilename);
$destination = $uploadDir . $uniqueFilename;
if (move_uploaded_file($tmpName, $destination)) {
$uploadedFileNames[] = $uniqueFilename;
$stmt = $pdo->prepare("INSERT INTO files (note_id, original_filename, stored_filename, file_type, file_size, uploaded_at) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$noteId, $safeFilename, $uniqueFilename, $files['type'][$key], $files['size'][$key], date('Y-m-d H:i:s')]);
} else {
$errmsg = "Error: Could not move uploaded file '$safeFilename'.";
}
} else {
$errmsg = "Error uploading file '$name'. Error code: " . $files['error'][$key];
}
}
}
if (isset($errmsg)) {
return ['success' => false, 'message' => $errmsg];
}
return ['success' => true, 'message' => 'Files uploaded successfully.', 'fileNames' => $uploadedFileNames];
}
public function deleteFile($fileId, $userId) {
$pdo = $this->linkDB();
if (!$pdo) return ['success' => false, 'message' => 'Database error.'];
try {
// Delete the local file
$stmt = $pdo->prepare("SELECT stored_filename FROM files WHERE id = ?");
$stmt->execute([$fileId]);
$file = $stmt->fetch();
if ($file) {
$filePath = __DIR__ . '/../Uploads/' . $file['stored_filename'];
if (file_exists($filePath)) {
unlink($filePath);
}
}
if ($this->isAdmin()) { // Admin can delete any file
$stmt = $pdo->prepare("DELETE FROM files WHERE id = ?");
$params = [$fileId];
} else { // User can only delete their own files
$stmt = $pdo->prepare("DELETE FROM files WHERE id = ? AND note_id IN (SELECT id FROM notes WHERE user_id = ?)");
$params = [$fileId, $userId];
}
$stmt->execute($params);
if ($stmt->rowCount() > 0) {
return ['success' => true, 'message' => 'File deleted successfully.'];
}
return ['success' => false, 'message' => 'File not found or permission denied.'];
} catch (PDOException $e) {
error_log("Delete File Error: " . $e->getMessage());
return ['success' => false, 'message' => 'Failed to delete file.'];
}
}
}

85
Model/UserModel.php Normal file
View File

@@ -0,0 +1,85 @@
<?php
namespace ppa\Model;
use ppa\Model\ParticipantModel;
//use ppb\Library\Msg;
use PDOException;
class UserModel extends Database
{
public function loginUser($username, $password)
{
$pdo = $this->linkDB();
if (!$pdo) return ['success' => false, 'message' => 'Database connection error.'];
try {
$stmt = $pdo->prepare("SELECT id, username, password, role FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['role'] = $user['role']; // Store role
return ['success' => true, 'message' => 'Login successful!'];
}
return ['success' => false, 'message' => 'Invalid username or password.'];
} catch (PDOException $e) {
error_log("Login Error: " . $e->getMessage());
return ['success' => false, 'message' => 'An error occurred during login.'];
}
}
function logoutUser()
{
session_unset();
session_destroy();
return ['success' => true, 'message' => 'Logged out successfully.'];
}
function registerUser($username, $password) {
$pdo = $this->linkDB();
if (!$pdo) return ['success' => false, 'message' => 'Database connection error.'];
$errors = [];
if (empty($username)) $errors[] = "Username is required.";
if (empty($password)) $errors[] = "Password is required.";
if (strlen($password) < 8) $errors[] = "Password must be at least 8 characters.";
if (!preg_match('/[A-Z]/', $password)) $errors[] = "Password needs an uppercase letter.";
if (!preg_match('/[a-z]/', $password)) $errors[] = "Password needs a lowercase letter.";
if (!preg_match('/[0-9]/', $password)) $errors[] = "Password needs a number.";
if (!preg_match('/[^A-Za-z0-9]/', $password)) $errors[] = "Password needs a special character.";
if (!empty($errors)) {
return ['success' => false, 'message' => implode("\\n", $errors)];
}
try {
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = ?");
$stmt->execute([$username]);
if ($stmt->fetch()) {
return ['success' => false, 'message' => 'Username already taken.'];
}
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("INSERT INTO users (username, password) VALUES (?, ?)"); // Role defaults to 'user'
$stmt->execute([$username, $hashedPassword]);
return ['success' => true, 'message' => 'Registration successful! Please login.'];
} catch (PDOException $e) {
error_log("Registration Error: " . $e->getMessage());
return ['success' => false, 'message' => 'An error occurred during registration.'];
}
}
function getUserCount() {
$pdo = $this->linkDB();
if (!$pdo) return 0;
try {
$stmt = $pdo->prepare("SELECT COUNT(*) FROM users");
$stmt->execute();
return $stmt->fetchColumn();
} catch (PDOException $e) {
error_log("Get User Count Error: " . $e->getMessage());
return 0;
}
}
}

View File

@@ -1,17 +0,0 @@
<?php include dirname(__DIR__).'/header.phtml'; ?>
<h2>Galerie</h2>
<div class="container">
<?php
foreach($photos as $p) {
echo '<div class="item-4-12">';
echo '<h3>' . $p["title"] . '</h3>'
. '<img src="images/' . $p["filename"] . '" />';
echo '</div>';
}
?>
</div>
<?php include dirname(__DIR__).'/footer.phtml'; ?>

View File

@@ -0,0 +1,85 @@
<?php
use ppa\Model\NotesModel;
include dirname(__DIR__).'/header.phtml';
//// Test write permissions
//// This is the directory we will upload files to.
//$uploadDir = __DIR__ . '/../Uploads/';
//if (!file_exists($uploadDir)) {
// mkdir($uploadDir, 0777, true);
//}
//$testFile = $uploadDir . 'test_write.txt';
//$testContent = 'Test write operation at ' . date('Y-m-d H:i:s');
//$writeResult = file_put_contents($testFile, $testContent);
$parsedown = new Parsedown();
$parsedown->setSafeMode(true);
$this->notesModel = new \ppa\Model\NotesModel();
$note = null;
function isLoggedIn() {
return isset($_SESSION['user_id']);
}
function isAdmin() {
return isLoggedIn() && isset($_SESSION['role']) && $_SESSION['role'] === 'admin';
}
function sanitize($data, $flags = ENT_QUOTES, $encoding = 'UTF-8') {
return htmlspecialchars((string)$data, $flags, $encoding);
}
?>
<div class="container">
<div class="page-header">
<h2>Create New Note</h2>
<a href="?controller=Notes&page=showNotes&do=showNotes" class="button secondary">Cancel</a>
</div>
<label class="error-message"><?php if (isset($errmsg)):?>
<?php echo $errmsg;?>
<?php endif; ?></label>
<div id="drop-zone">Drag & drop a .txt or .md file here, or fill manually.</div>
<form id="note-form" method="POST" enctype="multipart/form-data">
<input type="hidden" name="action" value="create_note">
<div class="form-group">
<label for="title">Title:</label>
<input type="text" id="title" name="title" value="" required>
</div>
<div class="form-group">
<label for="content">Content (Markdown supported):</label>
<textarea id="content" name="content" rows="10" required></textarea>
</div>
<div class="form-group">
<label>Live Markdown Preview:</label>
<div id="markdown-preview" class="markdown-preview">
Start typing or drop a file to see preview...
</div>
</div>
<div class="form-group">
<label>Priorität:</label>
<select name="priority" id="priority">
<option value="1">LOW</option>
<option value="2">MID</option>
<option value="3">HIGH</option>
</select>
</div>
<div class="form-group">
<label for="attachments">Attach Files:</label>
<input type="file" id="attachments" name="attachments[]" multiple>
</div>
<div class="form-actions">
<button type="submit" class="button">Create Note</button>
</div>
<input type="hidden" name="controller" value="Notes">
<input type="hidden" name="do" value="createNote">
</form>
</div>

View File

@@ -0,0 +1,89 @@
<?php
use ppa\Model\NotesModel;
include dirname(__DIR__).'/header.phtml';
$parsedown = new Parsedown();
$parsedown->setSafeMode(true);
$this->notesModel = new \ppa\Model\NotesModel();
$note = null;
$noteId = $_GET['id'] ?? 0;
$note = $this->notesModel->getNoteById($noteId, $_SESSION['user_id']);
if (!$note) {
echo "<div class='alert alert-danger'>Note not found or you don't have permission to edit it.</div>";
echo "<a href='?controller=Notes&page=showNotes&do=showNotes' class='button secondary'>Back to Dashboard</a>";
}
$files = $this->notesModel->getUploadedFiles($noteId);
function isLoggedIn() {
return isset($_SESSION['user_id']);
}
function isAdmin() {
return isLoggedIn() && isset($_SESSION['role']) && $_SESSION['role'] === 'admin';
}
function sanitize($data, $flags = ENT_QUOTES, $encoding = 'UTF-8') {
return htmlspecialchars((string)$data, $flags, $encoding);
}
?>
<div class="container">
<div class="page-header">
<h2><?php echo 'Edit Note' . (isAdmin() && $note && $note['user_id'] != $_SESSION['user_id'] ? ' (Admin Edit - Owner: '.sanitize($note['owner_username']).')' : '') ?></h2>
<a href="?controller=Notes&page=showNotes&do=showNotes" class="button secondary">Cancel</a>
</div>
<div id="drop-zone">Drag & drop a .txt or .md file here, or fill manually.</div>
<form id="note-form" method="POST" enctype="multipart/form-data">
<input type="hidden" name="action" value="update_note">
<input type="hidden" name="note_id" value="<?php echo sanitize($note['id']); ?>">
<div class="form-group">
<label for="title">Title:</label>
<input type="text" id="title" name="title" value="<?php echo $note ? sanitize($note['title']) : ''; ?>" required>
</div>
<div class="form-group">
<label for="content">Content (Markdown supported):</label>
<textarea id="content" name="content" rows="10" required><?php echo $note ? sanitize($note['content']) : ''; ?></textarea>
</div>
<div class="form-group">
<label>Live Markdown Preview:</label>
<div id="markdown-preview" class="markdown-preview">
<?php if($note && !empty($note['content'])) echo $parsedown->text(sanitize($note['content'])); else echo "Start typing or drop a file to see preview..."; ?>
</div>
</div>
<div class="form-group">
<label>Priorität:</label>
<select name="priority" id="priority">
<option value="1">LOW</option>
<option value="2">MID</option>
<option value="3">HIGH</option>
</select>
</div>
<div class="form-group">
<label for="attachments">Attach additional Files:</label>
<input type="file" id="attachments" name="attachments[]" multiple>
</div>
<?php if($files && count($files) > 0): ?>
<div class="form-group">
<label>Files currently attached:</label>
<ul>
<?php foreach($files as $file): ?>
<li>
<a href="<?php echo substr($_SERVER['PHP_SELF'], 0, -9).'Uploads/'.$file['stored_filename']; ?>" download target="_blank"><?php echo htmlspecialchars($file['original_filename']); ?></a>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<div class="form-actions">
<button type="submit" class="button">Update Note</button>
</div>
<input type="hidden" name="controller" value="Notes">
<input type="hidden" name="do" value="editNote">
</form>
</div>

View File

@@ -0,0 +1,64 @@
<?php include dirname(__DIR__).'/header.phtml'; ?>
<div class="container">
<?php
function isLoggedIn() {
return isset($_SESSION['user_id']);
}
function isAdmin() {
return isLoggedIn() && isset($_SESSION['role']) && $_SESSION['role'] === 'admin';
}
function sanitize($data, $flags = ENT_QUOTES, $encoding = 'UTF-8') {
return htmlspecialchars((string)$data, $flags, $encoding);
}
$sortBy = $_GET['sort_by'] ?? 'uploaded_at';
$sortOrder = strtoupper($_GET['sort_order'] ?? 'DESC'); // Ensure uppercase for comparison
?>
<div class="page-header">
<h2>All Users' Files</h2>
</div>
<?php if (isset($errmsg)): ?>
<label class="error-message"><?php echo $errmsg; ?></label>
<?php endif; ?>
<table class="notes-table">
<thead>
<tr>
<th data-sort="id">File ID <span class="sort-icon"><?php if($sortBy === 'id') echo $sortOrder === 'ASC' ? '▲' : '▼'; ?></span></th>
<th data-sort="original_filename">Original File Name <span class="sort-icon"><?php if($sortBy === 'original_filename') echo $sortOrder === 'ASC' ? '▲' : '▼'; ?></span></th>
<th data-sort="stored_filename">Stored File Name <span class="sort-icon"><?php if($sortBy === 'stored_filename') echo $sortOrder === 'ASC' ? '▲' : '▼'; ?></span></th>
<th data-sort="note_id">Note ID <span class="sort-icon"><?php if($sortBy === 'note_id') echo $sortOrder === 'ASC' ? '▲' : '▼'; ?></span></th>
<th data-sort="owner_username">Owner <span class="sort-icon"><?php if($sortBy === 'owner_username') echo $sortOrder === 'ASC' ? '▲' : '▼'; ?></span></th>
<th data-sort="uploaded_at">Uploaded At <span class="sort-icon"><?php if($sortBy === 'uploaded_at') echo $sortOrder === 'ASC' ? '▲' : '▼'; ?></span></th>
<th data-sort="file_size">File Size <span class="sort-icon"><?php if($sortBy === 'file_size') echo $sortOrder === 'ASC' ? '▲' : '▼'; ?></span></th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($files as $file): ?>
<tr>
<td><?php echo sanitize($file['id']); ?></td>
<td><a href="<?php echo substr($_SERVER['PHP_SELF'], 0, -9).'Uploads/'.$file['stored_filename']; ?>"><?php echo sanitize($file['stored_filename']); ?></a></td>
<td><?php echo sanitize($file['stored_filename']); ?></td>
<td><?php echo sanitize($file['note_id']); ?></td>
<td><?php echo sanitize($file['owner_username']); ?></td>
<td><?php echo date("d.m.Y H:i", strtotime($file['uploaded_at'])); ?></td>
<td><?php echo round(sanitize($file['file_size']) / 1024, 2) . ' KB'; ?></td>
<td class="actions-cell">
<form method="POST" action="?controller=Notes&do=deleteFile" onsubmit="return confirm('Are you sure you want to delete this file?');" style="display: inline;">
<input type="hidden" name="file_id" value="<?php echo $file['id']; ?>">
<button type="submit" class="button danger">Delete</button>
<input type="hidden" name="controller" value="Notes">
<input type="hidden" name="do" value="deleteFile">
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php include dirname(__DIR__).'/footer.phtml'; ?>

View File

@@ -0,0 +1,59 @@
<?php include dirname(__DIR__).'/header.phtml'; ?>
<?php
$parsedown = new Parsedown();
$parsedown->setSafeMode(true);
$this->notesModel = new \ppa\Model\NotesModel();
$files = $this->notesModel->getUploadedFiles($note['id']);
?>
<div class="container">
<?php if (isset($note) && $note): ?>
<div class="note-details">
<div class="note-header">
<h2><?php echo htmlspecialchars($note['title'] ?? ''); ?></h2>
<div class="note-meta">
<?php if (($isAdmin ?? false) && isset($note['owner_username'])): ?>
<span class="note-owner">Owner: <?php echo htmlspecialchars($note['owner_username']); ?></span>
<?php endif; ?>
<span class="note-date">
Last updated: <?php echo isset($note['updated_at']) ? date("d.m.Y H:i", strtotime($note['updated_at'])) : 'N/A'; ?>
</span>
</div>
</div>
<div class="note-content">
<?php echo $parsedown->text($note['content'] ?? ''); ?>
</div>
<div class="note-files">
<?php if (isset($files) && count($files) > 0): ?>
<h3>Attached Files:</h3>
<ul>
<?php foreach ($files as $file): ?>
<li>
<a href="<?php echo substr($_SERVER['PHP_SELF'], 0, -9).'Uploads/'.$file['stored_filename']; ?>" download target="_blank"><?php echo htmlspecialchars($file['original_filename']); ?></a>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
<div class="note-actions">
<a href="?controller=Notes&page=showNotes&do=showNotes" class="button">Back to Notes</a>
<?php if (isset($note['id'])): ?>
<a href="?controller=Notes&do=editNote&id=<?php echo (int)$note['id']; ?>" class="button">Edit Note</a>
<?php endif; ?>
</div>
</div>
<?php else: ?>
<div class="error-message">
<h2>Note Not Found</h2>
<p><?php echo htmlspecialchars($error ?? 'The requested note could not be found.'); ?></p>
<a href="?controller=Notes&page=showNotes&do=showNotes" class="button">Back to Notes</a>
</div>
<?php endif; ?>
</div>
<?php include dirname(__DIR__).'/footer.phtml'; ?>

View File

@@ -0,0 +1,83 @@
<?php include dirname(__DIR__).'/header.phtml'; ?>
<div class="container">
<?php
$parsedown = new Parsedown();
$parsedown->setSafeMode(true);
function isLoggedIn() {
return isset($_SESSION['user_id']);
}
function isAdmin() {
return isLoggedIn() && isset($_SESSION['role']) && $_SESSION['role'] === 'admin';
}
function sanitize($data, $flags = ENT_QUOTES, $encoding = 'UTF-8') {
return htmlspecialchars((string)$data, $flags, $encoding);
}
$sortBy = $_GET['sort_by'] ?? 'updated_at';
$sortOrder = strtoupper($_GET['sort_order'] ?? 'DESC'); // Ensure uppercase for comparison
?>
<div class="page-header">
<h2><?php echo isAdmin() ? "All Users' Notes" : "My Notes"; ?></h2>
<a href="?controller=Notes&do=createNote" class="button">Create New Note</a>
</div>
<?php if (isset($errmsg)): ?>
<label class="error-message"><?php echo $errmsg; ?></label>
<?php endif; ?>
<table class="notes-table">
<thead>
<tr>
<th data-sort="id">ID <span class="sort-icon"><?php if($sortBy === 'id') echo $sortOrder === 'ASC' ? '▲' : '▼'; ?></span></th>
<th data-sort="title">Title <span class="sort-icon"><?php if($sortBy === 'title') echo $sortOrder === 'ASC' ? '▲' : '▼'; ?></span></th>
<?php if (isAdmin()): ?>
<th data-sort="owner_username">Owner <span class="sort-icon"><?php if($sortBy === 'owner_username') echo $sortOrder === 'ASC' ? '▲' : '▼'; ?></span></th>
<?php endif; ?>
<th>Content (Preview)</th>
<th data-sort="updated_at">Last Edited <span class="sort-icon"><?php if($sortBy === 'updated_at') echo $sortOrder === 'ASC' ? '▲' : '▼'; ?></span></th>
<th data-sort="priority">Priority<span class="sort-icon"><?php if($sortBy === 'priority') echo $sortOrder === 'ASC' ? '▲' : '▼'; ?></span></th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($notes as $note): ?>
<tr>
<td><?php echo sanitize($note['id']); ?></td>
<td><a href="?controller=Notes&do=showNoteDetails&id=<?php echo $note['id']; ?>"><?php echo sanitize($note['title']); ?></a></td>
<?php if (isAdmin()): ?>
<td><?php echo sanitize($note['owner_username']); ?></td>
<?php endif; ?>
<td>
<?php
$plainTextContent = strip_tags($parsedown->text($note['content'] ?? ''));
$previewContent = mb_substr($plainTextContent, 0, 70);
echo sanitize($previewContent) . (mb_strlen($plainTextContent) > 70 ? '...' : '');
?>
</td>
<td><?php echo date("d.m.Y H:i", strtotime($note['updated_at'])); ?></td>
<?php
if($note['priority'] === 'LOW') echo ('<td class="style_low";>');
elseif($note['priority'] === 'MID') echo ('<td class="style_mid";>');
elseif($note['priority'] === 'HIGH') echo ('<td class="style_high";>');
echo sanitize($note['priority']);
echo ('</td>')
?>
<td class="actions-cell">
<a href="?controller=Notes&do=editNote&id=<?php echo $note['id']; ?>" class="button">Edit</a>
<form method="POST" action="?controller=Notes&do=deleteNote" onsubmit="return confirm('Are you sure you want to delete this note?');" style="display: inline;">
<input type="hidden" name="note_id" value="<?php echo $note['id']; ?>">
<button type="submit" class="button danger">Delete</button>
<input type="hidden" name="controller" value="Notes">
<input type="hidden" name="do" value="deleteNote">
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php include dirname(__DIR__).'/footer.phtml'; ?>

View File

@@ -0,0 +1,26 @@
<?php include dirname(__DIR__).'/header.phtml'; ?>
<div class="form-container">
<h2>Login</h2>
<form id="login-form" method="POST">
<input type="hidden" name="action" value="login">
<div class="form-group">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</div>
<div class="form-actions">
<button type="submit">Login</button>
<label><?php if (isset($errmsg)):?>
<?php echo $errmsg;?>
<?php endif; ?>
</label>
<p style="margin-top:15px; text-align:center;">Don't have an account? <a href="?controller=User&do=showUserRegisterForm">Register here</a></p>
</div>
<input type="hidden" name="controller" value="User">
<input type="hidden" name="do" value="loginUser">
</form>
</div>
<?php include dirname(__DIR__).'/footer.phtml'; ?>

View File

@@ -0,0 +1,28 @@
<?php include dirname(__DIR__).'/header.phtml'; ?>
<div class="form-container">
<h2>Register</h2>
<form id="register-form" method="POST">
<input type="hidden" name="action" value="register">
<div class="form-group">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<div class="password-strength-meter"><div id="strength-bar"></div></div>
<div id="password-strength"><ul></ul></div>
</div>
<div class="form-group">
<label for="confirm_password">Confirm Password:</label>
<input type="password" id="confirm_password" name="confirm_password" required>
</div>
<div class="form-actions">
<button type="submit" >Register</button>
<p style="margin-top:15px; text-align:center;">Already have an account? <a href="?controller=User&do=showUserLoginForm">Login here</a></p>
</div>
<input type="hidden" name="controller" value="User">
<input type="hidden" name="do" value="registerUser">
</form>
</div>
<?php include dirname(__DIR__).'/footer.phtml'; ?>

View File

@@ -1,6 +1,39 @@
<?php include dirname(__DIR__).'/header.phtml'; ?>
<?php
use ppa\Model\NotesModel;
include dirname(__DIR__).'/header.phtml';
<h2>Baustelle</h2>
$parsedown = new Parsedown();
$parsedown->setSafeMode(true);
$this->notesModel = new \ppa\Model\NotesModel();
$this->userModel = new \ppa\Model\UserModel();
?>
<h2>Welcome in out Notes App!</h2>
<p>To start, simply select an option in the navigation bar.</p>
<h2>Notes App statistics</h2>
<b style="font-size: 20px; margin: 20px">
<?php
echo $this->notesModel->getNoteCount();
?>
Notes
</b><br>
<b style="font-size: 20px; margin: 20px">
<?php
echo $this->userModel->getUserCount();
?>
Users
</b><br>
<b style="font-size: 20px; margin: 20px">
<?php
echo $this->notesModel->getFileCount();
?>
Files
</b><br>
<?php include dirname(__DIR__).'/footer.phtml'; ?>

View File

@@ -1,27 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<title>Gallery View Vorlage</title>
<title>Notizen App</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width">
<link href="CSS/style.css" rel="stylesheet" type="text/css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js"></script>
<script src="JavaScript/script.js"></script>
</head>
<body>
<header>
<div class="wrapper">
<div class="button">
<a href="?controller=User&do=showUserLoginForm">Anmelden</a>
<header class="top-bar">
<h1>Notes App <?php if(isset($_SESSION['role']) && $_SESSION['role'] === 'admin') echo "<span style='font-size:0.7em; color:#ffdd57;'>(Admin Panel)</span>"; ?></h1>
<nav class="top-nav">
<ul>
<li><a href="?controller=Welcome&do=showWelcome">Welcome!</a></li>
<li><a href="?controller=Notes&do=showNotes">Notes</a></li>
<?php if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin'): ?>
<li><a href="?controller=Notes&do=fileManager">File Manager</a></li>
<?php endif; ?>
</ul>
</nav>
<?php if (isset($_SESSION['user_id'])): ?>
<div class="user-info">
<span>Welcome, <?php echo htmlspecialchars($_SESSION['username'], ENT_QUOTES, 'UTF-8'); ?>!</span>
<form id="logout-form" method="POST" style="display: inline;">
<a class="icon-button" href="?controller=User&do=logoutUser"></a>
</form>
</div>
<h1>Fotofreun<span>.de</span> OWL<span>e.V.</span></h1>
</div>
<?php else: ?>
<div class="user-info">
<?php if (!isset($_SESSION['user_id'])): ?>
<a href="?controller=User&do=showUserLoginForm">Login</a>
<?php endif; ?>
<?php if (!isset($_SESSION['user_id'])): ?>
<a href="?controller=User&do=showUserRegisterForm">Register</a>
<?php endif; ?>
</div>
<?php endif; ?>
</header>
<nav>
<ul>
<li><a href="?controller=Welcome&do=showWelcome">Willkommen</a></li>
<li><a href="#">Mitglieder</a></li>
<li><a href="?controller=Gallery&do=showPhotos">Galerie</a></li>
<li><a href="#">G&auml;stebuch</a></li>
</ul>
</nav>
<div class="wrapper">
<main>

View File

@@ -2,6 +2,8 @@
session_start();
require_once __DIR__ . '/Library/Parsedown.php';
spl_autoload_register(function ($className) {
if (substr($className, 0, 4) !== 'ppa\\') { return; }