Compare commits
2 Commits
674fabb715
...
01861f08c6
| Author | SHA1 | Date | |
|---|---|---|---|
| 01861f08c6 | |||
| a4d386f2c5 |
77
README.md
77
README.md
@@ -4,26 +4,47 @@
|
|||||||
|
|
||||||
## Anpassung der Useranmeldung
|
## Anpassung der Useranmeldung
|
||||||
|
|
||||||
#### Registrierung ohne Captcha und E-Mail-Verifikation
|
#### Registrierung mit Google reCAPTCHA v3
|
||||||
|
|
||||||
**Beschreibung:**
|
**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.
|
Die Registrierung wurde mit Google reCAPTCHA v3 abgesichert. reCAPTCHA v3 arbeitet unsichtbar im Hintergrund und analysiert das Benutzerverhalten, um Bots zu erkennen. E-Mail-Verifizierung ist deaktiviert - Benutzer werden nach der Registrierung automatisch aktiviert.
|
||||||
|
|
||||||
**Technische Umsetzung:**
|
**Technische Umsetzung:**
|
||||||
- Entfernung des Captcha-Features aus dem Registrierungsprozess
|
- Integration von Google reCAPTCHA v3 (unsichtbar, keine Checkbox)
|
||||||
- Deaktivierung der E-Mail-Verifizierung
|
- Score-basierte Validierung (0.0 = Bot, 1.0 = Mensch)
|
||||||
- Automatische Aktivierung neuer Benutzerkonten
|
- Blockierung bei Score unter 0.5
|
||||||
- Vereinfachung des Registrierungsformulars
|
- Konfiguration über `RECAPTCHA_SITE_KEY` und `RECAPTCHA_SECRET_KEY`
|
||||||
|
|
||||||
**Admin-Funktion:**
|
**Code-Beispiel (View):**
|
||||||
- Nur Administratoren können über das gleiche Formular neue Benutzer anlegen
|
```html
|
||||||
- Sicherstellung, dass nur autorisierte Personen Benutzerkonten erstellen können
|
<script src="https://www.google.com/recaptcha/api.js?render=SITE_KEY"></script>
|
||||||
|
<script>
|
||||||
|
grecaptcha.ready(function() {
|
||||||
|
grecaptcha.execute('SITE_KEY', {action: 'register'}).then(function(token) {
|
||||||
|
document.getElementById('recaptcha-response').value = token;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Code-Beispiel (Server-Validierung):**
|
||||||
|
```php
|
||||||
|
$response = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret=' . $secret_key . '&response=' . $recaptcha_response);
|
||||||
|
$response_data = json_decode($response);
|
||||||
|
if (!$response_data->success || $response_data->score < 0.5) {
|
||||||
|
// Bot erkannt oder Validierung fehlgeschlagen
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Vorteile von v3:**
|
||||||
|
- Keine Benutzerinteraktion erforderlich
|
||||||
|
- Bessere User Experience
|
||||||
|
- Intelligente Bot-Erkennung durch Verhaltensanalyse
|
||||||
|
|
||||||
```
|
```
|
||||||
<!-- Screenshot Platzhalter -->
|
<!-- Screenshot Platzhalter -->
|
||||||
[📸 Screenshot: Vereinfachtes Registrierungsformular]
|
[📸 Screenshot: Registrierungsformular]
|
||||||
[📸 Screenshot: Admin-Benutzererstellung]
|
[📸 Screenshot: reCAPTCHA Badge]
|
||||||
[📸 Screenshot: Automatisch aktivierter Benutzer]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -70,6 +91,38 @@ Implementierung einer öffentlichen Benutzerliste, die alle Benutzer und ihre Gr
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Notizen-App
|
||||||
|
|
||||||
|
#### Persönliche Notizen mit Markdown-Unterstützung
|
||||||
|
|
||||||
|
**Beschreibung:**
|
||||||
|
Implementierung einer vollständigen Notizen-Anwendung mit CRUD-Funktionalität (Create, Read, Update, Delete). Benutzer können persönliche Notizen erstellen, bearbeiten und löschen.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- **Markdown-Unterstützung**: Notizen werden mit SimpleMarkdown gerendert
|
||||||
|
- **AJAX-Integration**: Alle Operationen ohne Seiten-Neuladung
|
||||||
|
- **Benutzergebunden**: Jeder Benutzer sieht nur seine eigenen Notizen
|
||||||
|
- **Echtzeit-Vorschau**: Markdown wird direkt in HTML umgewandelt
|
||||||
|
|
||||||
|
**Technische Umsetzung:**
|
||||||
|
- NoteController mit vollständiger CRUD-Implementierung
|
||||||
|
- NoteModel für Datenbankoperationen
|
||||||
|
- SimpleMarkdown-Library für Markdown-Parsing
|
||||||
|
- AJAX-Endpoints für dynamische Interaktion
|
||||||
|
|
||||||
|
**Zugriffsrechte:**
|
||||||
|
- Nur für angemeldete Benutzer verfügbar
|
||||||
|
- Jeder Benutzer hat nur Zugriff auf seine eigenen Notizen
|
||||||
|
|
||||||
|
```
|
||||||
|
<!-- Screenshot Platzhalter -->
|
||||||
|
[📸 Screenshot: Notizen-Übersicht]
|
||||||
|
[📸 Screenshot: Notiz erstellen/bearbeiten]
|
||||||
|
[📸 Screenshot: Markdown-Vorschau]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## jQuery - Einführung und Grundlagen
|
## jQuery - Einführung und Grundlagen
|
||||||
|
|
||||||
#### JavaScript Basics
|
#### JavaScript Basics
|
||||||
|
|||||||
@@ -63,19 +63,17 @@ return array(
|
|||||||
* DB_CHARSET The charset, necessary for security reasons. Check Database.php class for more info.
|
* DB_CHARSET The charset, necessary for security reasons. Check Database.php class for more info.
|
||||||
*/
|
*/
|
||||||
'DB_TYPE' => 'mysql',
|
'DB_TYPE' => 'mysql',
|
||||||
'DB_HOST' => 'localhost',
|
'DB_HOST' => '127.0.0.1',
|
||||||
'DB_NAME' => 'huge',
|
'DB_NAME' => 'huge',
|
||||||
'DB_USER' => 'root',
|
'DB_USER' => 'root',
|
||||||
'DB_PASS' => 'root',
|
'DB_PASS' => 'root',
|
||||||
'DB_PORT' => '3306',
|
'DB_PORT' => '3306',
|
||||||
'DB_CHARSET' => 'utf8',
|
'DB_CHARSET' => 'utf8',
|
||||||
/**
|
/**
|
||||||
* Configuration for: Captcha size
|
* Configuration for: Google reCAPTCHA v2
|
||||||
* The currently used Captcha generator (https://github.com/Gregwar/Captcha) also runs without giving a size,
|
|
||||||
* so feel free to use ->build(); inside CaptchaModel.
|
|
||||||
*/
|
*/
|
||||||
'CAPTCHA_WIDTH' => 359,
|
'RECAPTCHA_SITE_KEY' => 'recaptcha-site-key',
|
||||||
'CAPTCHA_HEIGHT' => 100,
|
'RECAPTCHA_SECRET_KEY' => 'recaptcha-secret-key',
|
||||||
/**
|
/**
|
||||||
* Configuration for: Cookies
|
* Configuration for: Cookies
|
||||||
* 1209600 seconds = 2 weeks
|
* 1209600 seconds = 2 weeks
|
||||||
|
|||||||
144
application/controller/DatabaseController.php
Normal file
144
application/controller/DatabaseController.php
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class DatabaseController
|
||||||
|
*
|
||||||
|
* Controller for managing databases and showing their structure
|
||||||
|
*/
|
||||||
|
class DatabaseController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Construct this object by extending the basic Controller class
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
// Only logged-in users can access the database manager
|
||||||
|
Auth::checkAuthentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main database management interface
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$this->View->render('database/index', array(
|
||||||
|
'databases' => DatabaseModel::getAllDatabases(),
|
||||||
|
'current_db' => Config::get('DB_NAME')
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show details of a specific database
|
||||||
|
* @param string $database_name
|
||||||
|
*/
|
||||||
|
public function show($database_name = null)
|
||||||
|
{
|
||||||
|
if (!$database_name) {
|
||||||
|
$database_name = Config::get('DB_NAME');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->View->render('database/show', array(
|
||||||
|
'tables' => DatabaseModel::getTablesInDatabase($database_name),
|
||||||
|
'database_name' => $database_name,
|
||||||
|
'table_info' => DatabaseModel::getTableDetails($database_name)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new database
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
$database_name = Request::post('database_name');
|
||||||
|
|
||||||
|
if ($this->isAjaxRequest()) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if (DatabaseModel::createDatabase($database_name)) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Database created successfully'
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Failed to create database'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Redirect::to('database');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a database
|
||||||
|
* @param string $database_name
|
||||||
|
*/
|
||||||
|
public function delete($database_name)
|
||||||
|
{
|
||||||
|
// Prevent deletion of the current database
|
||||||
|
if ($database_name === Config::get('DB_NAME')) {
|
||||||
|
if ($this->isAjaxRequest()) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Cannot delete the currently connected database'
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Redirect::to('database');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$success = DatabaseModel::deleteDatabase($database_name);
|
||||||
|
|
||||||
|
if ($this->isAjaxRequest()) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
if ($success) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Database deleted successfully'
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Failed to delete database'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Redirect::to('database');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get database structure as JSON (AJAX endpoint)
|
||||||
|
* @param string $database_name
|
||||||
|
*/
|
||||||
|
public function getStructure($database_name = null)
|
||||||
|
{
|
||||||
|
if (!$database_name) {
|
||||||
|
$database_name = Config::get('DB_NAME');
|
||||||
|
}
|
||||||
|
|
||||||
|
$structure = DatabaseModel::getDatabaseStructure($database_name);
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'structure' => $structure
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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';
|
||||||
|
}
|
||||||
|
}
|
||||||
201
application/controller/DbUserController.php
Normal file
201
application/controller/DbUserController.php
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class UserController for Database Manager
|
||||||
|
*
|
||||||
|
* Controller for managing MySQL users and privileges
|
||||||
|
*/
|
||||||
|
class DbUserController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Construct this object by extending the basic Controller class
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
// Only admin users can access database user management
|
||||||
|
Auth::checkAuthentication();
|
||||||
|
Auth::checkAdminAuthentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all database users
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$this->View->render('dbuser/index', array(
|
||||||
|
'users' => DbUserModel::getAllUsers(),
|
||||||
|
'current_user' => Config::get('DB_USER')
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new database user
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
if (Request::post('submit_create_user')) {
|
||||||
|
$username = Request::post('username');
|
||||||
|
$password = Request::post('password');
|
||||||
|
$host = Request::post('host');
|
||||||
|
|
||||||
|
if ($this->isAjaxRequest()) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if (DbUserModel::createUser($username, $password, $host)) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'User created successfully',
|
||||||
|
'reload' => true
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Failed to create user'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DbUserModel::createUser($username, $password, $host)) {
|
||||||
|
Redirect::to('dbuser');
|
||||||
|
} else {
|
||||||
|
Redirect::to('dbuser');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show create user form
|
||||||
|
$this->View->render('dbuser/create');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit user details and privileges
|
||||||
|
* @param string $username
|
||||||
|
* @param string $host
|
||||||
|
*/
|
||||||
|
public function edit($username, $host)
|
||||||
|
{
|
||||||
|
if (Request::post('submit_edit_user')) {
|
||||||
|
$new_password = Request::post('password');
|
||||||
|
$privileges = Request::post('privileges');
|
||||||
|
|
||||||
|
if ($this->isAjaxRequest()) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$success = true;
|
||||||
|
$message = 'User updated successfully';
|
||||||
|
|
||||||
|
if (!empty($new_password)) {
|
||||||
|
if (!DbUserModel::updateUserPassword($username, $host, $new_password)) {
|
||||||
|
$success = false;
|
||||||
|
$message = 'Failed to update user password';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($success && !DbUserModel::updateUserPrivileges($username, $host, $privileges)) {
|
||||||
|
$success = false;
|
||||||
|
$message = 'Failed to update user privileges';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => $message
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $message
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$success = true;
|
||||||
|
if (!empty($new_password)) {
|
||||||
|
$success = DbUserModel::updateUserPassword($username, $host, $new_password);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($success && !DbUserModel::updateUserPrivileges($username, $host, $privileges)) {
|
||||||
|
$success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Redirect::to('dbuser');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show edit user form
|
||||||
|
$this->View->render('dbuser/edit', array(
|
||||||
|
'user' => DbUserModel::getUserDetails($username, $host),
|
||||||
|
'privileges' => DbUserModel::getUserPrivileges($username, $host),
|
||||||
|
'databases' => DatabaseModel::getAllDatabases()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a user
|
||||||
|
* @param string $username
|
||||||
|
* @param string $host
|
||||||
|
*/
|
||||||
|
public function delete($username, $host)
|
||||||
|
{
|
||||||
|
// Prevent deletion of current user
|
||||||
|
if ($username === Config::get('DB_USER')) {
|
||||||
|
if ($this->isAjaxRequest()) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Cannot delete the currently connected user'
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Redirect::to('dbuser');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$success = DbUserModel::deleteUser($username, $host);
|
||||||
|
|
||||||
|
if ($this->isAjaxRequest()) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'User deleted successfully'
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Failed to delete user'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Redirect::to('dbuser');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show user privileges
|
||||||
|
* @param string $username
|
||||||
|
* @param string $host
|
||||||
|
*/
|
||||||
|
public function privileges($username, $host)
|
||||||
|
{
|
||||||
|
$this->View->render('dbuser/privileges', array(
|
||||||
|
'user' => DbUserModel::getUserDetails($username, $host),
|
||||||
|
'privileges' => DbUserModel::getUserPrivileges($username, $host)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,34 +5,23 @@ class MessageController extends Controller
|
|||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
// Require login for all message features
|
|
||||||
Auth::checkAuthentication();
|
Auth::checkAuthentication();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the request is an AJAX request
|
|
||||||
*/
|
|
||||||
private function isAjaxRequest()
|
private function isAjaxRequest()
|
||||||
{
|
{
|
||||||
return isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
|
return isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
|
||||||
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
|
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}
|
|
||||||
*/
|
|
||||||
public function send()
|
public function send()
|
||||||
{
|
{
|
||||||
// Handle POST request
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
$receiver_id = isset($_POST['receiver_id']) ? $_POST['receiver_id'] : null;
|
$receiver_id = isset($_POST['receiver_id']) ? $_POST['receiver_id'] : null;
|
||||||
$subject = isset($_POST['subject']) ? $_POST['subject'] : 'No Subject';
|
$subject = isset($_POST['subject']) ? $_POST['subject'] : 'No Subject';
|
||||||
$message = isset($_POST['message']) ? $_POST['message'] : null;
|
$message = isset($_POST['message']) ? $_POST['message'] : null;
|
||||||
|
|
||||||
if (!$receiver_id || !$message) {
|
if (!$receiver_id || !$message) {
|
||||||
// Return JSON for AJAX requests
|
|
||||||
if ($this->isAjaxRequest()) {
|
if ($this->isAjaxRequest()) {
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
echo json_encode(['success' => false, 'message' => 'Receiver and message are required']);
|
echo json_encode(['success' => false, 'message' => 'Receiver and message are required']);
|
||||||
@@ -44,11 +33,9 @@ class MessageController extends Controller
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the message
|
|
||||||
$sender_id = Session::get('user_id');
|
$sender_id = Session::get('user_id');
|
||||||
$success = MessageModel::sendToUser($sender_id, $receiver_id, $subject, $message);
|
$success = MessageModel::sendToUser($sender_id, $receiver_id, $subject, $message);
|
||||||
|
|
||||||
// Return JSON for AJAX requests
|
|
||||||
if ($this->isAjaxRequest()) {
|
if ($this->isAjaxRequest()) {
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
if ($success) {
|
if ($success) {
|
||||||
@@ -59,14 +46,12 @@ class MessageController extends Controller
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regular request handling
|
|
||||||
if ($success) {
|
if ($success) {
|
||||||
Session::add('feedback_positive', 'Message sent successfully');
|
Session::add('feedback_positive', 'Message sent successfully');
|
||||||
} else {
|
} else {
|
||||||
Session::add('feedback_negative', 'Failed to send message');
|
Session::add('feedback_negative', 'Failed to send message');
|
||||||
}
|
}
|
||||||
|
|
||||||
// If coming from conversation view, return there
|
|
||||||
if (isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], 'conversation') !== false) {
|
if (isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], 'conversation') !== false) {
|
||||||
Redirect::to('message/conversation/' . $receiver_id);
|
Redirect::to('message/conversation/' . $receiver_id);
|
||||||
} else {
|
} else {
|
||||||
@@ -75,7 +60,7 @@ class MessageController extends Controller
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle GET request
|
// GET request: message/send/{receiver_id}/{subject}/{message}
|
||||||
$url_parts = explode('/', trim($_SERVER['REQUEST_URI'], '/'));
|
$url_parts = explode('/', trim($_SERVER['REQUEST_URI'], '/'));
|
||||||
$receiver_id = isset($url_parts[2]) ? $url_parts[2] : null;
|
$receiver_id = isset($url_parts[2]) ? $url_parts[2] : null;
|
||||||
$subject = isset($url_parts[3]) ? urldecode($url_parts[3]) : null;
|
$subject = isset($url_parts[3]) ? urldecode($url_parts[3]) : null;
|
||||||
@@ -87,7 +72,6 @@ class MessageController extends Controller
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify receiver exists
|
|
||||||
$receiver = UserModel::getPublicProfileOfUser($receiver_id);
|
$receiver = UserModel::getPublicProfileOfUser($receiver_id);
|
||||||
if (!$receiver) {
|
if (!$receiver) {
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
@@ -95,7 +79,6 @@ class MessageController extends Controller
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the message
|
|
||||||
$sender_id = Session::get('user_id');
|
$sender_id = Session::get('user_id');
|
||||||
$success = MessageModel::sendToUser($sender_id, $receiver_id, $subject, $message);
|
$success = MessageModel::sendToUser($sender_id, $receiver_id, $subject, $message);
|
||||||
|
|
||||||
@@ -107,14 +90,8 @@ class MessageController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a message to a group via URL parameters
|
|
||||||
* URL format: message/sendgroup/{group_type}/{subject}/{message}
|
|
||||||
* group_type can be: admins, moderators, all_users
|
|
||||||
*/
|
|
||||||
public function sendgroup()
|
public function sendgroup()
|
||||||
{
|
{
|
||||||
// Handle POST request
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
$group_type = isset($_POST['group_type']) ? $_POST['group_type'] : null;
|
$group_type = isset($_POST['group_type']) ? $_POST['group_type'] : null;
|
||||||
$subject = isset($_POST['subject']) ? $_POST['subject'] : 'No Subject';
|
$subject = isset($_POST['subject']) ? $_POST['subject'] : 'No Subject';
|
||||||
@@ -126,14 +103,12 @@ class MessageController extends Controller
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate group type
|
|
||||||
if (!in_array($group_type, ['admins', 'moderators', 'all_users'])) {
|
if (!in_array($group_type, ['admins', 'moderators', 'all_users'])) {
|
||||||
Session::add('feedback_negative', 'Invalid group type');
|
Session::add('feedback_negative', 'Invalid group type');
|
||||||
Redirect::to('message');
|
Redirect::to('message');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the message
|
|
||||||
$sender_id = Session::get('user_id');
|
$sender_id = Session::get('user_id');
|
||||||
$success = MessageModel::sendToGroup($sender_id, $group_type, $subject, $message);
|
$success = MessageModel::sendToGroup($sender_id, $group_type, $subject, $message);
|
||||||
|
|
||||||
@@ -147,7 +122,7 @@ class MessageController extends Controller
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle GET request
|
// GET request: message/sendgroup/{group_type}/{subject}/{message}
|
||||||
$url_parts = explode('/', trim($_SERVER['REQUEST_URI'], '/'));
|
$url_parts = explode('/', trim($_SERVER['REQUEST_URI'], '/'));
|
||||||
$group_type = isset($url_parts[2]) ? $url_parts[2] : null;
|
$group_type = isset($url_parts[2]) ? $url_parts[2] : null;
|
||||||
$subject = isset($url_parts[3]) ? urldecode($url_parts[3]) : null;
|
$subject = isset($url_parts[3]) ? urldecode($url_parts[3]) : null;
|
||||||
@@ -159,14 +134,12 @@ class MessageController extends Controller
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate group type
|
|
||||||
if (!in_array($group_type, ['admins', 'moderators', 'all_users'])) {
|
if (!in_array($group_type, ['admins', 'moderators', 'all_users'])) {
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
echo json_encode(['success' => false, 'message' => 'Invalid group type. Must be: admins, moderators, or all_users']);
|
echo json_encode(['success' => false, 'message' => 'Invalid group type. Must be: admins, moderators, or all_users']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the message
|
|
||||||
$sender_id = Session::get('user_id');
|
$sender_id = Session::get('user_id');
|
||||||
$success = MessageModel::sendToGroup($sender_id, $group_type, $subject, $message);
|
$success = MessageModel::sendToGroup($sender_id, $group_type, $subject, $message);
|
||||||
|
|
||||||
@@ -178,18 +151,12 @@ class MessageController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle reply to a message
|
|
||||||
*/
|
|
||||||
public function reply()
|
public function reply()
|
||||||
{
|
{
|
||||||
// Always return JSON for this endpoint
|
while (ob_get_level()) ob_end_clean();
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
// Start output buffering to catch any accidental output
|
|
||||||
ob_start();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
echo json_encode(['success' => false, 'message' => 'Invalid request method']);
|
echo json_encode(['success' => false, 'message' => 'Invalid request method']);
|
||||||
exit();
|
exit();
|
||||||
@@ -209,41 +176,25 @@ class MessageController extends Controller
|
|||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the message (using sendToUser without subject)
|
$success = MessageModel::sendToUser($sender_id, $receiver_id, 'Direct Message', $message);
|
||||||
$success = MessageModel::sendToUser($sender_id, $receiver_id, 'Re: Message', $message);
|
|
||||||
|
|
||||||
if ($success) {
|
if ($success) {
|
||||||
echo json_encode(['success' => true, 'message' => 'Reply sent successfully']);
|
echo json_encode(['success' => true, 'message' => 'Reply sent successfully']);
|
||||||
} else {
|
} else {
|
||||||
echo json_encode(['success' => false, 'message' => 'Failed to send reply']);
|
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();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show global chat interface
|
|
||||||
*/
|
|
||||||
public function global()
|
public function global()
|
||||||
{
|
{
|
||||||
// Redirect to main messages page with global chat hash
|
|
||||||
Redirect::to('message#load-global');
|
Redirect::to('message#load-global');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the messenger interface
|
|
||||||
*/
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$user_id = Session::get('user_id');
|
$user_id = Session::get('user_id');
|
||||||
|
|
||||||
// Get conversations and unread count
|
|
||||||
$conversations = MessageModel::getConversations($user_id);
|
$conversations = MessageModel::getConversations($user_id);
|
||||||
$unread_count = MessageModel::getUnreadCount($user_id);
|
$unread_count = MessageModel::getUnreadCount($user_id);
|
||||||
|
|
||||||
@@ -254,9 +205,6 @@ class MessageController extends Controller
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show conversation with a specific user
|
|
||||||
*/
|
|
||||||
public function conversation()
|
public function conversation()
|
||||||
{
|
{
|
||||||
$user_id = Session::get('user_id');
|
$user_id = Session::get('user_id');
|
||||||
@@ -268,69 +216,53 @@ class MessageController extends Controller
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user info for the other person
|
|
||||||
$other_user = UserModel::getPublicProfileOfUser($other_user_id);
|
$other_user = UserModel::getPublicProfileOfUser($other_user_id);
|
||||||
if (!$other_user) {
|
if (!$other_user) {
|
||||||
Redirect::to('message');
|
Redirect::to('message');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to main messages page with conversation hash
|
|
||||||
Redirect::to('message#load-conversation-' . $other_user_id);
|
Redirect::to('message#load-conversation-' . $other_user_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get conversation messages as JSON (AJAX endpoint)
|
|
||||||
*/
|
|
||||||
public function getConversationMessages()
|
public function getConversationMessages()
|
||||||
{
|
{
|
||||||
|
while (ob_get_level()) ob_end_clean();
|
||||||
|
|
||||||
$user_id = Session::get('user_id');
|
$user_id = Session::get('user_id');
|
||||||
$url_parts = explode('/', trim($_SERVER['REQUEST_URI'], '/'));
|
$url_parts = explode('/', trim($_SERVER['REQUEST_URI'], '/'));
|
||||||
$other_user_id = isset($url_parts[2]) ? $url_parts[2] : null;
|
$other_user_id = isset($url_parts[2]) ? $url_parts[2] : null;
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
if (!$other_user_id) {
|
if (!$other_user_id) {
|
||||||
header('Content-Type: application/json');
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Missing user ID']);
|
echo json_encode(['success' => false, 'message' => 'Missing user ID']);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get messages
|
|
||||||
$messages = MessageModel::getMessagesWithUser($user_id, $other_user_id);
|
|
||||||
|
|
||||||
// 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();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
$messages = MessageModel::getMessagesWithUser($user_id, $other_user_id);
|
||||||
* Send message to global chat
|
MessageModel::markAsRead($user_id, $other_user_id);
|
||||||
*/
|
|
||||||
|
echo json_encode(['success' => true, 'messages' => $messages]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGlobalMessages()
|
||||||
|
{
|
||||||
|
while (ob_get_level()) ob_end_clean();
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
$messages = MessageModel::getGlobalMessages();
|
||||||
|
echo json_encode(['success' => true, 'messages' => $messages]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
public function sendToGlobal()
|
public function sendToGlobal()
|
||||||
{
|
{
|
||||||
// Always return JSON for this endpoint
|
while (ob_get_level()) ob_end_clean();
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
// Start output buffering to catch any accidental output
|
|
||||||
ob_start();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
echo json_encode(['success' => false, 'message' => 'Invalid request method']);
|
echo json_encode(['success' => false, 'message' => 'Invalid request method']);
|
||||||
exit();
|
exit();
|
||||||
@@ -356,25 +288,18 @@ class MessageController extends Controller
|
|||||||
} else {
|
} else {
|
||||||
echo json_encode(['success' => false, 'message' => 'Failed to send message']);
|
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();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get unread count as JSON
|
|
||||||
*/
|
|
||||||
public function unreadcount()
|
public function unreadcount()
|
||||||
{
|
{
|
||||||
|
while (ob_get_level()) ob_end_clean();
|
||||||
|
|
||||||
$user_id = Session::get('user_id');
|
$user_id = Session::get('user_id');
|
||||||
$unread_count = MessageModel::getUnreadCount($user_id);
|
$unread_count = MessageModel::getUnreadCount($user_id);
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
echo json_encode(['unread_count' => $unread_count]);
|
echo json_encode(['unread_count' => $unread_count]);
|
||||||
|
exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,23 +22,25 @@ class RegisterController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
// only admins can access registration; reuse existing admin auth check
|
if (Session::userIsLoggedIn()) {
|
||||||
Auth::checkAdminAuthentication();
|
Redirect::to('index');
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->View->render('register/index');
|
$this->View->render('register/index');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Register page action
|
|
||||||
* POST-request after form submit
|
|
||||||
*/
|
|
||||||
public function register_action()
|
public function register_action()
|
||||||
{
|
{
|
||||||
// enforce admin-only for registration
|
if (Session::userIsLoggedIn()) {
|
||||||
Auth::checkAdminAuthentication();
|
Redirect::to('index');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
RegistrationModel::registerNewUser();
|
if (RegistrationModel::registerNewUser()) {
|
||||||
|
Redirect::to('login');
|
||||||
Redirect::to('admin/index');
|
} else {
|
||||||
|
Redirect::to('register');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
181
application/controller/SqlController.php
Normal file
181
application/controller/SqlController.php
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SqlController
|
||||||
|
*
|
||||||
|
* Controller for executing raw SQL queries
|
||||||
|
*/
|
||||||
|
class SqlController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Construct this object by extending the basic Controller class
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
// Only logged-in users can access the SQL console
|
||||||
|
Auth::checkAuthentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show SQL console interface
|
||||||
|
* @param string $database_name
|
||||||
|
*/
|
||||||
|
public function index($database_name = null)
|
||||||
|
{
|
||||||
|
if (!$database_name) {
|
||||||
|
$database_name = Config::get('DB_NAME');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->View->render('sql/index', array(
|
||||||
|
'database_name' => $database_name,
|
||||||
|
'databases' => DatabaseModel::getAllDatabases(),
|
||||||
|
'history' => SqlModel::getQueryHistory(Session::get('user_id'))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute SQL query
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$database_name = Request::post('database_name') ?: Config::get('DB_NAME');
|
||||||
|
$sql_query = Request::post('sql_query');
|
||||||
|
|
||||||
|
if (empty($sql_query)) {
|
||||||
|
if ($this->isAjaxRequest()) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'SQL query cannot be empty'
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Redirect::to('sql');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = SqlModel::executeQuery($database_name, $sql_query, Session::get('user_id'));
|
||||||
|
|
||||||
|
if ($this->isAjaxRequest()) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if ($result['success']) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => $result['message'],
|
||||||
|
'result' => $result['result'],
|
||||||
|
'affected_rows' => $result['affected_rows'],
|
||||||
|
'execution_time' => $result['execution_time'],
|
||||||
|
'query_type' => $result['query_type']
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => $result['message'],
|
||||||
|
'error' => $result['error']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-AJAX: redirect with results in session
|
||||||
|
Session::set('sql_result', $result);
|
||||||
|
Redirect::to('sql/index/' . urlencode($database_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query history as JSON (AJAX endpoint)
|
||||||
|
*/
|
||||||
|
public function getHistory()
|
||||||
|
{
|
||||||
|
$history = SqlModel::getQueryHistory(Session::get('user_id'));
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'history' => $history
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear query history
|
||||||
|
*/
|
||||||
|
public function clearHistory()
|
||||||
|
{
|
||||||
|
$success = SqlModel::clearQueryHistory(Session::get('user_id'));
|
||||||
|
|
||||||
|
if ($this->isAjaxRequest()) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Query history cleared successfully'
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Failed to clear query history'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Redirect::to('sql');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get database schema for autocomplete
|
||||||
|
* @param string $database_name
|
||||||
|
*/
|
||||||
|
public function getSchema($database_name = null)
|
||||||
|
{
|
||||||
|
if (!$database_name) {
|
||||||
|
$database_name = Config::get('DB_NAME');
|
||||||
|
}
|
||||||
|
|
||||||
|
$schema = SqlModel::getDatabaseSchema($database_name);
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'schema' => $schema
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format SQL query (AJAX endpoint)
|
||||||
|
*/
|
||||||
|
public function formatQuery()
|
||||||
|
{
|
||||||
|
$query = Request::post('query');
|
||||||
|
|
||||||
|
if (empty($query)) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Query cannot be empty'
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$formatted = SqlModel::formatQuery($query);
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'formatted' => $formatted
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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';
|
||||||
|
}
|
||||||
|
}
|
||||||
248
application/controller/TableController.php
Normal file
248
application/controller/TableController.php
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class TableController
|
||||||
|
*
|
||||||
|
* Controller for managing database tables
|
||||||
|
*/
|
||||||
|
class TableController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Construct this object by extending the basic Controller class
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
// Only logged-in users can access the table manager
|
||||||
|
Auth::checkAuthentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show table content with pagination
|
||||||
|
* @param string $database_name
|
||||||
|
* @param string $table_name
|
||||||
|
* @param int $page
|
||||||
|
*/
|
||||||
|
public function show($database_name = null, $table_name = null, $page = 1)
|
||||||
|
{
|
||||||
|
if (!$database_name) {
|
||||||
|
$database_name = Config::get('DB_NAME');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table_name) {
|
||||||
|
Redirect::to('database/show/' . urlencode($database_name));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$page = (int)$page;
|
||||||
|
$per_page = 20;
|
||||||
|
|
||||||
|
$this->View->render('table/show', array(
|
||||||
|
'database_name' => $database_name,
|
||||||
|
'table_name' => $table_name,
|
||||||
|
'columns' => TableModel::getTableColumns($database_name, $table_name),
|
||||||
|
'rows' => TableModel::getTableRows($database_name, $table_name, $page, $per_page),
|
||||||
|
'total_rows' => TableModel::getTableRowCount($database_name, $table_name),
|
||||||
|
'current_page' => $page,
|
||||||
|
'per_page' => $per_page,
|
||||||
|
'table_info' => TableModel::getTableInfo($database_name, $table_name)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new table
|
||||||
|
* @param string $database_name
|
||||||
|
*/
|
||||||
|
public function create($database_name = null)
|
||||||
|
{
|
||||||
|
if (!$database_name) {
|
||||||
|
$database_name = Config::get('DB_NAME');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Request::post('submit_create_table')) {
|
||||||
|
$table_name = Request::post('table_name');
|
||||||
|
$columns = Request::post('columns');
|
||||||
|
|
||||||
|
if ($this->isAjaxRequest()) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if (TableModel::createTable($database_name, $table_name, $columns)) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Table created successfully',
|
||||||
|
'redirect' => Config::get('URL') . 'table/show/' . urlencode($database_name) . '/' . urlencode($table_name)
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Failed to create table'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TableModel::createTable($database_name, $table_name, $columns)) {
|
||||||
|
Redirect::to('table/show/' . urlencode($database_name) . '/' . urlencode($table_name));
|
||||||
|
} else {
|
||||||
|
Redirect::to('database/show/' . urlencode($database_name));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show create table form
|
||||||
|
$this->View->render('table/create', array(
|
||||||
|
'database_name' => $database_name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show table structure
|
||||||
|
* @param string $database_name
|
||||||
|
* @param string $table_name
|
||||||
|
*/
|
||||||
|
public function structure($database_name = null, $table_name = null)
|
||||||
|
{
|
||||||
|
if (!$database_name) {
|
||||||
|
$database_name = Config::get('DB_NAME');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table_name) {
|
||||||
|
Redirect::to('database/show/' . urlencode($database_name));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->View->render('table/structure', array(
|
||||||
|
'database_name' => $database_name,
|
||||||
|
'table_name' => $table_name,
|
||||||
|
'columns' => TableModel::getTableColumns($database_name, $table_name),
|
||||||
|
'indexes' => TableModel::getTableIndexes($database_name, $table_name),
|
||||||
|
'table_info' => TableModel::getTableInfo($database_name, $table_name)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a column to a table
|
||||||
|
* @param string $database_name
|
||||||
|
* @param string $table_name
|
||||||
|
*/
|
||||||
|
public function addColumn($database_name = null, $table_name = null)
|
||||||
|
{
|
||||||
|
if (!$database_name) {
|
||||||
|
$database_name = Config::get('DB_NAME');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$table_name) {
|
||||||
|
Redirect::to('database/show/' . urlencode($database_name));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Request::post('submit_add_column')) {
|
||||||
|
$column_name = Request::post('column_name');
|
||||||
|
$column_type = Request::post('column_type');
|
||||||
|
$column_null = Request::post('column_null');
|
||||||
|
$column_key = Request::post('column_key');
|
||||||
|
$column_default = Request::post('column_default');
|
||||||
|
$column_extra = Request::post('column_extra');
|
||||||
|
|
||||||
|
if ($this->isAjaxRequest()) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if (TableModel::addColumn($database_name, $table_name, $column_name, $column_type, $column_null, $column_key, $column_default, $column_extra)) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Column added successfully',
|
||||||
|
'reload' => true
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Failed to add column'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TableModel::addColumn($database_name, $table_name, $column_name, $column_type, $column_null, $column_key, $column_default, $column_extra)) {
|
||||||
|
Redirect::to('table/structure/' . urlencode($database_name) . '/' . urlencode($table_name));
|
||||||
|
} else {
|
||||||
|
Redirect::to('table/structure/' . urlencode($database_name) . '/' . urlencode($table_name));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show add column form
|
||||||
|
$this->View->render('table/add_column', array(
|
||||||
|
'database_name' => $database_name,
|
||||||
|
'table_name' => $table_name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop a column from a table
|
||||||
|
* @param string $database_name
|
||||||
|
* @param string $table_name
|
||||||
|
* @param string $column_name
|
||||||
|
*/
|
||||||
|
public function dropColumn($database_name, $table_name, $column_name)
|
||||||
|
{
|
||||||
|
$success = TableModel::dropColumn($database_name, $table_name, $column_name);
|
||||||
|
|
||||||
|
if ($this->isAjaxRequest()) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
if ($success) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Column dropped successfully',
|
||||||
|
'reload' => true
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Failed to drop column'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Redirect::to('table/structure/' . urlencode($database_name) . '/' . urlencode($table_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a table
|
||||||
|
* @param string $database_name
|
||||||
|
* @param string $table_name
|
||||||
|
*/
|
||||||
|
public function delete($database_name, $table_name)
|
||||||
|
{
|
||||||
|
$success = TableModel::deleteTable($database_name, $table_name);
|
||||||
|
|
||||||
|
if ($this->isAjaxRequest()) {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
if ($success) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Table deleted successfully',
|
||||||
|
'redirect' => Config::get('URL') . 'database/show/' . urlencode($database_name)
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Failed to delete table'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Redirect::to('database/show/' . urlencode($database_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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';
|
||||||
|
}
|
||||||
|
}
|
||||||
183
application/model/DatabaseModel.php
Normal file
183
application/model/DatabaseModel.php
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class DatabaseModel
|
||||||
|
*
|
||||||
|
* Model for database operations using PDO
|
||||||
|
*/
|
||||||
|
class DatabaseModel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get all databases on the server
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getAllDatabases()
|
||||||
|
{
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
$sql = "SHOW DATABASES";
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
$query->execute();
|
||||||
|
|
||||||
|
$databases = $query->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
// Filter out system databases
|
||||||
|
$system_dbs = ['information_schema', 'performance_schema', 'mysql', 'sys'];
|
||||||
|
return array_diff($databases, $system_dbs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all tables in a specific database
|
||||||
|
* @param string $database_name
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getTablesInDatabase($database_name)
|
||||||
|
{
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
$sql = "SHOW TABLES FROM " . $database_name;
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
$query->execute();
|
||||||
|
|
||||||
|
return $query->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get detailed information about tables in a database
|
||||||
|
* @param string $database_name
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getTableDetails($database_name)
|
||||||
|
{
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
$tables = self::getTablesInDatabase($database_name);
|
||||||
|
$table_details = array();
|
||||||
|
|
||||||
|
foreach ($tables as $table) {
|
||||||
|
$sql = "SHOW TABLE STATUS FROM " . $database_name . " LIKE :table_name";
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
$query->execute(array(':table_name' => $table));
|
||||||
|
|
||||||
|
$details = $query->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($details) {
|
||||||
|
$table_details[$table] = array(
|
||||||
|
'engine' => $details['Engine'],
|
||||||
|
'rows' => $details['Rows'],
|
||||||
|
'data_size' => self::formatBytes($details['Data_length']),
|
||||||
|
'index_size' => self::formatBytes($details['Index_length']),
|
||||||
|
'total_size' => self::formatBytes($details['Data_length'] + $details['Index_length']),
|
||||||
|
'collation' => $details['Collation'],
|
||||||
|
'comment' => $details['Comment']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $table_details;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get complete database structure (tables and columns)
|
||||||
|
* @param string $database_name
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getDatabaseStructure($database_name)
|
||||||
|
{
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
$structure = array();
|
||||||
|
$tables = self::getTablesInDatabase($database_name);
|
||||||
|
|
||||||
|
foreach ($tables as $table) {
|
||||||
|
$sql = "DESCRIBE " . $database_name . "." . $table;
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
$query->execute();
|
||||||
|
|
||||||
|
$columns = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$structure[$table] = $columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $structure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new database
|
||||||
|
* @param string $database_name
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function createDatabase($database_name)
|
||||||
|
{
|
||||||
|
if (!$database_name || !preg_match('/^[a-zA-Z0-9_]+$/', $database_name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = "CREATE DATABASE `" . $database_name . "` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci";
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
return $query->execute();
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a database
|
||||||
|
* @param string $database_name
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function deleteDatabase($database_name)
|
||||||
|
{
|
||||||
|
if (!$database_name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = "DROP DATABASE `" . $database_name . "`";
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
return $query->execute();
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get table columns with details
|
||||||
|
* @param string $database_name
|
||||||
|
* @param string $table_name
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getTableColumns($database_name, $table_name)
|
||||||
|
{
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
$sql = "SHOW COLUMNS FROM " . $database_name . "." . $table_name;
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
$query->execute();
|
||||||
|
|
||||||
|
return $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format bytes to human readable format
|
||||||
|
* @param int $bytes
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private static function formatBytes($bytes)
|
||||||
|
{
|
||||||
|
if ($bytes >= 1073741824) {
|
||||||
|
return number_format($bytes / 1073741824, 2) . ' GB';
|
||||||
|
} elseif ($bytes >= 1048576) {
|
||||||
|
return number_format($bytes / 1048576, 2) . ' MB';
|
||||||
|
} elseif ($bytes >= 1024) {
|
||||||
|
return number_format($bytes / 1024, 2) . ' KB';
|
||||||
|
} elseif ($bytes > 1) {
|
||||||
|
return $bytes . ' bytes';
|
||||||
|
} elseif ($bytes == 1) {
|
||||||
|
return '1 byte';
|
||||||
|
} else {
|
||||||
|
return '0 bytes';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,15 +15,13 @@ class RegistrationModel
|
|||||||
*/
|
*/
|
||||||
public static function registerNewUser($isAdmin = false)
|
public static function registerNewUser($isAdmin = false)
|
||||||
{
|
{
|
||||||
// clean the input
|
|
||||||
$user_name = strip_tags(Request::post('user_name'));
|
$user_name = strip_tags(Request::post('user_name'));
|
||||||
$user_email = strip_tags(Request::post('user_email'));
|
$user_email = strip_tags(Request::post('user_email'));
|
||||||
// Use 'user_password' if provided (admin registration), otherwise 'user_password_new'
|
$user_password_new = Request::post('user_password_new');
|
||||||
$user_password_new = $isAdmin ? Request::post('user_password_new') : Request::post('user_password_new');
|
$user_password_repeat = $user_password_new;
|
||||||
$user_password_repeat = $user_password_new; // no repeat field
|
|
||||||
|
|
||||||
// validate using existing validators and messages
|
|
||||||
$valid = true;
|
$valid = true;
|
||||||
|
if (!self::validateRecaptcha()) { $valid = false; }
|
||||||
if (!self::validateUserName($user_name)) { $valid = false; }
|
if (!self::validateUserName($user_name)) { $valid = false; }
|
||||||
if (!self::validateUserEmail($user_email, $user_email)) { $valid = false; }
|
if (!self::validateUserEmail($user_email, $user_email)) { $valid = false; }
|
||||||
if (!self::validateUserPassword($user_password_new, $user_password_repeat)) { $valid = false; }
|
if (!self::validateUserPassword($user_password_new, $user_password_repeat)) { $valid = false; }
|
||||||
@@ -77,12 +75,35 @@ class RegistrationModel
|
|||||||
return $return;
|
return $return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static function validateRecaptcha()
|
||||||
* Validates the username
|
{
|
||||||
*
|
$recaptcha_response = Request::post('g-recaptcha-response');
|
||||||
* @param $user_name
|
|
||||||
* @return bool
|
if (empty($recaptcha_response)) {
|
||||||
*/
|
Session::add('feedback_negative', 'reCAPTCHA verification failed. Please try again.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$secret_key = Config::get('RECAPTCHA_SECRET_KEY');
|
||||||
|
$verify_url = 'https://www.google.com/recaptcha/api/siteverify';
|
||||||
|
|
||||||
|
$response = file_get_contents($verify_url . '?secret=' . $secret_key . '&response=' . $recaptcha_response);
|
||||||
|
$response_data = json_decode($response);
|
||||||
|
|
||||||
|
if (!$response_data->success) {
|
||||||
|
Session::add('feedback_negative', 'reCAPTCHA verification failed. Please try again.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// v3 returns a score from 0.0 to 1.0 (1.0 = likely human, 0.0 = likely bot)
|
||||||
|
if (isset($response_data->score) && $response_data->score < 0.5) {
|
||||||
|
Session::add('feedback_negative', 'Registration blocked due to suspicious activity.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public static function validateUserName($user_name)
|
public static function validateUserName($user_name)
|
||||||
{
|
{
|
||||||
if (empty($user_name)) {
|
if (empty($user_name)) {
|
||||||
@@ -90,7 +111,6 @@ class RegistrationModel
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if username is too short (2), too long (64) or does not fit the pattern (aZ09)
|
|
||||||
if (!preg_match('/^[a-zA-Z0-9]{2,64}$/', $user_name)) {
|
if (!preg_match('/^[a-zA-Z0-9]{2,64}$/', $user_name)) {
|
||||||
Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_DOES_NOT_FIT_PATTERN'));
|
Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_DOES_NOT_FIT_PATTERN'));
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
289
application/model/SqlModel.php
Normal file
289
application/model/SqlModel.php
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SqlModel
|
||||||
|
*
|
||||||
|
* Model for executing raw SQL queries
|
||||||
|
*/
|
||||||
|
class SqlModel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Execute a SQL query and return the result
|
||||||
|
* @param string $database_name
|
||||||
|
* @param string $sql_query
|
||||||
|
* @param int $user_id
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function executeQuery($database_name, $sql_query, $user_id = null)
|
||||||
|
{
|
||||||
|
$start_time = microtime(true);
|
||||||
|
|
||||||
|
// Save query to history if user_id is provided
|
||||||
|
if ($user_id) {
|
||||||
|
self::saveQueryToHistory($user_id, $database_name, $sql_query);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
// Determine query type
|
||||||
|
$query_type = self::getQueryType($sql_query);
|
||||||
|
|
||||||
|
// Execute the query
|
||||||
|
$query = $database->prepare($sql_query);
|
||||||
|
$query->execute();
|
||||||
|
|
||||||
|
$execution_time = number_format((microtime(true) - $start_time) * 1000, 2);
|
||||||
|
|
||||||
|
// Handle different query types
|
||||||
|
if ($query_type === 'SELECT' || $query_type === 'SHOW' || $query_type === 'DESCRIBE' || $query_type === 'EXPLAIN') {
|
||||||
|
// Return result set
|
||||||
|
$result = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$message = 'Query executed successfully. ' . count($result) . ' rows returned.';
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'success' => true,
|
||||||
|
'message' => $message,
|
||||||
|
'result' => $result,
|
||||||
|
'query_type' => $query_type,
|
||||||
|
'execution_time' => $execution_time,
|
||||||
|
'affected_rows' => 0
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Return affected rows count
|
||||||
|
$affected_rows = $query->rowCount();
|
||||||
|
$message = 'Query executed successfully. ' . $affected_rows . ' row(s) affected.';
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'success' => true,
|
||||||
|
'message' => $message,
|
||||||
|
'query_type' => $query_type,
|
||||||
|
'execution_time' => $execution_time,
|
||||||
|
'affected_rows' => $affected_rows,
|
||||||
|
'result' => array()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
return array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Query execution failed',
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'query_type' => 'ERROR',
|
||||||
|
'execution_time' => 0,
|
||||||
|
'result' => array()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save query to history
|
||||||
|
* @param int $user_id
|
||||||
|
* @param string $database_name
|
||||||
|
* @param string $sql_query
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private static function saveQueryToHistory($user_id, $database_name, $sql_query)
|
||||||
|
{
|
||||||
|
// Create history table if it doesn't exist
|
||||||
|
self::createHistoryTableIfNotExists();
|
||||||
|
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = "INSERT INTO sql_query_history (user_id, database_name, query_text, query_timestamp)
|
||||||
|
VALUES (:user_id, :database_name, :query_text, NOW())";
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
$query->execute(array(
|
||||||
|
':user_id' => $user_id,
|
||||||
|
':database_name' => $database_name,
|
||||||
|
':query_text' => $sql_query
|
||||||
|
));
|
||||||
|
|
||||||
|
// Keep only last 50 queries per user
|
||||||
|
$sql = "DELETE FROM sql_query_history
|
||||||
|
WHERE user_id = :user_id
|
||||||
|
AND id NOT IN (
|
||||||
|
SELECT id FROM (
|
||||||
|
SELECT id FROM sql_query_history
|
||||||
|
WHERE user_id = :user_id
|
||||||
|
ORDER BY query_timestamp DESC
|
||||||
|
LIMIT 50
|
||||||
|
) AS temp
|
||||||
|
)";
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
$query->execute(array(':user_id' => $user_id));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create SQL query history table if it doesn't exist
|
||||||
|
*/
|
||||||
|
private static function createHistoryTableIfNotExists()
|
||||||
|
{
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = "CREATE TABLE IF NOT EXISTS sql_query_history (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
database_name VARCHAR(64) NOT NULL,
|
||||||
|
query_text TEXT NOT NULL,
|
||||||
|
query_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_user_timestamp (user_id, query_timestamp)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
|
||||||
|
$database->exec($sql);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// Table creation failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get query history for a user
|
||||||
|
* @param int $user_id
|
||||||
|
* @param int $limit
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getQueryHistory($user_id, $limit = 50)
|
||||||
|
{
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = "SELECT database_name, query_text, query_timestamp
|
||||||
|
FROM sql_query_history
|
||||||
|
WHERE user_id = :user_id
|
||||||
|
ORDER BY query_timestamp DESC
|
||||||
|
LIMIT :limit";
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
$query->bindValue(':user_id', $user_id, PDO::PARAM_INT);
|
||||||
|
$query->bindValue(':limit', $limit, PDO::PARAM_INT);
|
||||||
|
$query->execute();
|
||||||
|
|
||||||
|
return $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear query history for a user
|
||||||
|
* @param int $user_id
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function clearQueryHistory($user_id)
|
||||||
|
{
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = "DELETE FROM sql_query_history WHERE user_id = :user_id";
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
return $query->execute(array(':user_id' => $user_id));
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get database schema for autocomplete
|
||||||
|
* @param string $database_name
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getDatabaseSchema($database_name)
|
||||||
|
{
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
$schema = array();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get all tables
|
||||||
|
$sql = "SHOW TABLES FROM " . $database_name;
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
$query->execute();
|
||||||
|
$tables = $query->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
foreach ($tables as $table) {
|
||||||
|
// Get columns for each table
|
||||||
|
$sql = "SHOW COLUMNS FROM " . $database_name . "." . $table;
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
$query->execute();
|
||||||
|
$columns = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$schema[$table] = array_map(function($column) {
|
||||||
|
return $column['Field'];
|
||||||
|
}, $columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $schema;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format SQL query (basic formatting)
|
||||||
|
* @param string $query
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function formatQuery($query)
|
||||||
|
{
|
||||||
|
// Basic SQL formatting
|
||||||
|
$query = trim($query);
|
||||||
|
|
||||||
|
// Uppercase SQL keywords
|
||||||
|
$keywords = array(
|
||||||
|
'SELECT', 'FROM', 'WHERE', 'AND', 'OR', 'ORDER BY', 'GROUP BY',
|
||||||
|
'HAVING', 'LIMIT', 'OFFSET', 'INSERT', 'INTO', 'VALUES',
|
||||||
|
'UPDATE', 'SET', 'DELETE', 'CREATE', 'TABLE', 'ALTER', 'DROP',
|
||||||
|
'INDEX', 'PRIMARY KEY', 'FOREIGN KEY', 'REFERENCES', 'JOIN',
|
||||||
|
'INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'ON', 'AS', 'DISTINCT',
|
||||||
|
'COUNT', 'SUM', 'AVG', 'MIN', 'MAX', 'EXISTS', 'IN', 'BETWEEN',
|
||||||
|
'LIKE', 'IS NULL', 'IS NOT NULL', 'UNION', 'ALL'
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($keywords as $keyword) {
|
||||||
|
$query = preg_replace('/\b' . preg_quote($keyword) . '\b/i', $keyword, $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add line breaks for better readability
|
||||||
|
$query = preg_replace('/\b(FROM|WHERE|GROUP BY|ORDER BY|HAVING|LIMIT|UNION)\b/', "\n$1", $query);
|
||||||
|
$query = preg_replace('/,\s*(\w)/', ",\n $1", $query);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the type of SQL query
|
||||||
|
* @param string $sql_query
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private static function getQueryType($sql_query)
|
||||||
|
{
|
||||||
|
$query = strtoupper(trim($sql_query));
|
||||||
|
|
||||||
|
if (strpos($query, 'SELECT') === 0) {
|
||||||
|
return 'SELECT';
|
||||||
|
} elseif (strpos($query, 'INSERT') === 0) {
|
||||||
|
return 'INSERT';
|
||||||
|
} elseif (strpos($query, 'UPDATE') === 0) {
|
||||||
|
return 'UPDATE';
|
||||||
|
} elseif (strpos($query, 'DELETE') === 0) {
|
||||||
|
return 'DELETE';
|
||||||
|
} elseif (strpos($query, 'CREATE') === 0) {
|
||||||
|
return 'CREATE';
|
||||||
|
} elseif (strpos($query, 'ALTER') === 0) {
|
||||||
|
return 'ALTER';
|
||||||
|
} elseif (strpos($query, 'DROP') === 0) {
|
||||||
|
return 'DROP';
|
||||||
|
} elseif (strpos($query, 'SHOW') === 0) {
|
||||||
|
return 'SHOW';
|
||||||
|
} elseif (strpos($query, 'DESCRIBE') === 0) {
|
||||||
|
return 'DESCRIBE';
|
||||||
|
} elseif (strpos($query, 'EXPLAIN') === 0) {
|
||||||
|
return 'EXPLAIN';
|
||||||
|
} else {
|
||||||
|
return 'OTHER';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
284
application/model/TableModel.php
Normal file
284
application/model/TableModel.php
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class TableModel
|
||||||
|
*
|
||||||
|
* Model for table operations using PDO
|
||||||
|
*/
|
||||||
|
class TableModel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get table rows with pagination
|
||||||
|
* @param string $database_name
|
||||||
|
* @param string $table_name
|
||||||
|
* @param int $page
|
||||||
|
* @param int $per_page
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getTableRows($database_name, $table_name, $page = 1, $per_page = 20)
|
||||||
|
{
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
$offset = ($page - 1) * $per_page;
|
||||||
|
|
||||||
|
$sql = "SELECT * FROM `" . $database_name . "`.`" . $table_name . "` LIMIT :offset, :per_page";
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
$query->bindParam(':offset', $offset, PDO::PARAM_INT);
|
||||||
|
$query->bindParam(':per_page', $per_page, PDO::PARAM_INT);
|
||||||
|
$query->execute();
|
||||||
|
|
||||||
|
return $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get total number of rows in a table
|
||||||
|
* @param string $database_name
|
||||||
|
* @param string $table_name
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public static function getTableRowCount($database_name, $table_name)
|
||||||
|
{
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
$sql = "SELECT COUNT(*) as count FROM `" . $database_name . "`.`" . $table_name . "`";
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
$query->execute();
|
||||||
|
|
||||||
|
$result = $query->fetch(PDO::FETCH_ASSOC);
|
||||||
|
return (int)$result['count'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get table column information
|
||||||
|
* @param string $database_name
|
||||||
|
* @param string $table_name
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getTableColumns($database_name, $table_name)
|
||||||
|
{
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
$sql = "SHOW COLUMNS FROM `" . $database_name . "`.`" . $table_name . "`";
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
$query->execute();
|
||||||
|
|
||||||
|
return $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get table indexes
|
||||||
|
* @param string $database_name
|
||||||
|
* @param string $table_name
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getTableIndexes($database_name, $table_name)
|
||||||
|
{
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
$sql = "SHOW INDEX FROM `" . $database_name . "`.`" . $table_name . "`";
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
$query->execute();
|
||||||
|
|
||||||
|
return $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get table information
|
||||||
|
* @param string $database_name
|
||||||
|
* @param string $table_name
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getTableInfo($database_name, $table_name)
|
||||||
|
{
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
$sql = "SHOW TABLE STATUS FROM `" . $database_name . "` LIKE :table_name";
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
$query->execute(array(':table_name' => $table_name));
|
||||||
|
|
||||||
|
$result = $query->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
return array(
|
||||||
|
'engine' => $result['Engine'],
|
||||||
|
'rows' => $result['Rows'],
|
||||||
|
'data_size' => self::formatBytes($result['Data_length']),
|
||||||
|
'index_size' => self::formatBytes($result['Index_length']),
|
||||||
|
'total_size' => self::formatBytes($result['Data_length'] + $result['Index_length']),
|
||||||
|
'collation' => $result['Collation'],
|
||||||
|
'comment' => $result['Comment'],
|
||||||
|
'auto_increment' => $result['Auto_increment'],
|
||||||
|
'create_time' => $result['Create_time'],
|
||||||
|
'update_time' => $result['Update_time']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new table
|
||||||
|
* @param string $database_name
|
||||||
|
* @param string $table_name
|
||||||
|
* @param array $columns
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function createTable($database_name, $table_name, $columns)
|
||||||
|
{
|
||||||
|
if (!$database_name || !$table_name || empty($columns)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
// Build column definitions
|
||||||
|
$column_definitions = array();
|
||||||
|
foreach ($columns as $column) {
|
||||||
|
$definition = "`" . $column['name'] . "` " . $column['type'];
|
||||||
|
|
||||||
|
if (isset($column['null']) && $column['null'] === 'NO') {
|
||||||
|
$definition .= " NOT NULL";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($column['default']) && $column['default'] !== '') {
|
||||||
|
$definition .= " DEFAULT '" . $column['default'] . "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($column['extra']) && $column['extra'] === 'auto_increment') {
|
||||||
|
$definition .= " AUTO_INCREMENT";
|
||||||
|
}
|
||||||
|
|
||||||
|
$column_definitions[] = $definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle primary key
|
||||||
|
foreach ($columns as $column) {
|
||||||
|
if (isset($column['key']) && $column['key'] === 'PRI') {
|
||||||
|
$column_definitions[] = "PRIMARY KEY (`" . $column['name'] . "`)";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$columns_sql = implode(', ', $column_definitions);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = "CREATE TABLE `" . $database_name . "`.`" . $table_name . "` (" . $columns_sql . ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
return $query->execute();
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a table
|
||||||
|
* @param string $database_name
|
||||||
|
* @param string $table_name
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function deleteTable($database_name, $table_name)
|
||||||
|
{
|
||||||
|
if (!$database_name || !$table_name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = "DROP TABLE `" . $database_name . "`.`" . $table_name . "`";
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
return $query->execute();
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a column to a table
|
||||||
|
* @param string $database_name
|
||||||
|
* @param string $table_name
|
||||||
|
* @param string $column_name
|
||||||
|
* @param string $column_type
|
||||||
|
* @param string $column_null
|
||||||
|
* @param string $column_key
|
||||||
|
* @param string $column_default
|
||||||
|
* @param string $column_extra
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function addColumn($database_name, $table_name, $column_name, $column_type, $column_null, $column_key, $column_default, $column_extra)
|
||||||
|
{
|
||||||
|
if (!$database_name || !$table_name || !$column_name || !$column_type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
$definition = "`" . $column_name . "` " . $column_type;
|
||||||
|
|
||||||
|
if ($column_null === 'NO') {
|
||||||
|
$definition .= " NOT NULL";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($column_default !== '') {
|
||||||
|
$definition .= " DEFAULT '" . $column_default . "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($column_extra === 'auto_increment') {
|
||||||
|
$definition .= " AUTO_INCREMENT";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = "ALTER TABLE `" . $database_name . "`.`" . $table_name . "` ADD " . $definition;
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
return $query->execute();
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop a column from a table
|
||||||
|
* @param string $database_name
|
||||||
|
* @param string $table_name
|
||||||
|
* @param string $column_name
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function dropColumn($database_name, $table_name, $column_name)
|
||||||
|
{
|
||||||
|
if (!$database_name || !$table_name || !$column_name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$database = DatabaseFactory::getFactory()->getConnection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = "ALTER TABLE `" . $database_name . "`.`" . $table_name . "` DROP COLUMN `" . $column_name . "`";
|
||||||
|
$query = $database->prepare($sql);
|
||||||
|
return $query->execute();
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format bytes to human readable format
|
||||||
|
* @param int $bytes
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private static function formatBytes($bytes)
|
||||||
|
{
|
||||||
|
if ($bytes >= 1073741824) {
|
||||||
|
return number_format($bytes / 1073741824, 2) . ' GB';
|
||||||
|
} elseif ($bytes >= 1048576) {
|
||||||
|
return number_format($bytes / 1048576, 2) . ' MB';
|
||||||
|
} elseif ($bytes >= 1024) {
|
||||||
|
return number_format($bytes / 1024, 2) . ' KB';
|
||||||
|
} elseif ($bytes > 1) {
|
||||||
|
return $bytes . ' bytes';
|
||||||
|
} elseif ($bytes == 1) {
|
||||||
|
return '1 byte';
|
||||||
|
} else {
|
||||||
|
return '0 bytes';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,9 @@
|
|||||||
<div class="link-forgot-my-password">
|
<div class="link-forgot-my-password">
|
||||||
<a href="<?php echo Config::get('URL'); ?>login/requestPasswordReset">I forgot my password</a>
|
<a href="<?php echo Config::get('URL'); ?>login/requestPasswordReset">I forgot my password</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="link-register" style="margin-top: 15px;">
|
||||||
|
<a href="<?php echo Config::get('URL'); ?>register" class="button">Register new account</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,98 +2,82 @@
|
|||||||
<h1>Messenger</h1>
|
<h1>Messenger</h1>
|
||||||
<?php $this->renderFeedbackMessages(); ?>
|
<?php $this->renderFeedbackMessages(); ?>
|
||||||
|
|
||||||
<!-- Add user ID for JavaScript -->
|
|
||||||
<meta name="user-id" content="<?= Session::get('user_id') ?>">
|
<meta name="user-id" content="<?= Session::get('user_id') ?>">
|
||||||
|
|
||||||
<script src="<?= Config::get('URL') ?>public/js/messaging.js"></script>
|
<script src="<?= Config::get('URL') ?>public/js/messaging.js"></script>
|
||||||
|
|
||||||
<div style="display: flex; gap: 20px; margin-top: 20px;">
|
<div class="messenger-container">
|
||||||
<!-- Left Sidebar -->
|
|
||||||
<div style="width: 300px;" class="messaging-sidebar">
|
<div style="width: 300px;" class="messaging-sidebar">
|
||||||
<div style="margin-bottom: 20px; padding: 15px; background: #f5f5f5; border: 1px solid #ddd;">
|
<div class="sidebar-section">
|
||||||
<h3 style="margin: 0 0 15px 0;">Channels</h3>
|
<h3>Channels</h3>
|
||||||
<div onclick="loadGlobalChat()" style="display: block; padding: 10px; margin-bottom: 8px; background: white; border: 1px solid #ddd; text-decoration: none; color: #333; cursor: pointer; transition: background-color 0.2s;" onmouseover="this.style.backgroundColor='#f0f0f0'" onmouseout="this.style.backgroundColor='white'" id="global-chat-link">
|
<div onclick="loadGlobalChat()" class="chat-item" id="global-chat-link">
|
||||||
<div style="display: flex; align-items: center; gap: 8px;">
|
<div class="chat-item-title">
|
||||||
<span style="color: #2196F3;">#</span>
|
<span class="chat-item-icon" style="color: #2196F3;">#</span>
|
||||||
<strong>Global Chat</strong>
|
<strong>Global Chat</strong>
|
||||||
</div>
|
</div>
|
||||||
<small style="color: #666; font-size: 11px;">Public chatroom</small>
|
<div class="chat-item-meta">Public chatroom</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-bottom: 20px; padding: 15px; background: #f5f5f5; border: 1px solid #ddd;">
|
<div class="sidebar-section">
|
||||||
<h3 style="margin: 0 0 15px 0;">Direct Messages</h3>
|
<h3>Direct Messages</h3>
|
||||||
<?php if (empty($this->conversations)): ?>
|
<?php if (empty($this->conversations)): ?>
|
||||||
<p style="text-align: center; padding: 20px; background: white; border: 1px solid #ddd;">No conversations yet</p>
|
<p class="chat-item" style="text-align: center; cursor: default;">No conversations yet</p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?php foreach ($this->conversations as $conv): ?>
|
<?php foreach ($this->conversations as $conv): ?>
|
||||||
<div onclick="loadConversation(<?= $conv->other_user_id ?>, '<?= htmlspecialchars(addslashes($conv->user_name)) ?>')"
|
<div onclick="loadConversation(<?= $conv->other_user_id ?>, '<?= htmlspecialchars(addslashes($conv->user_name)) ?>')"
|
||||||
style="display: block; padding: 10px; margin-bottom: 8px; background: white; border: 1px solid #ddd; text-decoration: none; color: #333; cursor: pointer; transition: background-color 0.2s;"
|
class="chat-item"
|
||||||
onmouseover="this.style.backgroundColor='#f0f0f0'"
|
|
||||||
onmouseout="this.style.backgroundColor='white'"
|
|
||||||
id="conversation-<?= $conv->other_user_id ?>">
|
id="conversation-<?= $conv->other_user_id ?>">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
<div class="chat-item-header">
|
||||||
<div style="display: flex; align-items: center; gap: 8px;">
|
<div class="chat-item-title">
|
||||||
<span style="color: #4CAF50;">@</span>
|
<span class="chat-item-icon" style="color: #4CAF50;">@</span>
|
||||||
<strong><?= htmlspecialchars($conv->user_name) ?></strong>
|
<strong><?= htmlspecialchars($conv->user_name) ?></strong>
|
||||||
</div>
|
</div>
|
||||||
<?php if ($conv->unread_count > 0): ?>
|
<?php if ($conv->unread_count > 0): ?>
|
||||||
<span style="background: #f44336; color: white; padding: 2px 6px; border-radius: 10px; font-size: 11px;">
|
<span class="unread-badge"><?= $conv->unread_count ?></span>
|
||||||
<?= $conv->unread_count ?>
|
|
||||||
</span>
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<small style="color: #666; font-size: 11px;"><?= date('M j, H:i', strtotime($conv->last_message_time)) ?></small>
|
<div class="chat-item-meta"><?= date('M j, H:i', strtotime($conv->last_message_time)) ?></div>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="padding: 15px; background: #f5f5f5; border: 1px solid #ddd;">
|
<div class="sidebar-section">
|
||||||
<h3 style="margin: 0 0 15px 0;">New Message</h3>
|
<h3>New Message</h3>
|
||||||
<form action="<?= Config::get('URL') ?>message/send" method="post" id="message-form">
|
<form action="<?= Config::get('URL') ?>message/send" method="post" id="message-form" class="new-message-form">
|
||||||
<select name="receiver_id" style="width: 100%; padding: 8px; margin-bottom: 8px; border: 1px solid #ddd;" required>
|
<select name="receiver_id" required>
|
||||||
<option value="">Select user</option>
|
<option value="">Select user</option>
|
||||||
<?php foreach ($this->all_users as $user): ?>
|
<?php foreach ($this->all_users as $user): ?>
|
||||||
<option value="<?= $user->user_id ?>"><?= htmlspecialchars($user->user_name) ?></option>
|
<option value="<?= $user->user_id ?>"><?= htmlspecialchars($user->user_name) ?></option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
<input type="text" name="subject" placeholder="Subject" style="width: 100%; padding: 8px; margin-bottom: 8px; border: 1px solid #ddd; max-width: 250px;" required>
|
<input type="hidden" name="subject" value="Direct Message">
|
||||||
<textarea name="message" placeholder="Message" style="width: 100%; padding: 8px; margin-bottom: 8px; border: 1px solid #ddd; height: 60px; resize: vertical; max-width: 250px;" required></textarea>
|
<textarea name="message" placeholder="Type your message..." required></textarea>
|
||||||
<button type="submit" class="button">Send Message</button>
|
<button type="submit" class="button" style="width: 100%;">Send Message</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Chat Area -->
|
<div style="flex: 1;" class="chat-main-area">
|
||||||
<div style="flex: 1; border: 1px solid #ddd; background: white;" class="chat-main-area">
|
<div id="chat-content">
|
||||||
<div id="chat-content" style="height: 700px; min-height: 700px; display: flex; flex-direction: column;">
|
<div id="default-state">
|
||||||
<!-- Default state -->
|
<div class="default-welcome">
|
||||||
<div id="default-state" style="flex: 1; display: flex; align-items: center; justify-content: center; background: #f9f9f9;">
|
<h3>Welcome to Messenger</h3>
|
||||||
<div style="text-align: center; padding: 60px 20px; color: #666;">
|
|
||||||
<h3 style="margin: 0 0 10px 0;">Welcome to Messenger</h3>
|
|
||||||
<p>Select Global Chat or a conversation to start messaging</p>
|
<p>Select Global Chat or a conversation to start messaging</p>
|
||||||
<small style="color: #999;">Click on any channel or conversation in the sidebar</small>
|
<small>Click on any channel or conversation in the sidebar</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Chat interface (hidden by default) -->
|
<div id="chat-interface">
|
||||||
<div id="chat-interface" style="display: none; flex: 1; flex-direction: column;">
|
<div id="chat-header">
|
||||||
<!-- Chat header -->
|
|
||||||
<div id="chat-header" style="padding: 15px; border-bottom: 1px solid #ddd; background: #f5f5f5; display: flex; justify-content: space-between; align-items: center;">
|
|
||||||
<div id="chat-title"></div>
|
<div id="chat-title"></div>
|
||||||
<div id="chat-actions"></div>
|
<div id="chat-actions"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Messages container -->
|
<div id="messages-container"></div>
|
||||||
<div id="messages-container" style="flex: 1; padding: 20px; overflow-y: auto;">
|
|
||||||
<!-- Messages will be loaded here -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Message input -->
|
<div id="message-input-area"></div>
|
||||||
<div id="message-input-area" style="padding: 15px; border-top: 1px solid #ddd; background: #f5f5f5;">
|
|
||||||
<!-- Message input will be loaded here -->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -101,40 +85,168 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.messenger-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.messaging-sidebar {
|
.messaging-sidebar {
|
||||||
height: fit-content;
|
width: 300px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #f0f2f5 100%);
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-section h3 {
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-main-area {
|
.chat-main-area {
|
||||||
height: auto;
|
flex: 1;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-content {
|
||||||
|
height: 600px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#default-state {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(180deg, #f5f7fa 0%, #fafbfc 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-interface {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-header {
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #fff 100%);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#message-input-area {
|
||||||
|
padding: 15px;
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
background: linear-gradient(180deg, #fff 0%, #f8f9fa 100%);
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#messages-container {
|
#messages-container {
|
||||||
background: #fafafa;
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px;
|
||||||
|
background: linear-gradient(180deg, #f5f7fa 0%, #fafbfc 100%);
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-item {
|
||||||
|
display: block;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item:hover {
|
||||||
|
background: #f5f8ff;
|
||||||
|
border-color: #2196F3;
|
||||||
|
transform: translateX(3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item.active {
|
||||||
|
background: #e3f2fd;
|
||||||
|
border-color: #2196F3;
|
||||||
|
box-shadow: 0 2px 4px rgba(33,150,243,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
width: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unread-badge {
|
||||||
|
background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item-meta {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #888;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.message-bubble {
|
.message-bubble {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
max-width: 70%;
|
max-width: 70%;
|
||||||
padding: 10px 15px;
|
padding: 12px 16px;
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 4px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
line-height: 1.4;
|
line-height: 1.5;
|
||||||
|
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-bubble.sent {
|
.message-bubble.sent {
|
||||||
background: #2196F3;
|
background: linear-gradient(135deg, #2196F3 0%, #1976D2 100%);
|
||||||
color: white;
|
color: white;
|
||||||
border-bottom-right-radius: 4px;
|
border-bottom-right-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-bubble.received {
|
.message-bubble.received {
|
||||||
background: white;
|
background: white;
|
||||||
color: #333;
|
color: #333;
|
||||||
border: 1px solid #e0e0e0;
|
border: 1px solid #e8e8e8;
|
||||||
border-bottom-left-radius: 4px;
|
border-bottom-left-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-time {
|
.message-time {
|
||||||
@@ -144,7 +256,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.message-row {
|
.message-row {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,11 +269,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.global-message {
|
.global-message {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 16px;
|
||||||
padding: 15px;
|
padding: 16px;
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
||||||
|
border: 1px solid #eee;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-message:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.global-message-header {
|
.global-message-header {
|
||||||
@@ -169,18 +288,116 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-message-sender {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-avatar {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.global-message-bubble {
|
.global-message-bubble {
|
||||||
color: #333;
|
color: #444;
|
||||||
line-height: 1.5;
|
line-height: 1.6;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px 18px;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 25px;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #2196F3;
|
||||||
|
box-shadow: 0 0 0 3px rgba(33,150,243,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-button {
|
||||||
|
border-radius: 25px;
|
||||||
|
padding: 12px 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-button:hover {
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-welcome {
|
||||||
|
text-align: center;
|
||||||
|
padding: 80px 20px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-welcome h3 {
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
color: #333;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-welcome p {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-welcome small {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-message-form select,
|
||||||
|
.new-message-form textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-message-form select:focus,
|
||||||
|
.new-message-form textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #2196F3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-message-form textarea {
|
||||||
|
height: 80px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.chat-main-area {
|
.messenger-container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messaging-sidebar {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-content {
|
||||||
height: 500px;
|
height: 500px;
|
||||||
margin-top: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-bubble {
|
.message-bubble {
|
||||||
@@ -193,32 +410,26 @@
|
|||||||
let currentChatType = null;
|
let currentChatType = null;
|
||||||
let currentChatId = null;
|
let currentChatId = null;
|
||||||
let messagePollingInterval = null;
|
let messagePollingInterval = null;
|
||||||
|
let isFirstLoad = true;
|
||||||
|
|
||||||
// Function to scroll messages container to bottom
|
|
||||||
function scrollToBottom() {
|
function scrollToBottom() {
|
||||||
const container = document.getElementById('messages-container');
|
const container = document.getElementById('messages-container');
|
||||||
if (container) {
|
if (container) {
|
||||||
// Small timeout to ensure content is rendered
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
container.scrollTop = container.scrollHeight;
|
container.scrollTop = container.scrollHeight;
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle DOM ready event
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Check URL hash for auto-loading chats
|
|
||||||
const hash = window.location.hash.substring(1);
|
const hash = window.location.hash.substring(1);
|
||||||
|
|
||||||
if (hash === 'load-global') {
|
if (hash === 'load-global') {
|
||||||
// Load global chat
|
|
||||||
setTimeout(() => loadGlobalChat(), 100);
|
setTimeout(() => loadGlobalChat(), 100);
|
||||||
} else if (hash.startsWith('load-conversation-')) {
|
} else if (hash.startsWith('load-conversation-')) {
|
||||||
// Load conversation
|
|
||||||
const userId = hash.split('-')[2];
|
const userId = hash.split('-')[2];
|
||||||
if (userId) {
|
if (userId) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Get user name from the conversation list
|
|
||||||
const conversationEl = document.querySelector('#conversation-' + userId);
|
const conversationEl = document.querySelector('#conversation-' + userId);
|
||||||
if (conversationEl) {
|
if (conversationEl) {
|
||||||
const userName = conversationEl.querySelector('strong').textContent;
|
const userName = conversationEl.querySelector('strong').textContent;
|
||||||
@@ -232,12 +443,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
function loadGlobalChat() {
|
function loadGlobalChat() {
|
||||||
currentChatType = 'global';
|
currentChatType = 'global';
|
||||||
currentChatId = 'global';
|
currentChatId = 'global';
|
||||||
|
isFirstLoad = true;
|
||||||
|
|
||||||
// Hide default state, show chat interface
|
|
||||||
document.getElementById('default-state').style.display = 'none';
|
document.getElementById('default-state').style.display = 'none';
|
||||||
document.getElementById('chat-interface').style.display = 'flex';
|
document.getElementById('chat-interface').style.display = 'flex';
|
||||||
|
|
||||||
// Update header
|
|
||||||
document.getElementById('chat-title').innerHTML = `
|
document.getElementById('chat-title').innerHTML = `
|
||||||
<div style="display: flex; align-items: center; gap: 8px;">
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
<span style="color: #2196F3; font-size: 18px;">#</span>
|
<span style="color: #2196F3; font-size: 18px;">#</span>
|
||||||
@@ -252,33 +462,21 @@ function loadGlobalChat() {
|
|||||||
</span>
|
</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Highlight active chat
|
|
||||||
highlightActiveChat('global');
|
highlightActiveChat('global');
|
||||||
|
|
||||||
// Load messages
|
|
||||||
loadGlobalMessages();
|
loadGlobalMessages();
|
||||||
|
setTimeout(scrollToBottom, 300);
|
||||||
// Scroll to bottom after loading
|
|
||||||
setTimeout(() => {
|
|
||||||
scrollToBottom();
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
// Setup message input
|
|
||||||
setupGlobalMessageInput();
|
setupGlobalMessageInput();
|
||||||
|
|
||||||
// Start polling for new messages
|
|
||||||
startMessagePolling();
|
startMessagePolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadConversation(userId, userName) {
|
function loadConversation(userId, userName) {
|
||||||
currentChatType = 'conversation';
|
currentChatType = 'conversation';
|
||||||
currentChatId = userId;
|
currentChatId = userId;
|
||||||
|
isFirstLoad = true;
|
||||||
|
|
||||||
// Hide default state, show chat interface
|
|
||||||
document.getElementById('default-state').style.display = 'none';
|
document.getElementById('default-state').style.display = 'none';
|
||||||
document.getElementById('chat-interface').style.display = 'flex';
|
document.getElementById('chat-interface').style.display = 'flex';
|
||||||
|
|
||||||
// Update header
|
|
||||||
document.getElementById('chat-title').innerHTML = `
|
document.getElementById('chat-title').innerHTML = `
|
||||||
<div style="display: flex; align-items: center; gap: 8px;">
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
<span style="color: #4CAF50; font-size: 18px;">@</span>
|
<span style="color: #4CAF50; font-size: 18px;">@</span>
|
||||||
@@ -293,88 +491,80 @@ function loadConversation(userId, userName) {
|
|||||||
</span>
|
</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Highlight active chat
|
|
||||||
highlightActiveChat(userId);
|
highlightActiveChat(userId);
|
||||||
|
|
||||||
// Load messages
|
|
||||||
loadConversationMessages(userId);
|
loadConversationMessages(userId);
|
||||||
|
setTimeout(scrollToBottom, 300);
|
||||||
// Scroll to bottom after loading
|
|
||||||
setTimeout(() => {
|
|
||||||
scrollToBottom();
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
// Setup message input
|
|
||||||
setupConversationMessageInput(userId);
|
setupConversationMessageInput(userId);
|
||||||
|
|
||||||
// Start polling for new messages
|
|
||||||
startMessagePolling();
|
startMessagePolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
function highlightActiveChat(chatId) {
|
function highlightActiveChat(chatId) {
|
||||||
// Remove active class from all chats
|
document.querySelectorAll('.chat-item').forEach(el => el.classList.remove('active'));
|
||||||
document.querySelectorAll('[id^="conversation-"], [id="global-chat-link"]').forEach(el => {
|
|
||||||
el.style.backgroundColor = 'white';
|
|
||||||
el.style.borderColor = '#ddd';
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add active class to current chat
|
|
||||||
const activeEl = chatId === 'global' ?
|
const activeEl = chatId === 'global' ?
|
||||||
document.getElementById('global-chat-link') :
|
document.getElementById('global-chat-link') :
|
||||||
document.getElementById('conversation-' + chatId);
|
document.getElementById('conversation-' + chatId);
|
||||||
|
|
||||||
if (activeEl) {
|
if (activeEl) activeEl.classList.add('active');
|
||||||
activeEl.style.backgroundColor = '#e8f4fd';
|
|
||||||
activeEl.style.borderColor = '#2196F3';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadGlobalMessages() {
|
function loadGlobalMessages(silent = false) {
|
||||||
const container = document.getElementById('messages-container');
|
const container = document.getElementById('messages-container');
|
||||||
|
|
||||||
|
if (isFirstLoad && !silent) {
|
||||||
container.innerHTML = '<div style="text-align: center; padding: 40px; color: #666;">Loading messages...</div>';
|
container.innerHTML = '<div style="text-align: center; padding: 40px; color: #666;">Loading messages...</div>';
|
||||||
|
}
|
||||||
|
|
||||||
fetch('<?= Config::get('URL') ?>message/getGlobalMessages')
|
fetch('<?= Config::get('URL') ?>message/getGlobalMessages')
|
||||||
.then(response => {
|
.then(response => {
|
||||||
console.log('Global messages response status:', response.status);
|
if (!response.ok) throw new Error('HTTP error: ' + response.status);
|
||||||
console.log('Global messages response headers:', response.headers);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return response.text().then(text => {
|
|
||||||
console.error('Server returned non-JSON response for global messages:', text);
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}, response: ${text.substring(0, 200)}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success && data.messages) {
|
if (data.success && data.messages) {
|
||||||
|
const wasAtBottom = container.scrollHeight - container.scrollTop <= container.clientHeight + 50;
|
||||||
displayGlobalMessages(data.messages);
|
displayGlobalMessages(data.messages);
|
||||||
} else {
|
if (wasAtBottom) scrollToBottom();
|
||||||
container.innerHTML = '<div style="text-align: center; padding: 40px; color: #f44336;">Error loading messages: ' + (data.message || 'Unknown error') + '</div>';
|
isFirstLoad = false;
|
||||||
|
} else if (isFirstLoad) {
|
||||||
|
container.innerHTML = '<div style="text-align: center; padding: 40px; color: #f44336;">Error loading messages</div>';
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error loading global messages:', error);
|
console.error('Error loading global messages:', error);
|
||||||
container.innerHTML = '<div style="text-align: center; padding: 40px; color: #f44336;">Network error: ' + error.message + '</div>';
|
if (isFirstLoad) {
|
||||||
|
container.innerHTML = '<div style="text-align: center; padding: 40px; color: #f44336;">Network error</div>';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadConversationMessages(userId) {
|
function loadConversationMessages(userId, silent = false) {
|
||||||
const container = document.getElementById('messages-container');
|
const container = document.getElementById('messages-container');
|
||||||
|
|
||||||
|
if (isFirstLoad && !silent) {
|
||||||
container.innerHTML = '<div style="text-align: center; padding: 40px; color: #666;">Loading messages...</div>';
|
container.innerHTML = '<div style="text-align: center; padding: 40px; color: #666;">Loading messages...</div>';
|
||||||
|
}
|
||||||
|
|
||||||
fetch(`<?= Config::get('URL') ?>message/getConversationMessages/${userId}`)
|
fetch(`<?= Config::get('URL') ?>message/getConversationMessages/${userId}`)
|
||||||
.then(response => response.json())
|
.then(response => {
|
||||||
|
if (!response.ok) throw new Error('HTTP error: ' + response.status);
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success && data.messages) {
|
if (data.success && data.messages) {
|
||||||
|
const wasAtBottom = container.scrollHeight - container.scrollTop <= container.clientHeight + 50;
|
||||||
displayConversationMessages(data.messages);
|
displayConversationMessages(data.messages);
|
||||||
} else {
|
if (wasAtBottom) scrollToBottom();
|
||||||
|
isFirstLoad = false;
|
||||||
|
} else if (isFirstLoad) {
|
||||||
container.innerHTML = '<div style="text-align: center; padding: 40px; color: #f44336;">Error loading messages</div>';
|
container.innerHTML = '<div style="text-align: center; padding: 40px; color: #f44336;">Error loading messages</div>';
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error loading conversation messages:', error);
|
console.error('Error loading conversation messages:', error);
|
||||||
|
if (isFirstLoad) {
|
||||||
container.innerHTML = '<div style="text-align: center; padding: 40px; color: #f44336;">Network error</div>';
|
container.innerHTML = '<div style="text-align: center; padding: 40px; color: #f44336;">Network error</div>';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,9 +573,9 @@ function displayGlobalMessages(messages) {
|
|||||||
|
|
||||||
if (messages.length === 0) {
|
if (messages.length === 0) {
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div style="text-align: center; padding: 60px 20px;">
|
<div class="default-welcome">
|
||||||
<h4 style="color: #666;">No messages yet</h4>
|
<h3>No messages yet</h3>
|
||||||
<p style="color: #999;">Start the conversation!</p>
|
<p>Start the conversation!</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
return;
|
return;
|
||||||
@@ -400,15 +590,17 @@ function displayGlobalMessages(messages) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const isOwn = message.sender_id == document.querySelector('meta[name="user-id"]').content;
|
const isOwn = message.sender_id == document.querySelector('meta[name="user-id"]').content;
|
||||||
|
const initial = message.sender_name.charAt(0).toUpperCase();
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="global-message">
|
<div class="global-message">
|
||||||
<div class="global-message-header">
|
<div class="global-message-header">
|
||||||
<div style="display: flex; align-items: center; gap: 8px;">
|
<div class="global-message-sender">
|
||||||
|
<div class="sender-avatar" style="${isOwn ? 'background: linear-gradient(135deg, #2196F3 0%, #1976D2 100%);' : ''}">${initial}</div>
|
||||||
<strong style="color: ${isOwn ? '#2196F3' : '#333'}">
|
<strong style="color: ${isOwn ? '#2196F3' : '#333'}">
|
||||||
${escapeHtml(message.sender_name)}
|
${escapeHtml(message.sender_name)}
|
||||||
</strong>
|
</strong>
|
||||||
${isOwn ? '<span style="font-size: 10px; background: #2196F3; color: white; padding: 2px 4px; border-radius: 3px;">You</span>' : ''}
|
${isOwn ? '<span style="font-size: 10px; background: #2196F3; color: white; padding: 2px 6px; border-radius: 4px;">You</span>' : ''}
|
||||||
</div>
|
</div>
|
||||||
<span class="message-time">${timeString}</span>
|
<span class="message-time">${timeString}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -419,7 +611,6 @@ function displayGlobalMessages(messages) {
|
|||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
// Scroll to bottom
|
|
||||||
container.scrollTop = container.scrollHeight;
|
container.scrollTop = container.scrollHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,9 +620,9 @@ function displayConversationMessages(messages) {
|
|||||||
|
|
||||||
if (messages.length === 0) {
|
if (messages.length === 0) {
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div style="text-align: center; padding: 60px 20px;">
|
<div class="default-welcome">
|
||||||
<h4 style="color: #666;">No messages yet</h4>
|
<h3>No messages yet</h3>
|
||||||
<p style="color: #999;">Send a message to start the conversation!</p>
|
<p>Send a message to start the conversation!</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
return;
|
return;
|
||||||
@@ -460,7 +651,6 @@ function displayConversationMessages(messages) {
|
|||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
// Scroll to bottom
|
|
||||||
container.scrollTop = container.scrollHeight;
|
container.scrollTop = container.scrollHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,9 +662,9 @@ function setupGlobalMessageInput() {
|
|||||||
<input type="text"
|
<input type="text"
|
||||||
name="message"
|
name="message"
|
||||||
placeholder="Type your global message..."
|
placeholder="Type your global message..."
|
||||||
style="flex: 1; padding: 12px; border: 1px solid #ddd; border-radius: 25px;"
|
class="message-input"
|
||||||
required>
|
required>
|
||||||
<button type="submit" class="button" style="border-radius: 25px; padding: 12px 20px;">Send</button>
|
<button type="submit" class="button send-button">Send</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
`;
|
`;
|
||||||
@@ -488,9 +678,9 @@ function setupConversationMessageInput(userId) {
|
|||||||
<input type="text"
|
<input type="text"
|
||||||
name="message"
|
name="message"
|
||||||
placeholder="Type your message..."
|
placeholder="Type your message..."
|
||||||
style="flex: 1; padding: 12px; border: 1px solid #ddd; border-radius: 25px;"
|
class="message-input"
|
||||||
required>
|
required>
|
||||||
<button type="submit" class="button" style="border-radius: 25px; padding: 12px 20px;">Send</button>
|
<button type="submit" class="button send-button">Send</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
`;
|
`;
|
||||||
@@ -515,32 +705,20 @@ function sendGlobalMessage(event) {
|
|||||||
body: 'message=' + encodeURIComponent(message)
|
body: 'message=' + encodeURIComponent(message)
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
console.log('Global message response status:', response.status);
|
if (!response.ok) throw new Error('HTTP error: ' + response.status);
|
||||||
console.log('Global message response headers:', response.headers);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return response.text().then(text => {
|
|
||||||
console.error('Server returned non-JSON response for global message:', text);
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}, response: ${text.substring(0, 200)}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
form.reset();
|
form.reset();
|
||||||
loadGlobalMessages(); // Refresh messages
|
loadGlobalMessages(true);
|
||||||
// Scroll to bottom after sending
|
setTimeout(scrollToBottom, 300);
|
||||||
setTimeout(() => {
|
|
||||||
scrollToBottom();
|
|
||||||
}, 500);
|
|
||||||
} else {
|
} else {
|
||||||
alert('Failed to send message: ' + (data.message || 'Unknown error'));
|
alert('Failed to send message: ' + (data.message || 'Unknown error'));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error sending global message:', error);
|
alert('Network error. Please try again.');
|
||||||
alert('Network error. Please try again. (' + error.message + ')');
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
submitButton.textContent = originalText;
|
submitButton.textContent = originalText;
|
||||||
@@ -567,32 +745,20 @@ function sendConversationMessage(event, userId) {
|
|||||||
body: 'receiver_id=' + userId + '&message=' + encodeURIComponent(message)
|
body: 'receiver_id=' + userId + '&message=' + encodeURIComponent(message)
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
console.log('Response status:', response.status);
|
if (!response.ok) throw new Error('HTTP error: ' + response.status);
|
||||||
console.log('Response headers:', response.headers);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
return response.text().then(text => {
|
|
||||||
console.error('Server returned non-JSON response:', text);
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}, response: ${text.substring(0, 200)}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
form.reset();
|
form.reset();
|
||||||
loadConversationMessages(userId); // Refresh messages
|
loadConversationMessages(userId, true);
|
||||||
// Scroll to bottom after sending
|
setTimeout(scrollToBottom, 300);
|
||||||
setTimeout(() => {
|
|
||||||
scrollToBottom();
|
|
||||||
}, 500);
|
|
||||||
} else {
|
} else {
|
||||||
alert('Failed to send message: ' + (data.message || 'Unknown error'));
|
alert('Failed to send message: ' + (data.message || 'Unknown error'));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error sending conversation message:', error);
|
alert('Network error. Please try again.');
|
||||||
alert('Network error. Please try again. (' + error.message + ')');
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
submitButton.textContent = originalText;
|
submitButton.textContent = originalText;
|
||||||
@@ -601,19 +767,15 @@ function sendConversationMessage(event, userId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function startMessagePolling() {
|
function startMessagePolling() {
|
||||||
// Clear existing polling
|
if (messagePollingInterval) clearInterval(messagePollingInterval);
|
||||||
if (messagePollingInterval) {
|
|
||||||
clearInterval(messagePollingInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start new polling
|
|
||||||
messagePollingInterval = setInterval(() => {
|
messagePollingInterval = setInterval(() => {
|
||||||
if (currentChatType === 'global') {
|
if (currentChatType === 'global') {
|
||||||
loadGlobalMessages();
|
loadGlobalMessages(true);
|
||||||
} else if (currentChatType === 'conversation') {
|
} else if (currentChatType === 'conversation') {
|
||||||
loadConversationMessages(currentChatId);
|
loadConversationMessages(currentChatId, true);
|
||||||
}
|
}
|
||||||
}, 3000); // Poll every 3 seconds
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
@@ -622,7 +784,6 @@ function escapeHtml(text) {
|
|||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle new message form submission
|
|
||||||
document.getElementById('message-form').addEventListener('submit', function(e) {
|
document.getElementById('message-form').addEventListener('submit', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@@ -642,13 +803,11 @@ document.getElementById('message-form').addEventListener('submit', function(e) {
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
this.reset();
|
this.reset();
|
||||||
showFeedback('Message sent successfully!', 'success');
|
showFeedback('Message sent successfully!', 'success');
|
||||||
// Could refresh conversation list here
|
|
||||||
} else {
|
} else {
|
||||||
showFeedback(data.message || 'Failed to send message', 'error');
|
showFeedback(data.message || 'Failed to send message', 'error');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error sending message:', error);
|
|
||||||
showFeedback('Network error. Please try again.', 'error');
|
showFeedback('Network error. Please try again.', 'error');
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -659,7 +818,6 @@ document.getElementById('message-form').addEventListener('submit', function(e) {
|
|||||||
|
|
||||||
function showFeedback(message, type) {
|
function showFeedback(message, type) {
|
||||||
const feedback = document.createElement('div');
|
const feedback = document.createElement('div');
|
||||||
feedback.className = `feedback-${type}`;
|
|
||||||
feedback.textContent = message;
|
feedback.textContent = message;
|
||||||
feedback.style.cssText = `
|
feedback.style.cssText = `
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -682,14 +840,12 @@ function showFeedback(message, type) {
|
|||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add CSS for animations
|
|
||||||
const animationStyle = document.createElement('style');
|
const animationStyle = document.createElement('style');
|
||||||
animationStyle.textContent = `
|
animationStyle.textContent = `
|
||||||
@keyframes slideIn {
|
@keyframes slideIn {
|
||||||
from { transform: translateX(100%); opacity: 0; }
|
from { transform: translateX(100%); opacity: 0; }
|
||||||
to { transform: translateX(0); opacity: 1; }
|
to { transform: translateX(0); opacity: 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slideOut {
|
@keyframes slideOut {
|
||||||
from { transform: translateX(0); opacity: 1; }
|
from { transform: translateX(0); opacity: 1; }
|
||||||
to { transform: translateX(100%); opacity: 0; }
|
to { transform: translateX(100%); opacity: 0; }
|
||||||
|
|||||||
@@ -1,18 +1,30 @@
|
|||||||
<div class="container">
|
<script src="https://www.google.com/recaptcha/api.js?render=<?php echo Config::get('RECAPTCHA_SITE_KEY'); ?>"></script>
|
||||||
|
|
||||||
<!-- echo out the system feedback (error and success messages) -->
|
<div class="container">
|
||||||
<?php $this->renderFeedbackMessages(); ?>
|
<?php $this->renderFeedbackMessages(); ?>
|
||||||
|
|
||||||
<!-- login box on left side -->
|
|
||||||
<div class="login-box" style="width: 50%; display: block;">
|
<div class="login-box" style="width: 50%; display: block;">
|
||||||
<h2>Register a new account</h2>
|
<h2>Register a new account</h2>
|
||||||
|
|
||||||
<!-- register form -->
|
<form method="post" action="<?php echo Config::get('URL'); ?>register/register_action" id="register-form">
|
||||||
<form method="post" action="<?php echo Config::get('URL'); ?>register/register_action">
|
|
||||||
<input type="text" pattern="[a-zA-Z0-9]{2,64}" name="user_name" placeholder="Username (letters/numbers, 2-64 chars)" required />
|
<input type="text" pattern="[a-zA-Z0-9]{2,64}" name="user_name" placeholder="Username (letters/numbers, 2-64 chars)" required />
|
||||||
<input type="text" name="user_email" placeholder="email address (a real address)" required />
|
<input type="text" name="user_email" placeholder="email address (a real address)" required />
|
||||||
<input type="password" name="user_password_new" pattern=".{6,}" placeholder="Password (6+ characters)" required autocomplete="off" />
|
<input type="password" name="user_password_new" pattern=".{6,}" placeholder="Password (6+ characters)" required autocomplete="off" />
|
||||||
|
<input type="hidden" name="g-recaptcha-response" id="recaptcha-response" />
|
||||||
<input type="submit" value="Register" />
|
<input type="submit" value="Register" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('register-form').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var form = this;
|
||||||
|
grecaptcha.ready(function() {
|
||||||
|
grecaptcha.execute('<?php echo Config::get('RECAPTCHA_SITE_KEY'); ?>', {action: 'register'}).then(function(token) {
|
||||||
|
document.getElementById('recaptcha-response').value = token;
|
||||||
|
form.submit();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user