From 674fabb7150a1f80428c5aff9e27288d7d6ec80f Mon Sep 17 00:00:00 2001 From: "Elias F." Date: Sat, 10 Jan 2026 17:22:49 +0100 Subject: [PATCH] asdlkif --- README.md | 285 +++--- .../_installation/03-create-table-notes.sql | 5 + .../04-create-table-messages.sql | 5 +- application/config/config.development.php | 2 +- application/controller/MessageController.php | 176 +++- application/controller/NoteController.php | 67 +- application/core/View.php | 9 + application/libs/SimpleMarkdown.php | 102 +++ application/model/MessageModel.php | 34 +- application/model/NoteModel.php | 26 +- application/view/message/conversation.php | 124 +-- application/view/message/global.php | 17 + application/view/message/index.php | 820 ++++++++++++++---- application/view/note/index.php | 493 ++++++++++- assets/footer/gray0_ctp_on_line.svg | 12 + assets/screenshots/notes_1.png | Bin 0 -> 30611 bytes assets/screenshots/notes_2.png | Bin 0 -> 37032 bytes migration_add_is_read.php | 33 + public/css/style.css | 3 + public/js/messaging.js | 293 +++++++ test_messaging.php | 118 +++ 21 files changed, 2135 insertions(+), 489 deletions(-) create mode 100644 application/libs/SimpleMarkdown.php create mode 100644 application/view/message/global.php create mode 100644 assets/footer/gray0_ctp_on_line.svg create mode 100644 assets/screenshots/notes_1.png create mode 100644 assets/screenshots/notes_2.png create mode 100644 migration_add_is_read.php create mode 100644 public/js/messaging.js create mode 100644 test_messaging.php diff --git a/README.md b/README.md index 0a38f7c..9cac5cc 100644 --- a/README.md +++ b/README.md @@ -1,220 +1,155 @@ -# HUGE – Installation und Setup +# HUGE - Elias Fähnrich -## Schnellstart +--- -- Voraussetzungen prüfen (siehe unten) -- Abhängigkeiten installieren -- Datenbank anlegen -- Webserver auf `public/` zeigen lassen -- Starten und testen +## Anpassung der Useranmeldung -Befehle (Beispiele): +#### Registrierung ohne Captcha und E-Mail-Verifikation -```bash -# Composer installieren (macOS – über Homebrew) -brew install composer +**Beschreibung:** +Die Registrierung wurde so angepasst, dass Benutzer ohne Captcha und E-Mail-Verifikation registriert werden können. Benutzer werden nach der Registrierung automatisch aktiviert. -# Abhängigkeiten holen (im Projektordner) -composer install +**Technische Umsetzung:** +- Entfernung des Captcha-Features aus dem Registrierungsprozess +- Deaktivierung der E-Mail-Verifizierung +- Automatische Aktivierung neuer Benutzerkonten +- Vereinfachung des Registrierungsformulars -# Datenbank & Tabellen anlegen -mysql -u root -p < application/_installation/01-create-database.sql -mysql -u root -p < application/_installation/02-create-table-users.sql -mysql -u root -p < application/_installation/03-create-table-notes.sql +**Admin-Funktion:** +- Nur Administratoren können über das gleiche Formular neue Benutzer anlegen +- Sicherstellung, dass nur autorisierte Personen Benutzerkonten erstellen können -# Rechte für Avatare (je nach OS anpassen) -# Ubuntu/Debian: -sudo chown -R www-data:www-data public/avatars -sudo chmod -R 775 public/avatars - -# macOS (Apache Standardnutzer _www): -sudo chown -R _www:_www public/avatars -sudo chmod -R 775 public/avatars +``` + +[📸 Screenshot: Vereinfachtes Registrierungsformular] +[📸 Screenshot: Admin-Benutzererstellung] +[📸 Screenshot: Automatisch aktivierter Benutzer] ``` --- -## Voraussetzungen +## Erweiterung der Admin-Funktionen -- PHP >= 5.5 (laut composer.json) -- Composer -- Apache 2.4 mit `mod_rewrite` -- MySQL/MariaDB -- PHP-Erweiterungen: PDO + pdo_mysql, OpenSSL, mbstring -- Schreibrechte für `public/avatars/` +#### 👥 Benutzergruppen-Verwaltung ---- +**Beschreibung:** +Implementierung einer erweiterten Benutzerverwaltung mit Gruppen-System. Anstelle des einfachen Typen-Feldes wurde eine vollwertige Gruppenverwaltung eingeführt. -## Projekt beziehen +**Gruppenstruktur:** +- **Admin (Typ 7)**: Voll administrative Rechte +- **Gast (Typ 1)**: Leserechte für öffentliche Inhalte +- **Normaler Benutzer (Typ 2)**: Standard-Benutzerrechte +- **Typen 3-6**: Reserviert für zukünftige Gruppenerweiterungen -- Repository klonen oder entpacken -- In den Projektordner wechseln +**Technische Umsetzung:** +- Erstellung einer neuen Tabelle `user_groups` zur Gruppendefinition +- Zuweisung von Gruppennamen zu den Typen +- Integration in die Benutzerverwaltung des Admins ---- +#### Öffentliches Benutzerverzeichnis mit DataTables -## Abhängigkeiten installieren (Composer) +**Beschreibung:** +Implementierung einer öffentlichen Benutzerliste, die alle Benutzer und ihre Gruppen anzeigt. Diese Liste ist für alle zugänglich, jedoch schreibgeschützt. -- Im Projektordner ausführen: +**Technische Features:** +- Integration von DataTables/jQuery für interaktive Tabellen +- Sortier- und Filterfunktionen +- Paginierung der Benutzerliste +- Responsive Darstellung auf verschiedenen Geräten -```bash -composer install +**Zugriffsrechte:** +- **Öffentlicher Zugriff**: Nur Anzeige der Benutzerliste +- **Admin-Zugriff**: Vollständige Verwaltungsfunktionen + +``` + +[📸 Screenshot: Admin-Gruppenzuweisung] +[📸 Screenshot: Öffentliches Benutzerverzeichnis mit DataTables] +[📸 Screenshot: jQuery DataTables in Aktion] ``` --- -## Webserver konfigurieren (Apache) +## jQuery - Einführung und Grundlagen -- DocumentRoot auf `.../huge/public` setzen -- `AllowOverride All` aktivieren (damit `.htaccess` greift) -- `mod_rewrite` aktivieren -- Apache neu starten +#### JavaScript Basics +jQuery ist eine JavaScript-Bibliothek, die grundlegende JavaScript-Kenntnisse voraussetzt. Wichtige Grundlagen umfassen: -Minimalbeispiel VirtualHost: +- DOM-Manipulation +- Event-Handling +- Asynchrone Programmierung +- Objektorientierte Programmierung -```apacheconf - - ServerName huge.local - DocumentRoot "/pfad/zu/huge/public" +#### jQuery Grundlagen +Die jQuery-Bibliothek ist zentral für die Interaktivität der Benutzeroberfläche: - - AllowOverride All - Require all granted - - - SetEnv APPLICATION_ENV development - ErrorLog "${APACHE_LOG_DIR}/huge_error.log" - CustomLog "${APACHE_LOG_DIR}/huge_access.log" combined - +**Einbindung:** +```html + ``` -Hinweis: `.htaccess` leitet auf `public/index.php` um. +**Zentrale Elemente:** +- **Dollarzeichen ($)**: Hauptselektor für DOM-Elemente +- **Basis-Syntax**: `$("element")` +- **Document Ready**: `$(document).ready(function(){ ... })` ---- +**Wichtige Selektoren:** +```javascript +// ID-Selektor (schnellste Methode) +$("#elementId") -## Datenbank anlegen +// Klassen-Selektor +$(".klassenname") -- SQL-Skripte in dieser Reihenfolge ausführen (`application/_installation/`): - - `01-create-database.sql` - - `02-create-table-users.sql` - - `03-create-table-notes.sql` +// Multi-Selektor +$("div, p, a, span") -Beispiel in der Shell: - -```bash -mysql -u root -p < application/_installation/01-create-database.sql -mysql -u root -p < application/_installation/02-create-table-users.sql -mysql -u root -p < application/_installation/03-create-table-notes.sql +// Komplexe Selektoren +$("#container .item:first-child") ``` ---- +**DOM-Manipulation:** +```javascript +// Einzelne Eigenschaft ändern +$("#test").css("color", "#FFFFFF"); -## Framework-Konfiguration (Entwicklung) +// Mehrere Eigenschaften ändern +$("#test").css({ + "color": "#FFFFFF", + "height": "25px" +}); -- Datei: `application/config/config.development.php` -- Wichtige Schlüssel: - - URL - - `URL` (Basis-URL, endet mit `/`; auto-detect möglich) - - Pfade - - `PATH_CONTROLLER`, `PATH_VIEW` (meist unverändert) - - Routing - - `DEFAULT_CONTROLLER`, `DEFAULT_ACTION` - - Datenbank - - `DB_TYPE`, `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASS`, `DB_CHARSET` - - Captcha - - `CAPTCHA_WIDTH`, `CAPTCHA_HEIGHT` - - Cookies & Session - - `COOKIE_RUNTIME`, `COOKIE_PATH`, `COOKIE_DOMAIN`, `COOKIE_SECURE`, `COOKIE_HTTP` - - `SESSION_RUNTIME` - - Avatare/Gravatar - - `USE_GRAVATAR`, `GRAVATAR_DEFAULT_IMAGESET`, `GRAVATAR_RATING` - - `AVATAR_SIZE`, `AVATAR_JPEG_QUALITY`, `AVATAR_DEFAULT_IMAGE` - - Ordner `public/avatars/` beschreibbar machen - - Sicherheit - - `ENCRYPTION_KEY`, `HMAC_SALT` (für eigene Installation ändern) - - E-Mail - - `EMAIL_USED_MAILER`, `EMAIL_USE_SMTP` - - `EMAIL_SMTP_HOST`, `EMAIL_SMTP_AUTH`, `EMAIL_SMTP_USERNAME`, `EMAIL_SMTP_PASSWORD`, `EMAIL_SMTP_PORT`, `EMAIL_SMTP_ENCRYPTION` - - `EMAIL_PASSWORD_RESET_*`, `EMAIL_VERIFICATION_*` - ---- - -## Umgebungen (Environment) - -- Klasse: `application/core/Environment.php` -- Ermittelt `APPLICATION_ENV` (Fallback: `development`) -- Weitere Datei möglich: `config.production.php` -- Apache-Variable setzen: - -```apacheconf -SetEnv APPLICATION_ENV production +// Methodenverkettung (Chaining) +$("#test") + .css({ "color": "#FFFFFF", "height": "25px" }) + .html("Neuer Text") + .show(); ``` ---- +#### DataTables Integration +DataTables erweitert HTML-Tabellen um leistungsstarke Funktionen: -## Verzeichnisrechte - -- `public/avatars/` beschreibbar -- Optional Logs/Uploads je nach Bedarf - ---- - -## Starten - -- Browser: `http:///` -- Registrierung/Login testen -- Mailversand nur mit korrekt konfiguriertem SMTP - ---- - -## Tests ausführen - -- PHPUnit unter `vendor/bin/phpunit` -- Konfiguration: `tests/phpunit.xml` - -```bash -vendor/bin/phpunit -c tests/phpunit.xml +```javascript +$(document).ready(function() { + $('#benutzerTabelle').DataTable({ + "language": { + "url": "//cdn.datatables.net/plug-ins/1.10.25/i18n/German.json" + }, + "pageLength": 25, + "responsive": true + }); +}); ``` ---- - -## Häufige Probleme - -- 404 bei allen Routen - - `mod_rewrite` aktivieren - - `AllowOverride All` setzen - - DocumentRoot auf `public/` -- Falsche Links/Assets - - `URL` in der Config prüfen -- Datenbankfehler - - `DB_*`-Werte und Rechte prüfen -- E-Mails kommen nicht an - - `EMAIL_USE_SMTP=true` und SMTP-Daten prüfen -- Avatare fehlen - - Schreibrechte für `public/avatars/` +**Features:** +- Automatische Sortierung aller Spalten +- Suchfunktion in Echtzeit +- Paginierung mit anpassbarer Seitenlänge +- Responsive Darstellung auf mobilen Geräten +- Mehrsprachige Unterstützung (Deutsch implementiert) --- -## Option: Vagrant „One-Click-Installation“ - -- Ordner: `_one-click-installation/` -- Voraussetzungen: Vagrant + VirtualBox - -```bash -cd _one-click-installation -vagrant up -``` - -- Projekt über die VM nutzen (Host/Ports laut Vagrant-Ausgabe) - ---- - -## Struktur (Kurz) - -- `public/` (Webroot, `.htaccess`, `index.php`) -- `application/controller/` (Controller) -- `application/model/` (Modelle) -- `application/view/` (Views) -- `application/config/` (Konfiguration je Environment) -- `application/_installation/` (SQL-Skripte) -- `vendor/` (Composer-Abhängigkeiten) -- `tests/` (PHPUnit) +
+ Gadze +
\ No newline at end of file diff --git a/application/_installation/03-create-table-notes.sql b/application/_installation/03-create-table-notes.sql index 38d0368..c0b01f7 100644 --- a/application/_installation/03-create-table-notes.sql +++ b/application/_installation/03-create-table-notes.sql @@ -2,5 +2,10 @@ CREATE TABLE IF NOT EXISTS `huge`.`notes` ( `note_id` int(11) unsigned NOT NULL AUTO_INCREMENT, `note_text` text NOT NULL, `user_id` int(11) unsigned NOT NULL, + `note_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`note_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='user notes'; + +-- Foreign key constraint +ALTER TABLE `notes` + ADD CONSTRAINT `notes_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE; diff --git a/application/_installation/04-create-table-messages.sql b/application/_installation/04-create-table-messages.sql index 74c3129..01802e4 100644 --- a/application/_installation/04-create-table-messages.sql +++ b/application/_installation/04-create-table-messages.sql @@ -2,7 +2,7 @@ CREATE TABLE IF NOT EXISTS `messages` ( `id` int(11) NOT NULL AUTO_INCREMENT, `sender_id` int(11) NOT NULL, `receiver_id` int(11) DEFAULT NULL, - `group_type` enum('admins','moderators','all_users') DEFAULT NULL, + `group_type` enum('admins','moderators','all_users','global') DEFAULT NULL, `subject` varchar(255) NOT NULL, `message` text NOT NULL, `is_read` tinyint(1) NOT NULL DEFAULT 0, @@ -11,7 +11,8 @@ CREATE TABLE IF NOT EXISTS `messages` ( PRIMARY KEY (`id`), KEY `sender_id` (`sender_id`), KEY `receiver_id` (`receiver_id`), - KEY `is_read` (`is_read`) + KEY `is_read` (`is_read`), + KEY `group_type` (`group_type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -- Foreign key constraints diff --git a/application/config/config.development.php b/application/config/config.development.php index fd599b1..4fb05ea 100644 --- a/application/config/config.development.php +++ b/application/config/config.development.php @@ -63,7 +63,7 @@ return array( * DB_CHARSET The charset, necessary for security reasons. Check Database.php class for more info. */ 'DB_TYPE' => 'mysql', - 'DB_HOST' => '127.0.0.1', + 'DB_HOST' => 'localhost', 'DB_NAME' => 'huge', 'DB_USER' => 'root', 'DB_PASS' => 'root', diff --git a/application/controller/MessageController.php b/application/controller/MessageController.php index 4762a42..b51b586 100644 --- a/application/controller/MessageController.php +++ b/application/controller/MessageController.php @@ -10,6 +10,15 @@ class MessageController extends Controller Auth::checkAuthentication(); } + /** + * Check if the request is an AJAX request + */ + private function isAjaxRequest() + { + return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && + strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'; + } + /** * Send a message to a specific user via URL parameters * URL format: message/send/{receiver_id}/{subject}/{message} @@ -23,6 +32,13 @@ class MessageController extends Controller $message = isset($_POST['message']) ? $_POST['message'] : null; if (!$receiver_id || !$message) { + // Return JSON for AJAX requests + if ($this->isAjaxRequest()) { + header('Content-Type: application/json'); + echo json_encode(['success' => false, 'message' => 'Receiver and message are required']); + return; + } + Session::add('feedback_negative', 'Receiver and message are required'); Redirect::to('message'); return; @@ -32,6 +48,18 @@ class MessageController extends Controller $sender_id = Session::get('user_id'); $success = MessageModel::sendToUser($sender_id, $receiver_id, $subject, $message); + // Return JSON for AJAX requests + if ($this->isAjaxRequest()) { + header('Content-Type: application/json'); + if ($success) { + echo json_encode(['success' => true, 'message' => 'Message sent successfully']); + } else { + echo json_encode(['success' => false, 'message' => 'Failed to send message']); + } + return; + } + + // Regular request handling if ($success) { Session::add('feedback_positive', 'Message sent successfully'); } else { @@ -150,6 +178,64 @@ class MessageController extends Controller } } + /** + * Handle reply to a message + */ + public function reply() + { + // Always return JSON for this endpoint + header('Content-Type: application/json'); + + // Start output buffering to catch any accidental output + ob_start(); + + try { + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + echo json_encode(['success' => false, 'message' => 'Invalid request method']); + exit(); + } + + $receiver_id = isset($_POST['receiver_id']) ? $_POST['receiver_id'] : null; + $message = isset($_POST['message']) ? $_POST['message'] : null; + + if (!$receiver_id || !$message) { + echo json_encode(['success' => false, 'message' => 'Receiver and message are required']); + exit(); + } + + $sender_id = Session::get('user_id'); + if (!$sender_id) { + echo json_encode(['success' => false, 'message' => 'Not logged in']); + exit(); + } + + // Send the message (using sendToUser without subject) + $success = MessageModel::sendToUser($sender_id, $receiver_id, 'Re: Message', $message); + + if ($success) { + echo json_encode(['success' => true, 'message' => 'Reply sent successfully']); + } else { + echo json_encode(['success' => false, 'message' => 'Failed to send reply']); + } + } catch (Exception $e) { + // Catch any PHP errors + echo json_encode(['success' => false, 'message' => 'Server error: ' . $e->getMessage()]); + } + + // Clean any output buffer and exit + ob_end_clean(); + exit(); + } + + /** + * Show global chat interface + */ + public function global() + { + // Redirect to main messages page with global chat hash + Redirect::to('message#load-global'); + } + /** * Show the messenger interface */ @@ -189,13 +275,95 @@ class MessageController extends Controller return; } + // Redirect to main messages page with conversation hash + Redirect::to('message#load-conversation-' . $other_user_id); + } + + /** + * Get conversation messages as JSON (AJAX endpoint) + */ + public function getConversationMessages() + { + $user_id = Session::get('user_id'); + $url_parts = explode('/', trim($_SERVER['REQUEST_URI'], '/')); + $other_user_id = isset($url_parts[2]) ? $url_parts[2] : null; + + if (!$other_user_id) { + header('Content-Type: application/json'); + echo json_encode(['success' => false, 'message' => 'Missing user ID']); + return; + } + // Get messages $messages = MessageModel::getMessagesWithUser($user_id, $other_user_id); - $this->View->render('message/conversation', array( - 'messages' => $messages, - 'other_user' => $other_user - )); + // Mark messages as read when loading the conversation + MessageModel::markAsRead($user_id, $other_user_id); + + header('Content-Type: application/json'); + echo json_encode(['success' => true, 'messages' => $messages]); + } + + /** + * Get global chat messages as JSON (AJAX endpoint) + */ + public function getGlobalMessages() + { + // Always return JSON for this endpoint + header('Content-Type: application/json'); + + $messages = MessageModel::getGlobalMessages(); + echo json_encode(['success' => true, 'messages' => $messages]); + + // Stop any further execution + exit(); + } + + /** + * Send message to global chat + */ + public function sendToGlobal() + { + // Always return JSON for this endpoint + header('Content-Type: application/json'); + + // Start output buffering to catch any accidental output + ob_start(); + + try { + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + echo json_encode(['success' => false, 'message' => 'Invalid request method']); + exit(); + } + + $message = isset($_POST['message']) ? $_POST['message'] : null; + $sender_id = Session::get('user_id'); + + if (!$message) { + echo json_encode(['success' => false, 'message' => 'Message is required']); + exit(); + } + + if (!$sender_id) { + echo json_encode(['success' => false, 'message' => 'Not logged in']); + exit(); + } + + $success = MessageModel::sendToGlobal($sender_id, $message); + + if ($success) { + echo json_encode(['success' => true, 'message' => 'Message sent to global chat']); + } else { + echo json_encode(['success' => false, 'message' => 'Failed to send message']); + } + } catch (Exception $e) { + // Catch any PHP errors + echo json_encode(['success' => false, 'message' => 'Server error: ' . $e->getMessage()]); + } + + // Clean any output buffer and exit + ob_end_clean(); + exit(); } /** diff --git a/application/controller/NoteController.php b/application/controller/NoteController.php index f44ee7e..46f381d 100644 --- a/application/controller/NoteController.php +++ b/application/controller/NoteController.php @@ -3,6 +3,8 @@ /** * The note controller: Just an example of simple create, read, update and delete (CRUD) actions. */ +require_once __DIR__ . '/../libs/SimpleMarkdown.php'; + class NoteController extends Controller { /** @@ -29,6 +31,23 @@ class NoteController extends Controller )); } + /** + * Get note as JSON (AJAX endpoint) + */ + public function getNote($note_id) + { + $note = NoteModel::getNote($note_id); + + header('Content-Type: application/json'); + if ($note) { + // Add markdown version + $note->note_html = SimpleMarkdown::parse($note->note_text); + echo json_encode(['success' => true, 'note' => $note]); + } else { + echo json_encode(['success' => false, 'message' => 'Note not found']); + } + } + /** * This method controls what happens when you move to /dashboard/create in your app. * Creates a new note. This is usually the target of form submit actions. @@ -36,7 +55,18 @@ class NoteController extends Controller */ public function create() { - NoteModel::createNote(Request::post('note_text')); + $success = NoteModel::createNote(Request::post('note_text')); + + if ($this->isAjaxRequest()) { + header('Content-Type: application/json'); + if ($success) { + echo json_encode(['success' => true, 'message' => 'Note created successfully']); + } else { + echo json_encode(['success' => false, 'message' => 'Failed to create note']); + } + return; + } + Redirect::to('note'); } @@ -59,7 +89,18 @@ class NoteController extends Controller */ public function editSave() { - NoteModel::updateNote(Request::post('note_id'), Request::post('note_text')); + $success = NoteModel::updateNote(Request::post('note_id'), Request::post('note_text')); + + if ($this->isAjaxRequest()) { + header('Content-Type: application/json'); + if ($success) { + echo json_encode(['success' => true, 'message' => 'Note updated successfully']); + } else { + echo json_encode(['success' => false, 'message' => 'Failed to update note']); + } + return; + } + Redirect::to('note'); } @@ -71,7 +112,27 @@ class NoteController extends Controller */ public function delete($note_id) { - NoteModel::deleteNote($note_id); + $success = NoteModel::deleteNote($note_id); + + if ($this->isAjaxRequest()) { + header('Content-Type: application/json'); + if ($success) { + echo json_encode(['success' => true, 'message' => 'Note deleted successfully']); + } else { + echo json_encode(['success' => false, 'message' => 'Failed to delete note']); + } + return; + } + Redirect::to('note'); } + + /** + * Check if the request is an AJAX request + */ + private function isAjaxRequest() + { + return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && + strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'; + } } diff --git a/application/core/View.php b/application/core/View.php index 035fdf3..962466b 100644 --- a/application/core/View.php +++ b/application/core/View.php @@ -17,6 +17,15 @@ class View public $avatar_file_path; public $user_account_type; + /* Messenger properties */ + public $conversations; + public $unread_count; + public $all_users; + + /* Note properties */ + public $messages; + public $other_user; + /** * Static property to track if header has been rendered */ diff --git a/application/libs/SimpleMarkdown.php b/application/libs/SimpleMarkdown.php new file mode 100644 index 0000000..28d5d79 --- /dev/null +++ b/application/libs/SimpleMarkdown.php @@ -0,0 +1,102 @@ + quotes + */ + +class SimpleMarkdown +{ + /** + * Parse markdown text to HTML + */ + public static function parse($text) + { + // Convert special characters to HTML entities first + $text = htmlspecialchars($text, ENT_QUOTES, 'UTF-8'); + + // Headers (h1-h6) + $text = preg_replace('/^#{1,6}\s+(.+)$/m', '

$1

', $text); + + // Bold text + $text = preg_replace('/\*\*(.+?)\*\*/', '$1', $text); + + // Italic text + $text = preg_replace('/\*(.+?)\*/', '$1', $text); + + // Inline code + $text = preg_replace('/`(.+?)`/', '$1', $text); + + // Code blocks + $text = preg_replace('/```(.+?)```/s', '
$1
', $text); + + // Links + $text = preg_replace('/\[(.+?)\]\((.+?)\)/', '$1', $text); + + // Blockquotes + $text = preg_replace('/^>\s+(.+)$/m', '
$1
', $text); + + // Unordered lists + $text = preg_replace('/^\-\s+(.+)$/m', '
  • $1
  • ', $text); + $text = preg_replace('/(
  • .*<\/li>)/s', '
      $1
    ', $text); + + // Line breaks + $text = nl2br($text); + + // Clean up multiple consecutive line breaks + $text = preg_replace('/(\s*){3,}/', '

    ', $text); + + return $text; + } + + /** + * Convert HTML back to markdown (simplified) + * This is a basic implementation - may not handle all cases perfectly + */ + public static function toMarkdown($html) + { + // Basic HTML to markdown conversion + $markdown = $html; + + // Headers + $markdown = preg_replace('/(.+?)<\/h[1-6]>/i', '# $1', $markdown); + + // Bold + $markdown = preg_replace('/(.+?)<\/strong>/i', '**$1**', $markdown); + + // Italic + $markdown = preg_replace('/(.+?)<\/em>/i', '*$1*', $markdown); + + // Code + $markdown = preg_replace('/(.+?)<\/code>/i', '`$1`', $markdown); + $markdown = preg_replace('/
    (.+?)<\/code><\/pre>/is', "```$1```", $markdown);
    +        
    +        // Links
    +        $markdown = preg_replace('/(.+?)<\/a>/i', '[$2]($1)', $markdown);
    +        
    +        // Blockquotes
    +        $markdown = preg_replace('/
    (.+?)<\/blockquote>/i', '> $1', $markdown); + + // Lists (simplified) + $markdown = preg_replace('/
      (.+?)<\/ul>/is', '$1', $markdown); + $markdown = preg_replace('/
    • (.+?)<\/li>/i', '- $1', $markdown); + + // Line breaks + $markdown = preg_replace('//i', "\n", $markdown); + + // Decode HTML entities + $markdown = htmlspecialchars_decode($markdown, ENT_QUOTES); + + // Clean up + $markdown = preg_replace('/\n{3,}/', "\n\n", $markdown); + $markdown = trim($markdown); + + return $markdown; + } +} +?> \ No newline at end of file diff --git a/application/model/MessageModel.php b/application/model/MessageModel.php index 6ecef85..3492ce4 100644 --- a/application/model/MessageModel.php +++ b/application/model/MessageModel.php @@ -6,8 +6,8 @@ class MessageModel { $database = DatabaseFactory::getFactory()->getConnection(); - $sql = "INSERT INTO messages (sender_id, receiver_id, group_type, subject, message) - VALUES (:sender_id, :receiver_id, :group_type, :subject, :message)"; + $sql = "INSERT INTO messages (sender_id, receiver_id, group_type, subject, message, is_read) + VALUES (:sender_id, :receiver_id, :group_type, :subject, :message, 0)"; $query = $database->prepare($sql); return $query->execute(array( ':sender_id' => $sender_id, @@ -170,6 +170,36 @@ class MessageModel return $query->fetchAll(); } + public static function getGlobalMessages() + { + $database = DatabaseFactory::getFactory()->getConnection(); + + $sql = "SELECT m.*, u.user_name as sender_name, + 'received' as message_type + FROM messages m + JOIN users u ON m.sender_id = u.user_id + WHERE m.group_type = 'global' + AND m.receiver_id IS NULL + ORDER BY m.created_at ASC"; + + $query = $database->prepare($sql); + $query->execute(); + return $query->fetchAll(); + } + + public static function sendToGlobal($sender_id, $message) + { + $database = DatabaseFactory::getFactory()->getConnection(); + + $sql = "INSERT INTO messages (sender_id, group_type, subject, message, is_read) + VALUES (:sender_id, 'global', 'Global Chat', :message, 1)"; + $query = $database->prepare($sql); + return $query->execute(array( + ':sender_id' => $sender_id, + ':message' => $message + )); + } + public static function getAllUsers($current_user_id) { $database = DatabaseFactory::getFactory()->getConnection(); diff --git a/application/model/NoteModel.php b/application/model/NoteModel.php index d67ba9f..704e96e 100644 --- a/application/model/NoteModel.php +++ b/application/model/NoteModel.php @@ -14,12 +14,20 @@ class NoteModel { $database = DatabaseFactory::getFactory()->getConnection(); - $sql = "SELECT user_id, note_id, note_text FROM notes WHERE user_id = :user_id"; + $sql = "SELECT note_id, note_text, note_timestamp FROM notes WHERE user_id = :user_id ORDER BY note_timestamp DESC"; $query = $database->prepare($sql); $query->execute(array(':user_id' => Session::get('user_id'))); // fetchAll() is the PDO method that gets all result rows - return $query->fetchAll(); + $notes = $query->fetchAll(); + + // Add markdown HTML to each note + require_once __DIR__ . '/../libs/SimpleMarkdown.php'; + foreach ($notes as &$note) { + $note->note_html = SimpleMarkdown::parse($note->note_text); + } + + return $notes; } /** @@ -29,13 +37,21 @@ class NoteModel */ public static function getNote($note_id) { - $database = DatabaseFactory::getFactory()->getConnectionWithMySQLI(); + $database = DatabaseFactory::getFactory()->getConnection(); - $sql = "SELECT user_id, note_id, note_text FROM notes WHERE user_id = :user_id AND note_id = :note_id LIMIT 1"; + $sql = "SELECT note_id, note_text, note_timestamp FROM notes WHERE user_id = :user_id AND note_id = :note_id LIMIT 1"; $query = $database->prepare($sql); $query->execute(array(':user_id' => Session::get('user_id'), ':note_id' => $note_id)); - return $query; + $result = $query->fetch(); + + // Add markdown HTML + if ($result) { + require_once __DIR__ . '/../libs/SimpleMarkdown.php'; + $result->note_html = SimpleMarkdown::parse($result->note_text); + } + + return $result ? $result : null; } /** diff --git a/application/view/message/conversation.php b/application/view/message/conversation.php index 7062df0..ebb1991 100644 --- a/application/view/message/conversation.php +++ b/application/view/message/conversation.php @@ -1,115 +1,17 @@ -
      - +render('_templates/header'); ?> -
      - -
      -
      -
      - Conversation with other_user->user_name) ?> -
      -
      - messages)): ?> -
      - No messages yet. Start a conversation! -
      - - messages as $msg): ?> -
      -
      - message) ?> -
      -
      - created_at)) ?> -
      -
      - - -
      - - - -
      +
      + renderFeedbackMessages(); ?> + + + +
      +

      Redirecting to Conversation...

      +

      If you're not redirected automatically, click here.

      -
      - - - - -render('_templates/footer'); ?> +render('_templates/footer'); ?> \ No newline at end of file diff --git a/application/view/message/global.php b/application/view/message/global.php new file mode 100644 index 0000000..d867812 --- /dev/null +++ b/application/view/message/global.php @@ -0,0 +1,17 @@ +render('_templates/header'); ?> + +
      + renderFeedbackMessages(); ?> + + + +
      +

      Redirecting to Global Chat...

      +

      If you're not redirected automatically, click here.

      +
      +
      + +render('_templates/footer'); ?> \ No newline at end of file diff --git a/application/view/message/index.php b/application/view/message/index.php index 54779f2..7a072f5 100644 --- a/application/view/message/index.php +++ b/application/view/message/index.php @@ -1,175 +1,701 @@ -
      -

      Messenger

      +
      +

      Messenger

      + renderFeedbackMessages(); ?> -
      - -
      -
      -
      - Conversations - unread_count > 0): ?> - unread_count ?> - -
      -
      -
      - conversations)): ?> -
      - No conversations yet -
      - - conversations as $conv): ?> - -
      - user_name) ?> - unread_count > 0): ?> - unread_count ?> - -
      -

      - last_message_time)) ?> -

      -
      - - -
      -
      -
      + + + + - -
      -
      New Message
      -
      -
      -
      - - +
      + +
      +
      +

      Channels

      +
      -
      - - -
      -
      - - -
      - - -
      -
      - -
      -
      Send to Group
      -
      -
      -
      - - +
      +

      Direct Messages

      + conversations)): ?> +

      No conversations yet

      + + conversations as $conv): ?> +
      +
      +
      + @ + user_name) ?> +
      + unread_count > 0): ?> + + unread_count ?> + + +
      + last_message_time)) ?> +
      + +
      -
      - - + +
      +

      New Message

      + + + + + + +
      +
      + + +
      +
      + +
      +
      +

      Welcome to Messenger

      +

      Select Global Chat or a conversation to start messaging

      + Click on any channel or conversation in the sidebar +
      +
      + + +
      -
      - - -
      - -
      -
      - - -
      -
      -
      - Chat Area - Select a conversation to start messaging -
      -
      - Select a conversation from the left to view messages -
      -
      -
      -
      + + render('_templates/footer'); ?> diff --git a/application/view/note/index.php b/application/view/note/index.php index ce908a6..c7d1971 100644 --- a/application/view/note/index.php +++ b/application/view/note/index.php @@ -1,44 +1,459 @@ -
      -

      NoteController/index

      -
      +
      +

      My Notes

      + renderFeedbackMessages(); ?> - - renderFeedbackMessages(); ?> +
      + +
      +
      +
      + + +
      +
      -

      What happens here ?

      -

      - This is just a simple CRUD implementation. Creating, reading, updating and deleting things. -

      -

      -

      - - -
      -

      +
      +

      Your Notes

      + notes): ?> + notes as $note): ?> +
      +
      + note_text, 0, 50)) ?>note_text) > 50 ? '...' : '' ?> +
      +
      + note_timestamp)) ?> +
      +
      + note_text), 0, 80)) ?>note_text)) > 80 ? '...' : '' ?> +
      +
      + Edit + Delete +
      +
      + + +

      No notes yet. Create your first note!

      + +
      +
      - notes) { ?> - - - - - - - - - - - notes as $key => $value) { ?> - - - - - - - - -
      IdNoteEDITDELETE
      note_id; ?>note_text); ?>EditDelete
      - -
      No notes yet. Create some !
      - + +
      +
      +
      +

      Note Viewer

      +

      Select a note from the left to view its contents

      + Click anywhere on a note card to view it +
      +
      + + + +
      + + diff --git a/assets/footer/gray0_ctp_on_line.svg b/assets/footer/gray0_ctp_on_line.svg new file mode 100644 index 0000000..daf8384 --- /dev/null +++ b/assets/footer/gray0_ctp_on_line.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/screenshots/notes_1.png b/assets/screenshots/notes_1.png new file mode 100644 index 0000000000000000000000000000000000000000..01adaf7d68148c1bc086dd6a560c0691ea9bcb2d GIT binary patch literal 30611 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 + + // Check if is_read column exists + $sql = "SHOW COLUMNS FROM messages LIKE 'is_read'"; + $query = $database->prepare($sql); + $query->execute(); + $result = $query->fetch(); + + if (!$result) { + // Add is_read column + $sql = "ALTER TABLE messages ADD COLUMN is_read TINYINT(1) NOT NULL DEFAULT 0"; + $query = $database->prepare($sql); + $query->execute(); + + echo "Successfully added is_read column to messages table\n"; + } else { + echo "is_read column already exists in messages table\n"; + } + +} catch (Exception $e) { + echo "Error: " . $e->getMessage() . "\n"; +} +?> \ No newline at end of file diff --git a/public/css/style.css b/public/css/style.css index 8f29ff5..82c8b4a 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -85,9 +85,12 @@ body { } /* TODO */ .navigation { + } .navigation.right { float: right; + position: relative; + z-index: 99; } .navigation li { float: left; diff --git a/public/js/messaging.js b/public/js/messaging.js new file mode 100644 index 0000000..e269a31 --- /dev/null +++ b/public/js/messaging.js @@ -0,0 +1,293 @@ +// Messaging JavaScript functionality +class MessagingSystem { + constructor() { + this.currentUserId = null; + this.currentConversationUserId = null; + this.pollingInterval = null; + this.init(); + } + + init() { + // Get current user ID from the page + this.currentUserId = document.querySelector('meta[name="user-id"]')?.content; + + if (this.currentUserId) { + this.setupEventListeners(); + this.startPolling(); + } + } + + setupEventListeners() { + // Handle message form submissions + document.querySelectorAll('form[id$="-form"], form[action*="message/send"]').forEach(form => { + form.addEventListener('submit', (e) => { + e.preventDefault(); + this.sendMessage(form); + }); + }); + + // Handle conversation switching + document.querySelectorAll('a[href*="message/conversation/"]').forEach(link => { + link.addEventListener('click', (e) => { + e.preventDefault(); + const userId = link.href.split('/').pop(); + this.loadConversation(userId); + }); + }); + } + + async sendMessage(form) { + try { + const formData = new FormData(form); + const data = Object.fromEntries(formData.entries()); + + // Show loading state + const submitButton = form.querySelector('button[type="submit"]'); + const originalText = submitButton.textContent; + submitButton.textContent = 'Sending...'; + submitButton.disabled = true; + + const response = await fetch('index.php/message/send', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'X-Requested-With': 'XMLHttpRequest' + }, + body: new URLSearchParams(data) + }); + + const result = await response.json(); + + if (result.success) { + // Clear form + form.reset(); + + // Reload messages or show success + if (this.currentConversationUserId) { + await this.loadMessages(this.currentConversationUserId); + } else { + this.showFeedback('Message sent successfully!', 'success'); + } + } else { + this.showFeedback(result.message || 'Failed to send message', 'error'); + } + } catch (error) { + console.error('Error sending message:', error); + this.showFeedback('Network error. Please try again.', 'error'); + } finally { + // Restore button state + const submitButton = form.querySelector('button[type="submit"]'); + submitButton.textContent = originalText; + submitButton.disabled = false; + } + } + + async loadConversation(userId) { + this.currentConversationUserId = userId; + + try { + const response = await fetch(`index.php/message/conversation/${userId}`, { + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }); + + if (response.ok) { + const html = await response.text(); + const conversationContainer = document.querySelector('.conversation-container') || + document.querySelector('.panel-default'); + + if (conversationContainer) { + conversationContainer.innerHTML = html; + this.setupEventListeners(); // Re-setup event listeners for new content + } + } + } catch (error) { + console.error('Error loading conversation:', error); + this.showFeedback('Failed to load conversation', 'error'); + } + } + + async loadMessages(userId) { + try { + const response = await fetch(`index.php/message/getConversationMessages/${userId}`, { + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }); + + if (response.ok) { + const messages = await response.json(); + this.displayMessages(messages); + } + } catch (error) { + console.error('Error loading messages:', error); + } + } + + async loadGlobalMessages() { + try { + const response = await fetch('index.php/message/getGlobalMessages', { + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }); + + if (response.ok) { + const messages = await response.json(); + this.displayGlobalMessages(messages); + } + } catch (error) { + console.error('Error loading global messages:', error); + } + } + + displayMessages(messages) { + const messageContainer = document.querySelector('.message-container'); + if (!messageContainer) return; + + messageContainer.innerHTML = ''; + + messages.forEach(message => { + const messageDiv = document.createElement('div'); + messageDiv.className = `message ${message.sender_id == this.currentUserId ? 'sent' : 'received'}`; + + messageDiv.innerHTML = ` +
      + ${this.escapeHtml(message.message)} +
      +
      + ${this.formatDate(message.created_at)} +
      + `; + + messageContainer.appendChild(messageDiv); + }); + + // Scroll to bottom + messageContainer.scrollTop = messageContainer.scrollHeight; + } + + displayGlobalMessages(messages) { + const globalContainer = document.querySelector('.global-messages-container'); + if (!globalContainer) return; + + globalContainer.innerHTML = ''; + + messages.forEach(message => { + const messageDiv = document.createElement('div'); + messageDiv.className = 'global-message'; + + messageDiv.innerHTML = ` +
      + ${this.escapeHtml(message.sender_name)} + ${this.formatDate(message.created_at)} +
      +
      + ${this.escapeHtml(message.message)} +
      + `; + + globalContainer.appendChild(messageDiv); + }); + + // Scroll to bottom + globalContainer.scrollTop = globalContainer.scrollHeight; + } + + startPolling() { + // Poll for new messages every 5 seconds + this.pollingInterval = setInterval(() => { + this.checkForNewMessages(); + }, 5000); + + // Also check for unread count + setInterval(() => { + this.updateUnreadCount(); + }, 10000); + } + + async checkForNewMessages() { + if (this.currentConversationUserId) { + await this.loadMessages(this.currentConversationUserId); + } + } + + async updateUnreadCount() { + try { + const response = await fetch('index.php/message/unreadcount', { + headers: { + 'X-Requested-With': 'XMLHttpRequest' + } + }); + + if (response.ok) { + const data = await response.json(); + this.updateUnreadCountDisplay(data.count); + } + } catch (error) { + console.error('Error updating unread count:', error); + } + } + + updateUnreadCountDisplay(count) { + const unreadElements = document.querySelectorAll('.unread-count'); + unreadElements.forEach(element => { + if (count > 0) { + element.textContent = count; + element.style.display = 'inline-block'; + } else { + element.style.display = 'none'; + } + }); + } + + showFeedback(message, type) { + // Create feedback element + const feedback = document.createElement('div'); + feedback.className = `feedback-${type}`; + feedback.textContent = message; + feedback.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + padding: 15px 20px; + background: ${type === 'success' ? '#4caf50' : '#f44336'}; + color: white; + border-radius: 4px; + z-index: 1000; + box-shadow: 0 2px 10px rgba(0,0,0,0.2); + `; + + document.body.appendChild(feedback); + + // Remove after 3 seconds + setTimeout(() => { + feedback.remove(); + }, 3000); + } + + escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + + formatDate(dateString) { + const date = new Date(dateString); + return date.toLocaleString('en-US', { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); + } +} + +// Initialize the messaging system when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + window.messagingSystem = new MessagingSystem(); +}); + +// Make it available globally for any other scripts that might need it +window.MessagingSystem = MessagingSystem; \ No newline at end of file diff --git a/test_messaging.php b/test_messaging.php new file mode 100644 index 0000000..2ade073 --- /dev/null +++ b/test_messaging.php @@ -0,0 +1,118 @@ + + + + Test Messaging Endpoints + + +

      Test Messaging Endpoints

      + +

      Test 1: Get Global Messages

      + +
      + +

      Test 2: Get Conversation Messages

      + +
      + +

      Test 3: Send Global Message

      + +
      + +

      Test 4: Send Conversation Message

      + +
      + + + + \ No newline at end of file