Compare commits
No commits in common. "d318b621d51861baaceed70101fc2b7cce8160b2" and "0b3ff9e40b74ed057a83e31fe07d9ddefe1c2856" have entirely different histories.
d318b621d5
...
0b3ff9e40b
|
|
@ -163,43 +163,34 @@ class PluginManager
|
||||||
public static function setEnabled(string $plugin, bool $enabled): bool
|
public static function setEnabled(string $plugin, bool $enabled): bool
|
||||||
{
|
{
|
||||||
if (!isset(self::$catalog[$plugin])) {
|
if (!isset(self::$catalog[$plugin])) {
|
||||||
app_log('error', 'PluginManager::setEnabled: Plugin ' . $plugin . ' not found in catalog', ['scope' => 'plugin']);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use global DB and get PDO connection
|
global $db;
|
||||||
$db = $GLOBALS['db'];
|
if (!$db instanceof PDO) {
|
||||||
$pdo = $db->getConnection();
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Update or insert plugin setting in database
|
// Update or insert plugin setting in database
|
||||||
$stmt = $pdo->prepare(
|
$stmt = $db->prepare(
|
||||||
'INSERT INTO settings (`key`, `value`, updated_at)
|
'INSERT INTO settings (`key`, `value`, updated_at)
|
||||||
VALUES (:key, :value, NOW())
|
VALUES (:key, :value, NOW())
|
||||||
ON DUPLICATE KEY UPDATE `value` = :value, updated_at = NOW()'
|
ON DUPLICATE KEY UPDATE `value` = :value, updated_at = NOW()'
|
||||||
);
|
);
|
||||||
$key = 'plugin_enabled_' . $plugin;
|
$key = 'plugin_enabled_' . $plugin;
|
||||||
$value = $enabled ? '1' : '0';
|
$value = $enabled ? '1' : '0';
|
||||||
|
$stmt->execute([':key' => $key, ':value' => $value]);
|
||||||
app_log('info', 'PluginManager::setEnabled: Setting ' . $key . ' to ' . $value, ['scope' => 'plugin']);
|
|
||||||
|
|
||||||
$result = $stmt->execute([':key' => $key, ':value' => $value]);
|
|
||||||
|
|
||||||
if (!$result) {
|
|
||||||
app_log('error', 'PluginManager::setEnabled: Failed to execute query for ' . $plugin, ['scope' => 'plugin']);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear loaded cache if disabling
|
// Clear loaded cache if disabling
|
||||||
if (!$enabled && isset(self::$loaded[$plugin])) {
|
if (!$enabled && isset(self::$loaded[$plugin])) {
|
||||||
unset(self::$loaded[$plugin]);
|
unset(self::$loaded[$plugin]);
|
||||||
}
|
}
|
||||||
|
|
||||||
app_log('info', 'PluginManager::setEnabled: Successfully set ' . $plugin . ' to ' . ($enabled ? 'enabled' : 'disabled'), ['scope' => 'plugin']);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (\PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
// Log the actual error for debugging
|
// Log the actual error for debugging
|
||||||
app_log('error', 'PluginManager::setEnabled failed for ' . $plugin . ': ' . $e->getMessage(), ['scope' => 'plugin']);
|
error_log('PluginManager::setEnabled failed for ' . $plugin . ': ' . $e->getMessage());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -213,21 +204,25 @@ class PluginManager
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use global DB and get PDO connection
|
global $db;
|
||||||
$db = $GLOBALS['db'];
|
if ($db instanceof PDO) {
|
||||||
$pdo = $db->getConnection();
|
try {
|
||||||
|
$stmt = $db->prepare('SELECT `value` FROM settings WHERE `key` = :key LIMIT 1');
|
||||||
try {
|
$key = 'plugin_enabled_' . $plugin;
|
||||||
$stmt = $pdo->prepare('SELECT `value` FROM settings WHERE `key` = :key LIMIT 1');
|
$stmt->execute([':key' => $key]);
|
||||||
$key = 'plugin_enabled_' . $plugin;
|
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
$stmt->execute([':key' => $key]);
|
|
||||||
$result = $stmt->fetch(\PDO::FETCH_ASSOC);
|
if ($result !== false) {
|
||||||
|
return $result['value'] === '1';
|
||||||
return $result && $result['value'] === '1';
|
}
|
||||||
} catch (\PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
app_log('error', 'PluginManager::isEnabled failed for ' . $plugin . ': ' . $e->getMessage(), ['scope' => 'plugin']);
|
// Log error but return false
|
||||||
return false;
|
error_log('PluginManager::isEnabled database error for ' . $plugin . ': ' . $e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default to disabled if no database entry or database unavailable
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -241,7 +236,7 @@ class PluginManager
|
||||||
|
|
||||||
$pluginPath = self::$catalog[$plugin]['path'];
|
$pluginPath = self::$catalog[$plugin]['path'];
|
||||||
$bootstrapPath = $pluginPath . '/bootstrap.php';
|
$bootstrapPath = $pluginPath . '/bootstrap.php';
|
||||||
|
|
||||||
if (!file_exists($bootstrapPath)) {
|
if (!file_exists($bootstrapPath)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -249,20 +244,16 @@ class PluginManager
|
||||||
try {
|
try {
|
||||||
// Include bootstrap to run migrations
|
// Include bootstrap to run migrations
|
||||||
include_once $bootstrapPath;
|
include_once $bootstrapPath;
|
||||||
|
|
||||||
// Look for migration function
|
// Look for migration function
|
||||||
$migrationFunction = str_replace('-', '_', $plugin) . '_ensure_tables';
|
$migrationFunction = str_replace('-', '_', $plugin) . '_ensure_tables';
|
||||||
if (function_exists($migrationFunction)) {
|
if (function_exists($migrationFunction)) {
|
||||||
$migrationFunction();
|
$migrationFunction();
|
||||||
app_log('info', 'PluginManager::install: Successfully ran migrations for ' . $plugin, ['scope' => 'plugin']);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no migration function exists, that's okay for plugins that don't need tables
|
return false;
|
||||||
app_log('info', 'PluginManager::install: No migrations needed for ' . $plugin, ['scope' => 'plugin']);
|
|
||||||
return true;
|
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
app_log('error', 'PluginManager::install failed for ' . $plugin . ': ' . $e->getMessage(), ['scope' => 'plugin']);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -278,23 +269,22 @@ class PluginManager
|
||||||
|
|
||||||
global $db;
|
global $db;
|
||||||
if (!$db instanceof PDO) {
|
if (!$db instanceof PDO) {
|
||||||
app_log('error', 'PluginManager::purge: Database connection not available', ['scope' => 'plugin']);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// First disable the plugin
|
// First disable the plugin
|
||||||
self::setEnabled($plugin, false);
|
self::setEnabled($plugin, false);
|
||||||
|
|
||||||
// Remove plugin settings
|
// Remove plugin settings
|
||||||
$stmt = $db->prepare('DELETE FROM settings WHERE `key` LIKE :pattern');
|
$stmt = $db->prepare('DELETE FROM settings WHERE `key` LIKE :pattern');
|
||||||
$stmt->execute([':pattern' => 'plugin_enabled_' . $plugin]);
|
$stmt->execute([':pattern' => 'plugin_enabled_' . $plugin]);
|
||||||
|
|
||||||
// Drop plugin-specific tables (user_pro_* tables for this plugin)
|
// Drop plugin-specific tables (user_pro_* tables for this plugin)
|
||||||
$stmt = $db->prepare('SHOW TABLES LIKE "user_pro_%"');
|
$stmt = $db->prepare('SHOW TABLES LIKE "user_pro_%"');
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
|
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||||
|
|
||||||
foreach ($tables as $table) {
|
foreach ($tables as $table) {
|
||||||
// Check if this table belongs to the plugin by checking its migration file
|
// Check if this table belongs to the plugin by checking its migration file
|
||||||
$migrationFile = self::$catalog[$plugin]['path'] . '/migrations/create_' . $plugin . '_tables.sql';
|
$migrationFile = self::$catalog[$plugin]['path'] . '/migrations/create_' . $plugin . '_tables.sql';
|
||||||
|
|
@ -302,15 +292,12 @@ class PluginManager
|
||||||
$migrationContent = file_get_contents($migrationFile);
|
$migrationContent = file_get_contents($migrationFile);
|
||||||
if (strpos($migrationContent, $table) !== false) {
|
if (strpos($migrationContent, $table) !== false) {
|
||||||
$db->exec("DROP TABLE IF EXISTS `$table`");
|
$db->exec("DROP TABLE IF EXISTS `$table`");
|
||||||
app_log('info', 'PluginManager::purge: Dropped table ' . $table . ' for plugin ' . $plugin, ['scope' => 'plugin']);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app_log('info', 'PluginManager::purge: Successfully purged plugin ' . $plugin, ['scope' => 'plugin']);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
app_log('error', 'PluginManager::purge failed for ' . $plugin . ': ' . $e->getMessage(), ['scope' => 'plugin']);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -489,6 +489,22 @@ if (!empty($adminOverviewStatuses) && is_array($adminOverviewStatuses)) {
|
||||||
</span>
|
</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</form>
|
</form>
|
||||||
|
<form method="post" class="d-inline">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
|
||||||
|
<input type="hidden" name="section" value="plugins">
|
||||||
|
<input type="hidden" name="plugin" value="<?= htmlspecialchars($plugin['slug']) ?>">
|
||||||
|
<input type="hidden" name="action" value="plugin_purge">
|
||||||
|
<?php if ($plugin['can_disable']): ?>
|
||||||
|
<span data-toggle="tooltip" data-placement="top" title="Remove all plugin data and tables">
|
||||||
|
<button type="submit" class="btn btn-sm btn-outline-warning" onclick="return confirm('Are you sure? This will permanently delete all plugin data and tables!')">Purge</button>
|
||||||
|
</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span data-toggle="tooltip" data-placement="top"
|
||||||
|
title="<?= htmlspecialchars('Cannot purge: ' . (count($plugin['enabled_dependents']) > 0 ? 'Required by: ' . implode(', ', $plugin['enabled_dependents']) : 'Plugin has active dependents')) ?>">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-warning" disabled>Purge</button>
|
||||||
|
</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</form>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<form method="post" class="d-inline">
|
<form method="post" class="d-inline">
|
||||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
|
||||||
|
|
@ -506,9 +522,32 @@ if (!empty($adminOverviewStatuses) && is_array($adminOverviewStatuses)) {
|
||||||
</span>
|
</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</form>
|
</form>
|
||||||
|
<?php if (file_exists($pluginAdminMap[$plugin['slug']]['path'] . '/bootstrap.php')): ?>
|
||||||
|
<?php if ($plugin['can_install']): ?>
|
||||||
|
<form method="post" class="d-inline">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
|
||||||
|
<input type="hidden" name="section" value="plugins">
|
||||||
|
<input type="hidden" name="plugin" value="<?= htmlspecialchars($plugin['slug']) ?>">
|
||||||
|
<input type="hidden" name="action" value="plugin_install">
|
||||||
|
<span data-toggle="tooltip" data-placement="top" title="Install plugin database tables">
|
||||||
|
<button type="submit" class="btn btn-sm btn-outline-primary">Install</button>
|
||||||
|
</span>
|
||||||
|
</form>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php if ($plugin['has_migration']): ?>
|
||||||
|
<span data-toggle="tooltip" data-placement="top" title="Tables already exist">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-primary" disabled>Install</button>
|
||||||
|
</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span data-toggle="tooltip" data-placement="top" title="No migration files found">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-primary" disabled>Install</button>
|
||||||
|
</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if (file_exists($pluginAdminMap[$plugin['slug']]['path'] . '/bootstrap.php')): ?>
|
<?php if (file_exists($pluginAdminMap[$plugin['slug']]['path'] . '/bootstrap.php')): ?>
|
||||||
<span data-toggle="tooltip" data-placement="top" title="Check plugin health and status" style="margin-left: 0.5rem;">
|
<span data-toggle="tooltip" data-placement="top" title="Check plugin health and status">
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-toggle="modal" data-target="#pluginCheckModal<?= htmlspecialchars($plugin['slug']) ?>">Check</button>
|
<button type="button" class="btn btn-sm btn-outline-secondary" data-toggle="modal" data-target="#pluginCheckModal<?= htmlspecialchars($plugin['slug']) ?>">Check</button>
|
||||||
</span>
|
</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
@ -612,27 +651,25 @@ endif; ?>
|
||||||
<?php
|
<?php
|
||||||
$modalId = 'pluginCheckModal' . htmlspecialchars($plugin['slug']);
|
$modalId = 'pluginCheckModal' . htmlspecialchars($plugin['slug']);
|
||||||
$checkResults = [];
|
$checkResults = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check plugin files exist
|
// Check plugin files exist
|
||||||
$migrationFiles = glob($plugin['path'] . '/migrations/*.sql');
|
$migrationFiles = glob($plugin['path'] . '/migrations/*.sql');
|
||||||
$hasMigration = !empty($migrationFiles);
|
$hasMigration = !empty($migrationFiles);
|
||||||
|
|
||||||
$checkResults['files'] = [
|
$checkResults['files'] = [
|
||||||
'manifest' => file_exists($plugin['path'] . '/plugin.json'),
|
'manifest' => file_exists($plugin['path'] . '/plugin.json'),
|
||||||
'bootstrap' => file_exists($plugin['path'] . '/bootstrap.php'),
|
'bootstrap' => file_exists($plugin['path'] . '/bootstrap.php'),
|
||||||
'helpers' => file_exists($plugin['path'] . '/helpers.php'),
|
|
||||||
'controllers' => is_dir($plugin['path'] . '/controllers') && count(glob($plugin['path'] . '/controllers/*.php')) > 0,
|
|
||||||
'migration' => $hasMigration,
|
'migration' => $hasMigration,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Check database tables
|
// Check database tables
|
||||||
$db = \App\App::db();
|
$db = \App\App::db();
|
||||||
$pluginTables = [];
|
$pluginTables = [];
|
||||||
if ($db instanceof PDO) {
|
if ($db instanceof PDO) {
|
||||||
$stmt = $db->query("SHOW TABLES");
|
$stmt = $db->query("SHOW TABLES");
|
||||||
$allTables = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
|
$allTables = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||||
|
|
||||||
if ($hasMigration) {
|
if ($hasMigration) {
|
||||||
// Check each migration file for table references
|
// Check each migration file for table references
|
||||||
foreach ($migrationFiles as $migrationFile) {
|
foreach ($migrationFiles as $migrationFile) {
|
||||||
|
|
@ -647,20 +684,20 @@ endif; ?>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$checkResults['tables'] = $pluginTables;
|
$checkResults['tables'] = $pluginTables;
|
||||||
|
|
||||||
// Check plugin functions and integrations
|
// Check plugin functions
|
||||||
$bootstrapPath = $plugin['path'] . '/bootstrap.php';
|
$bootstrapPath = $plugin['path'] . '/bootstrap.php';
|
||||||
if (file_exists($bootstrapPath)) {
|
if (file_exists($bootstrapPath)) {
|
||||||
include_once $bootstrapPath;
|
include_once $bootstrapPath;
|
||||||
$migrationFunction = str_replace('-', '_', $plugin['slug']) . '_ensure_tables';
|
$migrationFunction = str_replace('-', '_', $plugin['slug']) . '_ensure_tables';
|
||||||
$migrationTestResult = null;
|
$migrationTestResult = null;
|
||||||
|
|
||||||
// Test migration function if it exists
|
// Test migration function if it exists
|
||||||
if (function_exists($migrationFunction)) {
|
if (function_exists($migrationFunction)) {
|
||||||
try {
|
try {
|
||||||
// Check if plugin tables already exist
|
// Check if plugin tables already exist
|
||||||
$tablesExist = !empty($pluginTables);
|
$tablesExist = !empty($pluginTables);
|
||||||
|
|
||||||
if ($tablesExist) {
|
if ($tablesExist) {
|
||||||
$migrationTestResult = 'already installed';
|
$migrationTestResult = 'already installed';
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -673,19 +710,13 @@ endif; ?>
|
||||||
} else {
|
} else {
|
||||||
$migrationTestResult = 'not applicable';
|
$migrationTestResult = 'not applicable';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check route and hook registrations
|
|
||||||
$routePrefix = $plugin['slug'];
|
|
||||||
$hasRouteRegistration = isset($GLOBALS['plugin_route_prefixes']) && isset($GLOBALS['plugin_route_prefixes'][$routePrefix]);
|
|
||||||
|
|
||||||
$checkResults['functions'] = [
|
$checkResults['functions'] = [
|
||||||
'migration' => function_exists($migrationFunction),
|
'migration' => function_exists($migrationFunction),
|
||||||
'migration_test' => $migrationTestResult ?: 'not applicable',
|
'migration_test' => $migrationTestResult ?: 'not applicable',
|
||||||
'route_registration' => $hasRouteRegistration,
|
|
||||||
'hook_registration' => true, // If bootstrap loaded, assume hooks are registered
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$checkResults['error'] = $e->getMessage();
|
$checkResults['error'] = $e->getMessage();
|
||||||
}
|
}
|
||||||
|
|
@ -699,99 +730,6 @@ endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h6 class="card-title mb-0">Plugin Information</h6>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="small">
|
|
||||||
<div class="mb-1"><strong>Name:</strong> <?= htmlspecialchars($plugin['name']) ?></div>
|
|
||||||
<div class="mb-1"><strong>Version:</strong> <?= htmlspecialchars($plugin['version'] ?? 'N/A') ?></div>
|
|
||||||
<div class="mb-1"><strong>Enabled:</strong> <span class="badge bg-<?= $plugin['enabled'] ? 'success' : 'secondary' ?>"><?= $plugin['enabled'] ? 'Yes' : 'No' ?></span></div>
|
|
||||||
<div class="mb-1"><strong>Dependencies:</strong> <?= !empty($plugin['dependencies']) ? htmlspecialchars(implode(', ', $plugin['dependencies'])) : 'None' ?></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h6 class="card-title mb-0">Functions Check</h6>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<?php foreach ($checkResults['functions'] ?? [] as $func => $value): ?>
|
|
||||||
<?php if ($func === 'migration_test'): ?>
|
|
||||||
<?php if ($value !== 'not applicable'): ?>
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
||||||
<span>Migration Test</span>
|
|
||||||
<?php if ($value === 'already installed'): ?>
|
|
||||||
<span class="badge bg-info">Already Installed</span>
|
|
||||||
<?php elseif (strpos($value, 'error') === false): ?>
|
|
||||||
<span class="badge bg-success">Passed</span>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="badge bg-danger">Failed</span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ($value === 'already installed'): ?>
|
|
||||||
<div class="text-muted small mb-2">Plugin tables already exist - migration not needed</div>
|
|
||||||
<?php elseif (strpos($value, 'error') !== false): ?>
|
|
||||||
<div class="text-muted small mb-2"><?= htmlspecialchars($value) ?></div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php elseif ($func === 'route_registration'): ?>
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
||||||
<span>Route Registration</span>
|
|
||||||
<span class="badge bg-<?= $value ? 'success' : 'danger' ?>">
|
|
||||||
<?= $value ? 'Registered' : 'Not Registered' ?>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<?php elseif ($func === 'hook_registration'): ?>
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
||||||
<span>Hook Registration</span>
|
|
||||||
<span class="badge bg-<?= $value ? 'success' : 'danger' ?>">
|
|
||||||
<?= $value ? 'Active' : 'Inactive' ?>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<?php elseif ($func === 'migration'): ?>
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
||||||
<span><?= htmlspecialchars(ucfirst($func)) ?> Function</span>
|
|
||||||
<span class="badge bg-<?= $value ? 'success' : 'danger' ?>">
|
|
||||||
<?= $value ? 'Available' : 'Missing' ?>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-3">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h6 class="card-title mb-0">Database Tables</h6>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<?php if (!empty($checkResults['tables'])): ?>
|
|
||||||
<?php foreach ($checkResults['tables'] as $table): ?>
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
||||||
<span><?= htmlspecialchars($table) ?></span>
|
|
||||||
<span class="badge bg-success">Present</span>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php else: ?>
|
|
||||||
<p class="text-muted mb-0">
|
|
||||||
<?php if ($checkResults['files']['migration']): ?>
|
|
||||||
Plugin has migration files but tables are not installed yet.
|
|
||||||
<?php else: ?>
|
|
||||||
No plugin tables found.
|
|
||||||
<?php endif; ?>
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
|
|
@ -809,6 +747,79 @@ endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h6 class="card-title mb-0">Database Tables</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (!empty($checkResults['tables'])): ?>
|
||||||
|
<?php foreach ($checkResults['tables'] as $table): ?>
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<span><?= htmlspecialchars($table) ?></span>
|
||||||
|
<span class="badge bg-success">Present</span>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="text-muted mb-0">No plugin tables found.</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h6 class="card-title mb-0">Functions Check</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php foreach ($checkResults['functions'] ?? [] as $func => $value): ?>
|
||||||
|
<?php if ($func === 'migration_test'): ?>
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<span>Migration Test</span>
|
||||||
|
<?php if ($value === 'not applicable'): ?>
|
||||||
|
<span class="badge bg-secondary">Not Applicable</span>
|
||||||
|
<?php elseif ($value === 'already installed'): ?>
|
||||||
|
<span class="badge bg-info">Already Installed</span>
|
||||||
|
<?php elseif (strpos($value, 'error') === false): ?>
|
||||||
|
<span class="badge bg-success">Passed</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-danger">Failed</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php if ($value === 'already installed'): ?>
|
||||||
|
<div class="text-muted small mb-2">Plugin tables already exist - migration not needed</div>
|
||||||
|
<?php elseif (strpos($value, 'error') !== false): ?>
|
||||||
|
<div class="text-muted small mb-2"><?= htmlspecialchars($value) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php elseif ($func === 'migration'): ?>
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<span><?= htmlspecialchars($func) ?>()</span>
|
||||||
|
<span class="badge bg-<?= $value ? 'success' : 'danger' ?>">
|
||||||
|
<?= $value ? 'Available' : 'Missing' ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h6 class="card-title mb-0">Plugin Information</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="small">
|
||||||
|
<div class="mb-1"><strong>Name:</strong> <?= htmlspecialchars($plugin['name']) ?></div>
|
||||||
|
<div class="mb-1"><strong>Version:</strong> <?= htmlspecialchars($plugin['version'] ?? 'N/A') ?></div>
|
||||||
|
<div class="mb-1"><strong>Enabled:</strong> <span class="badge bg-<?= $plugin['enabled'] ? 'success' : 'secondary' ?>"><?= $plugin['enabled'] ? 'Yes' : 'No' ?></span></div>
|
||||||
|
<div class="mb-1"><strong>Dependencies:</strong> <?= !empty($plugin['dependencies']) ? htmlspecialchars(implode(', ', $plugin['dependencies'])) : 'None' ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php if (isset($checkResults['error'])): ?>
|
<?php if (isset($checkResults['error'])): ?>
|
||||||
<div class="alert alert-danger mt-3">
|
<div class="alert alert-danger mt-3">
|
||||||
|
|
@ -817,22 +828,20 @@ endif; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<?php if ($plugin['has_migration']): ?>
|
<form method="post" class="d-inline">
|
||||||
<form method="post" class="d-inline">
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
|
||||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
|
<input type="hidden" name="section" value="plugins">
|
||||||
<input type="hidden" name="section" value="plugins">
|
<input type="hidden" name="plugin" value="<?= htmlspecialchars($plugin['slug']) ?>">
|
||||||
<input type="hidden" name="plugin" value="<?= htmlspecialchars($plugin['slug']) ?>">
|
<input type="hidden" name="action" value="plugin_install">
|
||||||
<input type="hidden" name="action" value="plugin_install">
|
<button type="submit" class="btn btn-primary">Install Tables</button>
|
||||||
<button type="submit" class="btn btn-primary">Install plugin DB tables</button>
|
</form>
|
||||||
</form>
|
<form method="post" class="d-inline">
|
||||||
<form method="post" class="d-inline">
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
|
||||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf_token) ?>">
|
<input type="hidden" name="section" value="plugins">
|
||||||
<input type="hidden" name="section" value="plugins">
|
<input type="hidden" name="plugin" value="<?= htmlspecialchars($plugin['slug']) ?>">
|
||||||
<input type="hidden" name="plugin" value="<?= htmlspecialchars($plugin['slug']) ?>">
|
<input type="hidden" name="action" value="plugin_purge">
|
||||||
<input type="hidden" name="action" value="plugin_purge">
|
<button type="submit" class="btn btn-warning" onclick="return confirm('Are you sure? This will permanently delete all plugin data and tables!')">Purge Plugin</button>
|
||||||
<button type="submit" class="btn btn-warning" onclick="return confirm('Are you sure? This will permanently delete all plugin data and tables!')">Purge all plugin data</button>
|
</form>
|
||||||
</form>
|
|
||||||
<?php endif; ?>
|
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -853,7 +862,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||||
if (typeof $ !== 'undefined' && $.fn.tooltip) {
|
if (typeof $ !== 'undefined' && $.fn.tooltip) {
|
||||||
$('[data-toggle="tooltip"]').tooltip();
|
$('[data-toggle="tooltip"]').tooltip();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelectorAll('form.tm-confirm').forEach((form) => {
|
document.querySelectorAll('form.tm-confirm').forEach((form) => {
|
||||||
form.addEventListener('submit', (event) => {
|
form.addEventListener('submit', (event) => {
|
||||||
const message = form.getAttribute('data-confirm') || 'Are you sure?';
|
const message = form.getAttribute('data-confirm') || 'Are you sure?';
|
||||||
|
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
# Register Plugin
|
|
||||||
|
|
||||||
Provides user registration functionality.
|
|
||||||
|
|
||||||
## API Endpoints
|
|
||||||
|
|
||||||
### POST /register?action=register
|
|
||||||
Register a new user account.
|
|
||||||
|
|
||||||
**Request:**
|
|
||||||
- `username` (string, 3-20 chars, required)
|
|
||||||
- `password` (string, 8-255 chars, required)
|
|
||||||
- `confirm_password` (string, required)
|
|
||||||
- `csrf_token` (string, required)
|
|
||||||
- `terms` (boolean, required)
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"data": {
|
|
||||||
"message": "Registration successful. You can log in now.",
|
|
||||||
"redirect_to": "?page=login"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### GET /register?action=status
|
|
||||||
Check if registration is enabled.
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
Enable/disable registration in `totalmeet.conf.php`:
|
|
||||||
```php
|
|
||||||
'registration_enabled' => true,
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security Features
|
|
||||||
|
|
||||||
- CSRF protection
|
|
||||||
- Rate limiting
|
|
||||||
- Password hashing
|
|
||||||
- Input sanitization
|
|
||||||
|
|
||||||
## Implementation
|
|
||||||
|
|
||||||
Uses simple callable dispatcher pattern for single-action plugin:
|
|
||||||
```php
|
|
||||||
register_plugin_route_prefix('register', [
|
|
||||||
'dispatcher' => function($context) {
|
|
||||||
require_once PLUGIN_REGISTER_PATH . 'controllers/register.php';
|
|
||||||
},
|
|
||||||
'access' => 'public',
|
|
||||||
]);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
None - functions independently.
|
|
||||||
|
|
@ -1,26 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
// Register plugin bootstrap
|
||||||
* Register Plugin Bootstrap
|
// (here we add any plugin autoloader, if needed)
|
||||||
*
|
|
||||||
* Initializes the register plugin using the App API pattern.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Define plugin base path if not already defined
|
// List here all the controllers in "/controllers/" that we need as pages
|
||||||
if (!defined('PLUGIN_REGISTER_PATH')) {
|
$GLOBALS['plugin_controllers']['register'] = [
|
||||||
define('PLUGIN_REGISTER_PATH', __DIR__ . '/');
|
'register'
|
||||||
}
|
];
|
||||||
|
|
||||||
require_once PLUGIN_REGISTER_PATH . 'helpers.php';
|
|
||||||
require_once PLUGIN_REGISTER_PATH . 'controllers/register.php';
|
|
||||||
|
|
||||||
// Register route with dispatcher class
|
|
||||||
register_plugin_route_prefix('register', [
|
|
||||||
'dispatcher' => \Plugins\Register\Controllers\RegisterController::class,
|
|
||||||
'access' => 'public',
|
|
||||||
'defaults' => ['action' => 'register'],
|
|
||||||
'plugin' => 'register',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Add to publicly accessible pages
|
// Add to publicly accessible pages
|
||||||
register_hook('filter_public_pages', function($pages) {
|
register_hook('filter_public_pages', function($pages) {
|
||||||
|
|
|
||||||
|
|
@ -1,188 +1,110 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User Registration API Controller
|
* User registration
|
||||||
*
|
*
|
||||||
* Provides RESTful endpoints for user registration.
|
* This page ("register") handles user registration if the feature is enabled in the configuration.
|
||||||
* Follows the API pattern used by other plugins.
|
* It accepts a POST request with a username and password, attempts to register the user,
|
||||||
|
* and redirects to the login page on success or displays an error message on failure.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Plugins\Register\Controllers;
|
// Define plugin base path if not already defined
|
||||||
|
if (!defined('PLUGIN_REGISTER_PATH')) {
|
||||||
use App\App;
|
define('PLUGIN_REGISTER_PATH', dirname(__FILE__, 2) . '/');
|
||||||
use App\Helpers\Theme;
|
}
|
||||||
use Exception;
|
|
||||||
use PDO;
|
|
||||||
|
|
||||||
require_once APP_PATH . 'classes/feedback.php';
|
|
||||||
require_once APP_PATH . 'classes/user.php';
|
|
||||||
require_once APP_PATH . 'classes/validator.php';
|
|
||||||
require_once APP_PATH . 'helpers/security.php';
|
|
||||||
require_once APP_PATH . 'helpers/theme.php';
|
|
||||||
require_once APP_PATH . 'includes/rate_limit_middleware.php';
|
|
||||||
require_once PLUGIN_REGISTER_PATH . 'models/register.php';
|
require_once PLUGIN_REGISTER_PATH . 'models/register.php';
|
||||||
|
require_once dirname(__FILE__, 4) . '/app/classes/user.php';
|
||||||
|
require_once dirname(__FILE__, 4) . '/app/classes/validator.php';
|
||||||
|
require_once dirname(__FILE__, 4) . '/app/helpers/security.php';
|
||||||
|
|
||||||
class RegisterController
|
// registration is allowed, go on
|
||||||
{
|
if ($config['registration_enabled'] == true) {
|
||||||
private $db;
|
|
||||||
private array $config;
|
|
||||||
private string $appRoot;
|
|
||||||
private $logger;
|
|
||||||
|
|
||||||
public function __construct()
|
try {
|
||||||
{
|
global $db, $logObject, $userObject;
|
||||||
$this->db = App::db();
|
|
||||||
$this->config = App::config();
|
|
||||||
$this->appRoot = App::get('app_root') ?? '/';
|
|
||||||
$this->logger = App::get('logObject');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(string $action, array $context = []): bool
|
if ( $_SERVER['REQUEST_METHOD'] == 'POST' ) {
|
||||||
{
|
|
||||||
$validSession = (bool)($context['valid_session'] ?? false);
|
|
||||||
$app_root = $context['app_root'] ?? $this->appRoot;
|
|
||||||
|
|
||||||
if (!$this->db) {
|
// Apply rate limiting
|
||||||
\Feedback::flash('ERROR', 'DEFAULT', 'Registration service unavailable. Please try again later.');
|
require_once dirname(__FILE__, 4) . '/app/includes/rate_limit_middleware.php';
|
||||||
$this->renderForm($validSession, $app_root, ['registrationEnabled' => false]);
|
checkRateLimit($db, 'register');
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->isRegistrationEnabled()) {
|
$security = SecurityHelper::getInstance();
|
||||||
\Feedback::flash('NOTICE', 'DEFAULT', 'Registration is currently disabled.');
|
|
||||||
$this->renderForm($validSession, $app_root, ['registrationEnabled' => false]);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
// Sanitize input
|
||||||
$this->handleSubmission($validSession, $app_root);
|
$formData = $security->sanitizeArray($_POST, ['username', 'password', 'confirm_password', 'csrf_token', 'terms']);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->renderForm($validSession, $app_root);
|
// Validate CSRF token
|
||||||
return true;
|
if (!$security->verifyCsrfToken($formData['csrf_token'] ?? '')) {
|
||||||
}
|
throw new Exception(Feedback::get('ERROR', 'CSRF_INVALID')['message']);
|
||||||
|
|
||||||
private function isRegistrationEnabled(): bool
|
|
||||||
{
|
|
||||||
return (bool)($this->config['registration_enabled'] ?? false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function handleSubmission(bool $validSession, string $app_root): void
|
|
||||||
{
|
|
||||||
checkRateLimit($this->db, 'register');
|
|
||||||
|
|
||||||
$security = \SecurityHelper::getInstance();
|
|
||||||
$formData = $security->sanitizeArray(
|
|
||||||
$_POST,
|
|
||||||
['username', 'password', 'confirm_password', 'csrf_token', 'terms']
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!$security->verifyCsrfToken($formData['csrf_token'] ?? '')) {
|
|
||||||
\Feedback::flash('ERROR', 'DEFAULT', 'Invalid security token. Please try again.');
|
|
||||||
$this->renderForm($validSession, $app_root, [
|
|
||||||
'values' => ['username' => $formData['username'] ?? ''],
|
|
||||||
]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$validator = new \Validator($formData);
|
|
||||||
$rules = [
|
|
||||||
'username' => [
|
|
||||||
'required' => true,
|
|
||||||
'min' => 3,
|
|
||||||
'max' => 20,
|
|
||||||
],
|
|
||||||
'password' => [
|
|
||||||
'required' => true,
|
|
||||||
'min' => 8,
|
|
||||||
'max' => 255,
|
|
||||||
],
|
|
||||||
'confirm_password' => [
|
|
||||||
'required' => true,
|
|
||||||
'matches' => 'password',
|
|
||||||
],
|
|
||||||
'terms' => [
|
|
||||||
'required' => true,
|
|
||||||
'accepted' => true,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!$validator->validate($rules)) {
|
|
||||||
\Feedback::flash('ERROR', 'DEFAULT', $validator->getFirstError());
|
|
||||||
$this->renderForm($validSession, $app_root, [
|
|
||||||
'values' => ['username' => $formData['username'] ?? ''],
|
|
||||||
]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$username = trim($formData['username']);
|
|
||||||
$password = $formData['password'];
|
|
||||||
|
|
||||||
try {
|
|
||||||
$register = new \Register($this->db);
|
|
||||||
$result = $register->register($username, $password);
|
|
||||||
|
|
||||||
if ($result === true) {
|
|
||||||
$this->logSuccessfulRegistration($username);
|
|
||||||
\Feedback::flash('NOTICE', 'DEFAULT', 'Registration successful. You can log in now.');
|
|
||||||
header('Location: ' . $app_root . '?page=login');
|
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
\Feedback::flash('ERROR', 'DEFAULT', 'Registration failed: ' . $result);
|
$validator = new Validator($formData);
|
||||||
$this->renderForm($validSession, $app_root, [
|
$rules = [
|
||||||
'values' => ['username' => $username],
|
'username' => [
|
||||||
]);
|
'required' => true,
|
||||||
} catch (Exception $e) {
|
'min' => 3,
|
||||||
\Feedback::flash('ERROR', 'DEFAULT', 'Registration failed: ' . $e->getMessage());
|
'max' => 20
|
||||||
$this->renderForm($validSession, $app_root, [
|
],
|
||||||
'values' => ['username' => $username],
|
'password' => [
|
||||||
]);
|
'required' => true,
|
||||||
|
'min' => 8,
|
||||||
|
'max' => 100
|
||||||
|
],
|
||||||
|
'confirm_password' => [
|
||||||
|
'required' => true,
|
||||||
|
'matches' => 'password'
|
||||||
|
],
|
||||||
|
'terms' => [
|
||||||
|
'required' => true,
|
||||||
|
'equals' => 'on'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$username = $formData['username'] ?? 'unknown';
|
||||||
|
|
||||||
|
if ($validator->validate($rules)) {
|
||||||
|
$password = $formData['password'];
|
||||||
|
|
||||||
|
// registering
|
||||||
|
$register = new Register($db);
|
||||||
|
$result = $register->register($username, $password);
|
||||||
|
|
||||||
|
// redirect to login
|
||||||
|
if ($result === true) {
|
||||||
|
// Get the new user's ID for logging
|
||||||
|
$userId = $userObject->getUserId($username)[0]['id'];
|
||||||
|
$logObject->log('info', "Registration: New user \"$username\" registered successfully. IP: $user_IP", ['user_id' => $userId, 'scope' => 'user']);
|
||||||
|
Feedback::flash('NOTICE', 'DEFAULT', "Registration successful. You can log in now.");
|
||||||
|
header('Location: ' . htmlspecialchars($app_root . '?page=login'));
|
||||||
|
exit();
|
||||||
|
// registration fail, redirect to login
|
||||||
|
} else {
|
||||||
|
$logObject->log('error', "Registration: Failed registration attempt for user \"$username\". IP: $user_IP. Reason: $result", ['user_id' => null, 'scope' => 'system']);
|
||||||
|
Feedback::flash('ERROR', 'DEFAULT', "Registration failed. $result");
|
||||||
|
header('Location: ' . htmlspecialchars($app_root . '?page=register'));
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error = $validator->getFirstError();
|
||||||
|
$logObject->log('error', "Registration: Failed validation for user \"" . ($username ?? 'unknown') . "\". IP: $user_IP. Reason: $error", ['user_id' => null, 'scope' => 'system']);
|
||||||
|
Feedback::flash('ERROR', 'DEFAULT', $error);
|
||||||
|
header('Location: ' . htmlspecialchars($app_root . '?page=register'));
|
||||||
|
exit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$logObject->log('error', "Registration: System error. IP: $user_IP. Error: " . $e->getMessage(), ['user_id' => null, 'scope' => 'system']);
|
||||||
|
Feedback::flash('ERROR', 'DEFAULT', $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
private function logSuccessfulRegistration(string $username): void
|
// Get any new feedback messages
|
||||||
{
|
include_once dirname(__FILE__, 4) . '/app/helpers/feedback.php';
|
||||||
if (!$this->logger) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
// Load the template
|
||||||
$userModel = new \User($this->db);
|
include PLUGIN_REGISTER_PATH . 'views/form-register.php';
|
||||||
$userRecord = $userModel->getUserId($username);
|
|
||||||
$userId = $userRecord[0]['id'] ?? null;
|
|
||||||
$userIP = $_SERVER['REMOTE_ADDR'] ?? '';
|
|
||||||
|
|
||||||
$this->logger->log(
|
// registration disabled
|
||||||
'info',
|
} else {
|
||||||
sprintf('Registration: New user "%s" registered successfully. IP: %s', $username, $userIP),
|
echo Feedback::render('NOTICE', 'DEFAULT', 'Registration is disabled', false);
|
||||||
['user_id' => $userId, 'scope' => 'user']
|
|
||||||
);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
app_log('warning', 'RegisterController logging failed: ' . $e->getMessage(), ['scope' => 'plugin']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function renderForm(bool $validSession, string $app_root, array $data = []): void
|
|
||||||
{
|
|
||||||
$formValues = $data['values'] ?? ['username' => ''];
|
|
||||||
$registrationEnabled = $data['registrationEnabled'] ?? true;
|
|
||||||
|
|
||||||
Theme::include('page-header');
|
|
||||||
Theme::include('page-menu');
|
|
||||||
if ($validSession) {
|
|
||||||
Theme::include('page-sidebar');
|
|
||||||
}
|
|
||||||
|
|
||||||
include APP_PATH . 'helpers/feedback.php';
|
|
||||||
|
|
||||||
$app_root_value = $app_root; // align variable name for template include
|
|
||||||
$app_root = $app_root_value;
|
|
||||||
$values = $formValues;
|
|
||||||
|
|
||||||
include PLUGIN_REGISTER_PATH . 'views/form-register.php';
|
|
||||||
|
|
||||||
Theme::include('page-footer');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registration Plugin Helpers
|
|
||||||
*
|
|
||||||
* Aggregates all helper modules for the registration plugin.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Include any helper modules here when they are added
|
|
||||||
// For now, the registration plugin doesn't have separate helper modules
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\App;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* class Register
|
* class Register
|
||||||
*
|
*
|
||||||
* Handles user registration using the App API pattern.
|
* Handles user registration.
|
||||||
*/
|
*/
|
||||||
class Register {
|
class Register {
|
||||||
/**
|
/**
|
||||||
|
|
@ -17,18 +15,21 @@ class Register {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register constructor.
|
* Register constructor.
|
||||||
* Initializes the database connection using App API.
|
* Initializes the database connection.
|
||||||
*
|
*
|
||||||
* @param PDO|null $database The database connection (optional, will use App::db() if not provided).
|
* @param object $database The database object to initialize the connection.
|
||||||
*/
|
*/
|
||||||
public function __construct($database = null) {
|
public function __construct($database) {
|
||||||
$this->db = $database instanceof PDO ? $database : App::db();
|
if ($database instanceof PDO) {
|
||||||
|
$this->db = $database;
|
||||||
|
} else {
|
||||||
|
$this->db = $database->getConnection();
|
||||||
|
}
|
||||||
|
require_once dirname(__FILE__, 4) . '/app/classes/ratelimiter.php';
|
||||||
|
require_once dirname(__FILE__, 4) . '/app/classes/twoFactorAuth.php';
|
||||||
|
|
||||||
require_once APP_PATH . 'classes/ratelimiter.php';
|
$this->rateLimiter = new RateLimiter($database);
|
||||||
require_once APP_PATH . 'classes/twoFactorAuth.php';
|
$this->twoFactorAuth = new TwoFactorAuthentication($database);
|
||||||
|
|
||||||
$this->rateLimiter = new RateLimiter($this->db);
|
|
||||||
$this->twoFactorAuth = new TwoFactorAuthentication($this->db);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<h2 class="action-title">Register</h2>
|
<h2 class="action-title">Register</h2>
|
||||||
<p class="action-subtitle">Enter your credentials to create a new account</p>
|
<p class="action-subtitle">Enter your credentials to create a new account</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-card-body">
|
<div class="action-card-body">
|
||||||
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=register" class="action-form">
|
<form method="POST" action="<?= htmlspecialchars($app_root) ?>?page=register" class="action-form">
|
||||||
<?php include CSRF_TOKEN_INCLUDE; ?>
|
<?php include CSRF_TOKEN_INCLUDE; ?>
|
||||||
|
|
@ -15,21 +15,21 @@
|
||||||
pattern="[A-Za-z0-9_\-]{3,20}" title="3-20 characters, letters, numbers, - and _"
|
pattern="[A-Za-z0-9_\-]{3,20}" title="3-20 characters, letters, numbers, - and _"
|
||||||
required />
|
required />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-form-group">
|
<div class="action-form-group">
|
||||||
<label for="password" class="action-form-label">Password</label>
|
<label for="password" class="action-form-label">Password</label>
|
||||||
<input type="password" class="form-control action-form-control" name="password" placeholder="Password"
|
<input type="password" class="form-control action-form-control" name="password" placeholder="Password"
|
||||||
pattern=".{8,}" title="Eight or more characters"
|
pattern=".{8,}" title="Eight or more characters"
|
||||||
required />
|
required />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-form-group">
|
<div class="action-form-group">
|
||||||
<label for="confirm_password" class="action-form-label">Confirm Password</label>
|
<label for="confirm_password" class="action-form-label">Confirm Password</label>
|
||||||
<input type="password" class="form-control action-form-control" name="confirm_password" placeholder="Confirm password"
|
<input type="password" class="form-control action-form-control" name="confirm_password" placeholder="Confirm password"
|
||||||
pattern=".{8,}" title="Eight or more characters"
|
pattern=".{8,}" title="Eight or more characters"
|
||||||
required />
|
required />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-form-group">
|
<div class="action-form-group">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" class="form-check-input" id="terms" name="terms" required>
|
<input type="checkbox" class="form-check-input" id="terms" name="terms" required>
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
We use cookies to improve your experience. See our <a href="<?= htmlspecialchars($app_root) ?>?page=cookies" target="_blank">cookies policy</a>
|
We use cookies to improve your experience. See our <a href="<?= htmlspecialchars($app_root) ?>?page=cookies" target="_blank">cookies policy</a>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-actions">
|
<div class="action-actions">
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-primary">
|
||||||
<i class="fas fa-user-plus me-2"></i>Create account
|
<i class="fas fa-user-plus me-2"></i>Create account
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,11 @@ function register_plugin_route_prefix(string $prefix, array $definition = []): v
|
||||||
PluginRouteRegistry::registerPrefix($prefix, $definition);
|
PluginRouteRegistry::registerPrefix($prefix, $definition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load enabled plugins
|
||||||
|
$plugins_dir = dirname(__DIR__) . '/plugins/';
|
||||||
|
$enabled_plugins = PluginManager::load($plugins_dir);
|
||||||
|
$GLOBALS['enabled_plugins'] = $enabled_plugins;
|
||||||
|
|
||||||
// Define CSRF token include path globally
|
// Define CSRF token include path globally
|
||||||
if (!defined('CSRF_TOKEN_INCLUDE')) {
|
if (!defined('CSRF_TOKEN_INCLUDE')) {
|
||||||
define('CSRF_TOKEN_INCLUDE', APP_PATH . 'includes/csrf_token.php');
|
define('CSRF_TOKEN_INCLUDE', APP_PATH . 'includes/csrf_token.php');
|
||||||
|
|
@ -113,7 +118,7 @@ if (!isset($page)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// List of pages that don't require authentication
|
// List of pages that don't require authentication
|
||||||
$public_pages = ['login', 'register', 'help', 'about', 'theme-asset', 'plugin-asset'];
|
$public_pages = ['login', 'help', 'about', 'theme-asset', 'plugin-asset'];
|
||||||
|
|
||||||
// Let plugins filter/extend public_pages
|
// Let plugins filter/extend public_pages
|
||||||
$public_pages = filter_public_pages($public_pages);
|
$public_pages = filter_public_pages($public_pages);
|
||||||
|
|
@ -152,7 +157,7 @@ $allowed_urls = [
|
||||||
'settings','theme','theme-asset','plugin-asset',
|
'settings','theme','theme-asset','plugin-asset',
|
||||||
'admin','status',
|
'admin','status',
|
||||||
'help','about',
|
'help','about',
|
||||||
'login','register','logout',
|
'login','logout',
|
||||||
];
|
];
|
||||||
|
|
||||||
// Let plugins filter/extend allowed_urls
|
// Let plugins filter/extend allowed_urls
|
||||||
|
|
@ -173,11 +178,6 @@ use App\Core\DatabaseConnector;
|
||||||
$db = DatabaseConnector::connect($config);
|
$db = DatabaseConnector::connect($config);
|
||||||
App::set('db', $db);
|
App::set('db', $db);
|
||||||
|
|
||||||
// Load enabled plugins (we need this after DB connection is established)
|
|
||||||
$plugins_dir = dirname(__DIR__) . '/plugins/';
|
|
||||||
$enabled_plugins = PluginManager::load($plugins_dir);
|
|
||||||
$GLOBALS['enabled_plugins'] = $enabled_plugins;
|
|
||||||
|
|
||||||
// Initialize Log throttler
|
// Initialize Log throttler
|
||||||
require_once APP_PATH . 'core/LogThrottler.php';
|
require_once APP_PATH . 'core/LogThrottler.php';
|
||||||
use App\Core\LogThrottler;
|
use App\Core\LogThrottler;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue