System plugin support: admin menu items, admin routes, API integration
- PluginManager: getAdminMenuItems(), handleAdminRoute(), getPluginType() - admin.php: load PluginManager, pass plugin_admin_menu to Twig - admin.php: route to plugin admin pages via handleAdminRoute() - admin.twig: show 'Plugins' sidebar section for system plugins - plugins-new.twig: choose content or system plugin type - handlePluginsNew: generate proper template based on type (content/system) - plugin-admin.twig: renders plugin output in admin layout - GeoIPInfo: example system plugin (admin page with GeoIP info) - System plugins: getAdminMenu(), getAdminRoutes(), handleAdminRoute() - Content plugins: getSidebarContent() (unchanged)
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 41 KiB |
@@ -4,6 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}CodePress Admin{% endblock %}</title>
|
||||
<link rel="icon" type="image/x-icon" href="/admin/assets/img/favicon.ico">
|
||||
<link rel="stylesheet" href="/admin/assets/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/admin/assets/css/bootstrap-icons.css">
|
||||
<link rel="stylesheet" href="/admin/assets/css/style.css">
|
||||
@@ -124,6 +125,17 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if plugin_admin_menu is not empty %}
|
||||
<li class="nav-section">Plugins</li>
|
||||
{% for item in plugin_admin_menu %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{ route == item.route ? 'active' : '' }}" href="/admin/{{ item.route }}">
|
||||
<i class="bi {{ item.icon|default('bi-gear') }}"></i> {{ item.label }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if has_permission('guide') %}
|
||||
<li class="nav-section">Help</li>
|
||||
<li class="nav-item">
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{% extends "layouts/admin.twig" %}
|
||||
|
||||
{% block title %}{{ route|capitalize }} - CodePress Admin{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{{ plugin_content|raw }}
|
||||
{% endblock %}
|
||||
@@ -13,6 +13,21 @@
|
||||
<input type="text" class="form-control" id="name" name="name" required autofocus>
|
||||
<small class="form-text text-muted">Alleen letters, cijfers, underscores en streepjes.</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Plugin type</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="type" id="type-content" value="content" checked>
|
||||
<label class="form-check-label" for="type-content">
|
||||
<strong>Content plugin</strong> — Verschijnt in de sidebar op content pagina's
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="type" id="type-system" value="system">
|
||||
<label class="form-check-label" for="type-system">
|
||||
<strong>Systeem plugin</strong> — Voegt functionaliteit toe aan het CMS (admin menu, API)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-white">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
|
||||
@@ -227,4 +227,82 @@ class PluginManager
|
||||
}
|
||||
return $urls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get admin menu items from system plugins.
|
||||
* Each system plugin can register admin menu items via getAdminMenu().
|
||||
* Returns an array of [label, route, icon] tuples.
|
||||
*
|
||||
* @return array Admin menu items
|
||||
*/
|
||||
public function getAdminMenuItems(): array
|
||||
{
|
||||
$items = [];
|
||||
foreach ($this->plugins as $pluginName => $plugin) {
|
||||
// Only system plugins provide admin menu items
|
||||
if (method_exists($plugin, 'getConfig')) {
|
||||
$config = $plugin->getConfig();
|
||||
if (($config['type'] ?? 'content') !== 'system') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (method_exists($plugin, 'getAdminMenu')) {
|
||||
$menuItems = $plugin->getAdminMenu();
|
||||
if (is_array($menuItems)) {
|
||||
foreach ($menuItems as $item) {
|
||||
$items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an admin route for a system plugin.
|
||||
* Called when a route matches a plugin's registered admin route.
|
||||
* Returns the rendered content string, or null if no plugin handled it.
|
||||
*
|
||||
* @param string $route The admin route
|
||||
* @return string|null Rendered content or null if not handled
|
||||
*/
|
||||
public function handleAdminRoute(string $route): ?string
|
||||
{
|
||||
foreach ($this->plugins as $pluginName => $plugin) {
|
||||
if (method_exists($plugin, 'getConfig')) {
|
||||
$config = $plugin->getConfig();
|
||||
if (($config['type'] ?? 'content') !== 'system') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (method_exists($plugin, 'getAdminRoutes')) {
|
||||
$routes = $plugin->getAdminRoutes();
|
||||
if (is_array($routes) && in_array($route, $routes, true)) {
|
||||
ob_start();
|
||||
$plugin->handleAdminRoute($route);
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a plugin's type (content or system).
|
||||
*
|
||||
* @param string $pluginName Plugin name
|
||||
* @return string 'content', 'system', or 'content' if unknown
|
||||
*/
|
||||
public function getPluginType(string $pluginName): string
|
||||
{
|
||||
$pluginDir = $this->pluginsPath . '/' . $pluginName;
|
||||
$jsonFile = $pluginDir . '/plugin.json';
|
||||
if (file_exists($jsonFile)) {
|
||||
$data = json_decode(file_get_contents($jsonFile), true);
|
||||
return $data['type'] ?? 'content';
|
||||
}
|
||||
return 'content';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
class GeoIPInfo
|
||||
{
|
||||
private ?CMSAPI $api = null;
|
||||
|
||||
public function setAPI(CMSAPI $api): void
|
||||
{
|
||||
$this->api = $api;
|
||||
}
|
||||
|
||||
public function getConfig(): array
|
||||
{
|
||||
return [
|
||||
'title' => 'GeoIP Info',
|
||||
'type' => 'system',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register admin menu items.
|
||||
*/
|
||||
public function getAdminMenu(): array
|
||||
{
|
||||
return [
|
||||
['label' => 'GeoIP Info', 'route' => 'geoip-info', 'icon' => 'bi-globe2'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register admin routes this plugin handles.
|
||||
*/
|
||||
public function getAdminRoutes(): array
|
||||
{
|
||||
return ['geoip-info'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle admin route — render the admin page.
|
||||
*/
|
||||
public function handleAdminRoute(string $route): void
|
||||
{
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
|
||||
$geoip = new GeoIP();
|
||||
$countryCode = $geoip->lookupCountry($ip);
|
||||
$country = GeoIP::getCountryName($countryCode);
|
||||
$flag = GeoIP::getCountryFlagEmoji($countryCode);
|
||||
|
||||
echo '<h2 class="mb-4"><i class="bi bi-globe2"></i> GeoIP Info</h2>';
|
||||
echo '<div class="card shadow-sm"><div class="card-body">';
|
||||
echo '<table class="table table-sm">';
|
||||
echo '<tr><td>IP adres</td><td><code>' . htmlspecialchars($ip) . '</code></td></tr>';
|
||||
echo '<tr><td>Land</td><td>' . $flag . ' ' . htmlspecialchars($country) . '</td></tr>';
|
||||
echo '</table>';
|
||||
echo '</div></div>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "GeoIPInfo",
|
||||
"version": "1.0.0",
|
||||
"author": "CodePress",
|
||||
"description": "Systeem plugin voor GeoIP informatie in admin",
|
||||
"type": "system"
|
||||
}
|
||||
+40
-2
@@ -27,6 +27,8 @@ require_once __DIR__ . '/../cms/core/class/Cache.php';
|
||||
require_once __DIR__ . '/../cms/core/class/GeoIP.php';
|
||||
require_once __DIR__ . '/../cms/core/class/Analytics.php';
|
||||
require_once __DIR__ . '/../cms/core/class/LogManager.php';
|
||||
require_once __DIR__ . '/../cms/core/plugin/CMSAPI.php';
|
||||
require_once __DIR__ . '/../cms/core/plugin/PluginManager.php';
|
||||
|
||||
// Initialize dynamic logging from site config
|
||||
$siteConfigForLogging = file_exists($appConfig['config_json'])
|
||||
@@ -62,6 +64,15 @@ $twig->addFunction(new \Twig\TwigFunction('role_label', function($role) {
|
||||
return AdminAuth::getRoleLabel($role);
|
||||
}));
|
||||
|
||||
// Initialize PluginManager for admin (system plugins provide admin menu items)
|
||||
$siteConfigForPlugins = file_exists($appConfig['config_json'])
|
||||
? (json_decode(file_get_contents($appConfig['config_json']), true) ?? [])
|
||||
: [];
|
||||
$enabledPluginsList = $siteConfigForPlugins['plugins']['enabled'] ?? $siteConfigForPlugins['enabled_plugins'] ?? [];
|
||||
$adminPluginManager = new PluginManager(__DIR__ . '/../plugins', $enabledPluginsList);
|
||||
$pluginAdminMenuItems = $adminPluginManager->getAdminMenuItems();
|
||||
$twig->addGlobal('plugin_admin_menu', $pluginAdminMenuItems);
|
||||
|
||||
// Routing
|
||||
$route = $_GET['route'] ?? '';
|
||||
|
||||
@@ -247,8 +258,23 @@ switch ($route) {
|
||||
break;
|
||||
|
||||
default:
|
||||
// Check if a system plugin handles this route
|
||||
$pluginContent = $adminPluginManager->handleAdminRoute($route);
|
||||
if ($pluginContent !== null) {
|
||||
echo $twig->render('pages/plugin-admin.twig', [
|
||||
'user' => $user,
|
||||
'route' => $route,
|
||||
'csrf_token' => $csrf,
|
||||
'sidebar_color' => getSidebarColor($appConfig),
|
||||
'needs_editor' => false,
|
||||
'message' => '',
|
||||
'message_type' => 'info',
|
||||
'plugin_content' => $pluginContent,
|
||||
]);
|
||||
} else {
|
||||
handleDashboard($auth, $appConfig, $twig, $user, $csrf, $siteConfig);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HANDLER FUNCTIONS
|
||||
@@ -1109,6 +1135,10 @@ function handlePluginsNew($auth, $config, $twig, $user, $csrf): void
|
||||
$messageType = 'danger';
|
||||
} else {
|
||||
$pluginName = trim($_POST['name'] ?? '');
|
||||
$pluginType = $_POST['type'] ?? 'content';
|
||||
if (!in_array($pluginType, ['content', 'system'], true)) {
|
||||
$pluginType = 'content';
|
||||
}
|
||||
if (!empty($pluginName)) {
|
||||
$pluginName = preg_replace('/[^a-zA-Z0-9_-]/', '-', $pluginName);
|
||||
$pluginsDir = $config['plugins_dir'];
|
||||
@@ -1121,11 +1151,19 @@ function handlePluginsNew($auth, $config, $twig, $user, $csrf): void
|
||||
'name' => ucfirst($pluginName),
|
||||
'version' => '1.0.0',
|
||||
'author' => $user['username'],
|
||||
'type' => $pluginType,
|
||||
];
|
||||
file_put_contents($newPluginDir . '/plugin.json', json_encode($pluginJson, JSON_PRETTY_PRINT));
|
||||
file_put_contents($newPluginDir . '/' . $pluginName . '.php', "<?php\n// Plugin: " . ucfirst($pluginName) . "\n\necho 'Hello from " . $pluginName . "';\n");
|
||||
|
||||
adminLog($config, 'info', $user['username'] . ' creëerde plugin ' . $pluginName);
|
||||
// Generate plugin template based on type
|
||||
if ($pluginType === 'system') {
|
||||
$template = "<?php\n\nclass $pluginName\n{\n private ?CMSAPI \$api = null;\n\n public function setAPI(CMSAPI \$api): void\n {\n \$this->api = \$api;\n }\n\n public function getConfig(): array\n {\n return ['title' => ucfirst('$pluginName'), 'type' => 'system'];\n }\n\n // Register admin menu items\n public function getAdminMenu(): array\n {\n return [\n ['label' => ucfirst('$pluginName'), 'route' => '" . strtolower($pluginName) . "', 'icon' => 'bi-gear'],\n ];\n }\n\n // Register admin routes this plugin handles\n public function getAdminRoutes(): array\n {\n return ['" . strtolower($pluginName) . "'];\n }\n\n // Handle admin route\n public function handleAdminRoute(string \$route): void\n {\n // Render your admin page here\n echo '<h1>" . ucfirst($pluginName) . "</h1><p>Systeem plugin admin pagina.</p>';\n }\n}\n";
|
||||
} else {
|
||||
$template = "<?php\n\nclass $pluginName\n{\n private ?CMSAPI \$api = null;\n\n public function setAPI(CMSAPI \$api): void\n {\n \$this->api = \$api;\n }\n\n public function getConfig(): array\n {\n return ['title' => ucfirst('$pluginName'), 'type' => 'content'];\n }\n\n public function getSidebarContent(): string\n {\n return '<p>" . ucfirst($pluginName) . " sidebar content.</p>';\n }\n}\n";
|
||||
}
|
||||
file_put_contents($newPluginDir . '/' . $pluginName . '.php', $template);
|
||||
|
||||
adminLog($config, 'info', $user['username'] . ' creëerde ' . $pluginType . ' plugin ' . $pluginName);
|
||||
header('Location: /admin/plugins-edit?plugin=' . urlencode($pluginName));
|
||||
exit;
|
||||
} else {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
@@ -31,7 +31,7 @@
|
||||
<link rel="me" href="{{ author_git }}">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" href="{{ theme_base_url }}/assets/img/favicon.ico" sizes="any">
|
||||
<link rel="icon" type="image/x-icon" href="{{ theme_base_url }}/assets/img/favicon.ico">
|
||||
<link rel="icon" type="image/svg+xml" href="{{ theme_base_url }}/assets/img/favicon.svg">
|
||||
<link rel="apple-touch-icon" href="{{ theme_base_url }}/assets/img/favicon.svg">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 31 KiB |
Reference in New Issue
Block a user