- plugin.json type field: 'content' (sidebar) or 'system' (API only) - Content-edit: label 'Plugins', hide section if layout has no sidebar - Content-edit: disable checkboxes when no sidebar layout selected - Content-edit: hide up arrow on first item, down arrow on last item - PluginManager: isPluginViewable() checks type=system -> not viewable - Admin plugins page: show Content/Systeem type badge - HTMLBlock: add plugin.json with type=content - Navigation: add type=content to config
231 lines
7.3 KiB
PHP
231 lines
7.3 KiB
PHP
<?php
|
|
|
|
class PluginManager
|
|
{
|
|
private array $plugins = [];
|
|
private string $pluginsPath;
|
|
private ?CMSAPI $api = null;
|
|
private array $enabledPlugins = [];
|
|
private array $actions = [];
|
|
private array $filters = [];
|
|
|
|
public function __construct(string $pluginsPath, array $enabledPlugins = [])
|
|
{
|
|
$this->pluginsPath = $pluginsPath;
|
|
$this->enabledPlugins = $enabledPlugins;
|
|
$this->loadPlugins();
|
|
}
|
|
|
|
public function setAPI(CMSAPI $api): void
|
|
{
|
|
$this->api = $api;
|
|
|
|
foreach ($this->plugins as $plugin) {
|
|
if (method_exists($plugin, 'setAPI')) {
|
|
$plugin->setAPI($api);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function loadPlugins(): void
|
|
{
|
|
if (!is_dir($this->pluginsPath)) {
|
|
return;
|
|
}
|
|
|
|
$pluginDirs = glob($this->pluginsPath . '/*', GLOB_ONLYDIR);
|
|
|
|
foreach ($pluginDirs as $pluginDir) {
|
|
$pluginName = basename($pluginDir);
|
|
|
|
if (!in_array($pluginName, $this->enabledPlugins, true)) {
|
|
continue;
|
|
}
|
|
|
|
$pluginFile = $pluginDir . '/' . $pluginName . '.php';
|
|
|
|
if (file_exists($pluginFile)) {
|
|
require_once $pluginFile;
|
|
|
|
$className = $pluginName;
|
|
if (class_exists($className)) {
|
|
$this->plugins[$pluginName] = new $className();
|
|
|
|
if ($this->api && method_exists($this->plugins[$pluginName], 'setAPI')) {
|
|
$this->plugins[$pluginName]->setAPI($this->api);
|
|
}
|
|
|
|
// Auto-register hooks from plugin methods
|
|
$hookMethods = ['onPageLoad', 'onBeforeRender', 'onAfterRender', 'onSearch', 'onMenuBuild'];
|
|
foreach ($hookMethods as $hook) {
|
|
if (method_exists($this->plugins[$pluginName], $hook)) {
|
|
$this->addAction($hook, [$this->plugins[$pluginName], $hook]);
|
|
}
|
|
}
|
|
|
|
// Register filter methods
|
|
$filterMethods = ['onContentFilter', 'onTitleFilter', 'onMenuFilter'];
|
|
foreach ($filterMethods as $filter) {
|
|
if (method_exists($this->plugins[$pluginName], $filter)) {
|
|
$this->addFilter($filter, [$this->plugins[$pluginName], $filter]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public function addAction(string $hook, callable $callback, int $priority = 10): void
|
|
{
|
|
$this->actions[$hook][$priority][] = $callback;
|
|
}
|
|
|
|
public function addFilter(string $hook, callable $callback, int $priority = 10): void
|
|
{
|
|
$this->filters[$hook][$priority][] = $callback;
|
|
}
|
|
|
|
public function doAction(string $hook, ...$args): void
|
|
{
|
|
if (!isset($this->actions[$hook])) return;
|
|
ksort($this->actions[$hook]);
|
|
foreach ($this->actions[$hook] as $callbacks) {
|
|
foreach ($callbacks as $callback) {
|
|
$callback(...$args);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function applyFilters(string $hook, $value, ...$args)
|
|
{
|
|
if (!isset($this->filters[$hook])) return $value;
|
|
ksort($this->filters[$hook]);
|
|
foreach ($this->filters[$hook] as $callbacks) {
|
|
foreach ($callbacks as $callback) {
|
|
$value = $callback($value, ...$args);
|
|
}
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
public function getPlugin(string $name): ?object
|
|
{
|
|
return $this->plugins[$name] ?? null;
|
|
}
|
|
|
|
public function getAllPlugins(): array
|
|
{
|
|
return $this->plugins;
|
|
}
|
|
|
|
public function getEnabledPlugins(): array
|
|
{
|
|
return $this->enabledPlugins;
|
|
}
|
|
|
|
public function isEnabled(string $pluginName): bool
|
|
{
|
|
return in_array($pluginName, $this->enabledPlugins, true);
|
|
}
|
|
|
|
public function isPluginViewable(object $plugin): bool
|
|
{
|
|
if (method_exists($plugin, 'getConfig')) {
|
|
$config = $plugin->getConfig();
|
|
if (isset($config['viewable']) && $config['viewable'] === false) {
|
|
return false;
|
|
}
|
|
// System plugins don't show in sidebar
|
|
if (isset($config['type']) && $config['type'] === 'system') {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function getSidebarContent(?array $allowedPlugins = null): string
|
|
{
|
|
$sidebarContent = '';
|
|
|
|
// If allowedPlugins is specified, iterate in that order (user-defined order)
|
|
if ($allowedPlugins !== null) {
|
|
foreach ($allowedPlugins as $pluginName) {
|
|
$plugin = $this->plugins[$pluginName] ?? null;
|
|
if (!$plugin || !$this->isPluginViewable($plugin) || !method_exists($plugin, 'getSidebarContent')) {
|
|
continue;
|
|
}
|
|
|
|
$content = $plugin->getSidebarContent();
|
|
if (trim($content) === '') {
|
|
continue;
|
|
}
|
|
|
|
$title = 'Plugin';
|
|
if (method_exists($plugin, 'getConfig')) {
|
|
$config = $plugin->getConfig();
|
|
$title = $config['title'] ?? 'Plugin';
|
|
}
|
|
|
|
$sidebarContent .= '
|
|
<div class="card mb-3">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">' . htmlspecialchars($title) . '</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
' . $content . '
|
|
</div>
|
|
</div>';
|
|
}
|
|
} else {
|
|
// No filter — show all viewable plugins in load order
|
|
foreach ($this->plugins as $pluginName => $plugin) {
|
|
if (!$this->isPluginViewable($plugin) || !method_exists($plugin, 'getSidebarContent')) {
|
|
continue;
|
|
}
|
|
|
|
$content = $plugin->getSidebarContent();
|
|
if (trim($content) === '') {
|
|
continue;
|
|
}
|
|
|
|
$title = 'Plugin';
|
|
if (method_exists($plugin, 'getConfig')) {
|
|
$config = $plugin->getConfig();
|
|
$title = $config['title'] ?? 'Plugin';
|
|
}
|
|
|
|
$sidebarContent .= '
|
|
<div class="card mb-3">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">' . htmlspecialchars($title) . '</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
' . $content . '
|
|
</div>
|
|
</div>';
|
|
}
|
|
}
|
|
|
|
return $sidebarContent;
|
|
}
|
|
|
|
/**
|
|
* Get CSS URLs from all enabled plugins that provide getCssUrl().
|
|
*
|
|
* @return array List of CSS URLs
|
|
*/
|
|
public function getPluginCssUrls(): array
|
|
{
|
|
$urls = [];
|
|
foreach ($this->plugins as $pluginName => $plugin) {
|
|
if (method_exists($plugin, 'getCssUrl')) {
|
|
$url = $plugin->getCssUrl();
|
|
if (!empty($url)) {
|
|
$urls[] = $url;
|
|
}
|
|
}
|
|
}
|
|
return $urls;
|
|
}
|
|
}
|