Files
WP-LIT-MCP/src/Plugin.php
T
Jan-Niclas LoosenandClaude Opus 5.5 234fd1edac fix(security): Pods-Meta nur lesbar, wenn Pods aktiv, aber nicht nutzbar
Ist Pods aktiv, seine API aber nicht nutzbar, melden gesampelte und registrierte Meta-Schlüssel READONLY statt schreibbar. Sonst würden Pods-Felder (auch über Pods' register_meta-Integration) an Pods vorbei geschrieben.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:30:05 +01:00

211 lines
7.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace LIT\MCP;
use LIT\MCP\Abilities\AbilityRegistrar;
use LIT\MCP\Abilities\ExposurePolicy;
use LIT\MCP\Abilities\ToolAvailability;
use LIT\MCP\Admin\AccessTab;
use LIT\MCP\Admin\AdminNotices;
use LIT\MCP\Admin\ConnectionRepository;
use LIT\MCP\Admin\ConnectionStatus;
use LIT\MCP\Admin\ConnectionTab;
use LIT\MCP\Admin\ConnectionUsage;
use LIT\MCP\Admin\LimitsTab;
use LIT\MCP\Admin\LogTab;
use LIT\MCP\Admin\SettingsPage;
use LIT\MCP\Admin\SiteHealth;
use LIT\MCP\Admin\ToolsTab;
use LIT\MCP\ChangeLog\ChangeLog;
use LIT\MCP\ChangeLog\ChangeLogCleanup;
use LIT\MCP\ChangeLog\ChangeLogInstaller;
use LIT\MCP\Guides\InstructionsBuilder;
use LIT\MCP\Integrations\Core\CoreIntegration;
use LIT\MCP\Integrations\Divi\DiviIntegration;
use LIT\MCP\Integrations\Divi\Release\DiviFeatureGate;
use LIT\MCP\Integrations\IntegrationRegistry;
use LIT\MCP\Integrations\Media\MediaIntegration;
use LIT\MCP\Integrations\Pods\PodsFieldSource;
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
use LIT\MCP\Integrations\WordPress\Schema\RegisteredMetaSource;
use LIT\MCP\Integrations\WordPress\Schema\SampledMetaSource;
use LIT\MCP\Integrations\WordPress\WordPressIntegration;
use LIT\MCP\Server\AuthorizeGate;
use LIT\MCP\Server\DiscoveryCanonical;
use LIT\MCP\Server\RequestContext;
use LIT\MCP\Server\ServerRegistrar;
use LIT\MCP\Server\VisibilityFilter;
use LIT\MCP\Updater\GiteaUpdater;
use LIT\MCP\Updater\RecheckOnInstall;
defined('ABSPATH') || exit;
/**
* Wires WP-LIT-MCP into WordPress.
*/
final class Plugin
{
public const VERSION = '1.1.0';
private static string $pluginFile = '';
private static ?SettingsPage $settingsPage = null;
public static function boot(string $pluginFile): void
{
self::$pluginFile = $pluginFile;
// Before the dependency check: a plugin that is inactive for a missing dependency must still be updatable.
(new GiteaUpdater(plugin_basename($pluginFile), dirname($pluginFile)))->register();
(new RecheckOnInstall(self::VERSION))->register();
$missing = (new Dependencies())->missing();
if ($missing !== []) {
self::noticeMissing($missing);
return;
}
$access = new Access();
$access->register();
(new RequestContext())->register();
(new VisibilityFilter())->register();
(new DiscoveryCanonical())->register();
(new AuthorizeGate())->register();
$limits = new Limits();
$changeLog = new ChangeLog();
(new ChangeLogInstaller($changeLog))->maybeInstall();
(new ChangeLogCleanup($changeLog, $limits))->register();
$schema = self::contentSchema();
$tools = new ToolAvailability();
$registry = self::integrations($schema, $changeLog, $limits, $tools);
$policy = ExposurePolicy::fromOption()->withGroups($registry->groupMap(...));
$tools->bind($registry, $policy);
// Integration-owned hooks (Divi: upgrader hook, check route, CLI command, tab "Divi") – before the settings
// page reads its tabs.
$registry->boot();
(new AbilityRegistrar(
$registry,
$policy,
[new DiviFeatureGate($changeLog)]
))->register();
(new ServerRegistrar(
$registry,
new InstructionsBuilder($registry, static fn (): array => $schema->postTypeNames(), $policy),
null,
null,
null,
$policy
))->register();
$status = new ConnectionStatus();
(new SiteHealth($status))->register();
$tabs = [
new ConnectionTab($status),
new AccessTab($access, new ConnectionRepository()),
new ToolsTab($registry, $policy),
new LogTab($changeLog, $limits, $registry),
new LimitsTab(),
];
self::$settingsPage = new SettingsPage(
$tabs,
plugins_url('assets/img/loosen-it-logo.svg', self::$pluginFile),
plugins_url('assets', self::$pluginFile),
self::VERSION
);
(new ConnectionUsage())->register();
if (is_admin()) {
self::$settingsPage->register();
(new AdminNotices($status))->register();
}
\WPMedia\MCP\OAuth\Bootstrap::instance();
}
/**
* The integrations in registry order – the order of the Tools tab, the guide and the server instructions. The
* Divi integration and its groups share $tools, which Plugin binds once registry and policy exist.
*
* @internal Public for PluginTest only.
*/
public static function integrations(
ContentSchema $schema,
ChangeLog $changeLog,
Limits $limits,
ToolAvailability $tools
): IntegrationRegistry {
// E3: lit-site-status and the Divi tab name the tools each feature affects; the registry exists only after
// the Divi integration, so it is asked at call time.
$registry = null;
$allAbilities = static function () use (&$registry): array {
return $registry instanceof IntegrationRegistry ? $registry->abilities() : [];
};
$divi = new DiviIntegration($schema, $changeLog, $tools, $allAbilities);
$registry = new IntegrationRegistry(
new CoreIntegration(self::pluginDir() . '/docs/guides', [$divi->environmentComponent()]),
new WordPressIntegration(
$schema,
$changeLog,
$divi->contentComposer(),
$divi->libraryIdsVisible(...),
$divi->builderDetector()
),
new MediaIntegration($schema, $changeLog, $limits, $divi->mediaUsageSources()),
$divi,
$divi->library(),
$divi->themeBuilder()
);
return $registry;
}
public static function settingsPage(): ?SettingsPage
{
return self::$settingsPage;
}
public static function pluginDir(): string
{
return dirname(self::$pluginFile);
}
/**
* @param list<string> $missing
*/
public static function noticeMissing(array $missing): void
{
add_action('admin_notices', static function () use ($missing): void {
printf(
'<div class="notice notice-error"><p>%s</p></div>',
esc_html(sprintf(
/* translators: %s: comma separated list of missing components */
__('WP-LIT-MCP ist inaktiv. Es fehlt: %s', 'wp-lit-mcp'),
implode(', ', $missing)
))
);
});
}
private static function contentSchema(): ContentSchema
{
// Pods active but unusable: its meta keys (sampled, or registered via Pods' register_meta integration) must not
// be written past Pods.
$podsUnusable = defined('PODS_VERSION') && ! PodsFieldSource::isAvailable();
$sources = [new RegisteredMetaSource(readOnly: $podsUnusable), new SampledMetaSource(readOnly: $podsUnusable)];
if (PodsFieldSource::isAvailable()) {
array_unshift($sources, new PodsFieldSource());
}
// Divi's post types and builder check come in statically: the schema exists before the Divi integration.
return new ContentSchema(
$sources,
DiviIntegration::builderEnabledFor(...),
DiviIntegration::internalPostTypes()
);
}
}