diff --git a/admin/theme/default/assets/img/favicon.ico b/admin/theme/default/assets/img/favicon.ico
index 78280bc..485452a 100644
Binary files a/admin/theme/default/assets/img/favicon.ico and b/admin/theme/default/assets/img/favicon.ico differ
diff --git a/admin/theme/default/views/layouts/admin.twig b/admin/theme/default/views/layouts/admin.twig
index e42b2a9..ab491cc 100644
--- a/admin/theme/default/views/layouts/admin.twig
+++ b/admin/theme/default/views/layouts/admin.twig
@@ -4,6 +4,7 @@
{% block title %}CodePress Admin{% endblock %}
+
@@ -124,6 +125,17 @@
{% endif %}
{% endif %}
+ {% if plugin_admin_menu is not empty %}
+ Plugins
+ {% for item in plugin_admin_menu %}
+
+
+ {{ item.label }}
+
+
+ {% endfor %}
+ {% endif %}
+
{% if has_permission('guide') %}
Help
diff --git a/admin/theme/default/views/pages/plugin-admin.twig b/admin/theme/default/views/pages/plugin-admin.twig
new file mode 100644
index 0000000..1b32e34
--- /dev/null
+++ b/admin/theme/default/views/pages/plugin-admin.twig
@@ -0,0 +1,7 @@
+{% extends "layouts/admin.twig" %}
+
+{% block title %}{{ route|capitalize }} - CodePress Admin{% endblock %}
+
+{% block content %}
+{{ plugin_content|raw }}
+{% endblock %}
\ No newline at end of file
diff --git a/admin/theme/default/views/pages/plugins-new.twig b/admin/theme/default/views/pages/plugins-new.twig
index 22ee7fa..5cca831 100644
--- a/admin/theme/default/views/pages/plugins-new.twig
+++ b/admin/theme/default/views/pages/plugins-new.twig
@@ -13,6 +13,21 @@
Alleen letters, cijfers, underscores en streepjes.
+
+
+
+
+
+
+
+
+
+
+
-{% endblock %}
+{% endblock %}
\ No newline at end of file
diff --git a/cms/core/plugin/PluginManager.php b/cms/core/plugin/PluginManager.php
index c334b75..92f3cd5 100644
--- a/cms/core/plugin/PluginManager.php
+++ b/cms/core/plugin/PluginManager.php
@@ -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';
+ }
}
diff --git a/plugins/GeoIPInfo/GeoIPInfo.php b/plugins/GeoIPInfo/GeoIPInfo.php
new file mode 100644
index 0000000..71d8d69
--- /dev/null
+++ b/plugins/GeoIPInfo/GeoIPInfo.php
@@ -0,0 +1,57 @@
+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 ' GeoIP Info
';
+ echo '';
+ echo '
';
+ echo '| IP adres | ' . htmlspecialchars($ip) . ' |
';
+ echo '| Land | ' . $flag . ' ' . htmlspecialchars($country) . ' |
';
+ echo '
';
+ echo '
';
+ }
+}
\ No newline at end of file
diff --git a/plugins/GeoIPInfo/plugin.json b/plugins/GeoIPInfo/plugin.json
new file mode 100644
index 0000000..1a73324
--- /dev/null
+++ b/plugins/GeoIPInfo/plugin.json
@@ -0,0 +1,7 @@
+{
+ "name": "GeoIPInfo",
+ "version": "1.0.0",
+ "author": "CodePress",
+ "description": "Systeem plugin voor GeoIP informatie in admin",
+ "type": "system"
+}
\ No newline at end of file
diff --git a/public/admin.php b/public/admin.php
index c24b32c..f47e534 100644
--- a/public/admin.php
+++ b/public/admin.php
@@ -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,7 +258,22 @@ switch ($route) {
break;
default:
- handleDashboard($auth, $appConfig, $twig, $user, $csrf, $siteConfig);
+ // 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);
+ }
}
// ============================================================================
@@ -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', "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 '" . ucfirst($pluginName) . "
Systeem plugin admin pagina.
';\n }\n}\n";
+ } else {
+ $template = "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 '" . ucfirst($pluginName) . " sidebar content.
';\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 {
diff --git a/themes/default/assets/img/favicon.ico b/themes/default/assets/img/favicon.ico
new file mode 100644
index 0000000..485452a
Binary files /dev/null and b/themes/default/assets/img/favicon.ico differ
diff --git a/themes/default/base.twig b/themes/default/base.twig
index 6a97574..eda30ad 100644
--- a/themes/default/base.twig
+++ b/themes/default/base.twig
@@ -31,7 +31,7 @@
-
+
diff --git a/themes/favicon.ico b/themes/favicon.ico
deleted file mode 100644
index 78280bc..0000000
Binary files a/themes/favicon.ico and /dev/null differ