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', '', $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 0000000..01adaf7 Binary files /dev/null and b/assets/screenshots/notes_1.png differ diff --git a/assets/screenshots/notes_2.png b/assets/screenshots/notes_2.png new file mode 100644 index 0000000..b35fe0f Binary files /dev/null and b/assets/screenshots/notes_2.png differ diff --git a/migration_add_is_read.php b/migration_add_is_read.php new file mode 100644 index 0000000..cc571a1 --- /dev/null +++ b/migration_add_is_read.php @@ -0,0 +1,33 @@ +getConnection(); + + // 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