refactor(divi): DiviTab geht im Reiter „Compatibility“ auf
Der eigene Divi-Reiter entfällt. DiviAdminExtras behält Admin-Hinweis, Orphan-Bereinigung vor der Ausgabe (lit_mcp_compat_profile_load), Hinweis zum nächsten Schritt und zur privaten Testseite (lit_mcp_compat_profile_intro), Zustandstexte mit Quelle (lit_mcp_compat_explanation), Automatik-Notiz und Visual-Builder-Hinweis (lit_mcp_compat_profile_extra) sowie die Spalten Quelle, Probleme und Betroffene Tools über die neue, gegen fehlerhafte Callbacks abgesicherte Erweiterung lit_mcp_compat_capability_columns. Texte, Doku, CSS (lit-mcp-compat-*) und Smoke auf den neuen Reiter umgestellt, tote divi_*-Hinweise entfernt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
85f4138b36
commit
b40b81f585
@@ -0,0 +1,321 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Integrations\Divi\Admin;
|
||||
|
||||
use Closure;
|
||||
use LIT\MCP\Admin\CompatibilityTab;
|
||||
use LIT\MCP\Admin\SettingsPage;
|
||||
use LIT\MCP\Compatibility\Capability;
|
||||
use LIT\MCP\Compatibility\CheckReportStore;
|
||||
use LIT\MCP\Compatibility\CompatibilityMatrix;
|
||||
use LIT\MCP\Compatibility\FeatureStatus;
|
||||
use LIT\MCP\Compatibility\ReleaseState;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviEnvironment;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviProfile;
|
||||
use LIT\MCP\Integrations\Divi\Release\FeatureOverview;
|
||||
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
||||
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
|
||||
use LIT\MCP\Integrations\Divi\Release\WriteProbe;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* What Divi adds to the tab "Compatibility" (Admin\CompatibilityTab, which owns check, release and withdrawal):
|
||||
* the admin notice on every admin page while a feature is unusable, the removal of check pages a dead check left
|
||||
* behind (in the tab's load action, not while rendering), the columns source / problems / affected tools of the
|
||||
* capability table (E3), the source-specific explanation of the overall state, above the capability table (intro)
|
||||
* the orphan report, the next step and what the check does and, closing the section, the automation note and – with a
|
||||
* report – the Visual Builder hint.
|
||||
*/
|
||||
final class DiviAdminExtras
|
||||
{
|
||||
/** @var list<int> Check pages removeOrphans() deleted in this request; renderIntro() reports them. */
|
||||
private array $removed = [];
|
||||
|
||||
/**
|
||||
* @param FeatureOverview|null $overview null = a table without the affected tools.
|
||||
* @param (Closure(): string)|null $version Installed Divi version ('' = none); null = DiviEnvironment.
|
||||
* @param CheckReportStore|null $reports null = the Divi store.
|
||||
*/
|
||||
public function __construct(
|
||||
private ?FeatureOverview $overview = null,
|
||||
private ?Closure $version = null,
|
||||
private ?CheckReportStore $reports = null
|
||||
) {
|
||||
}
|
||||
|
||||
public function register(): void
|
||||
{
|
||||
add_action('admin_notices', [$this, 'renderNotice']);
|
||||
add_action(CompatibilityTab::LOAD_ACTION, [$this, 'removeOrphans']);
|
||||
add_action(CompatibilityTab::INTRO_ACTION, [$this, 'renderIntro']);
|
||||
add_action(CompatibilityTab::EXTRA_ACTION, [$this, 'renderExtras']);
|
||||
add_filter(CompatibilityTab::EXPLANATION_FILTER, [$this, 'explanation'], 10, 3);
|
||||
add_filter(CompatibilityTab::COLUMNS_FILTER, [$this, 'columns'], 10, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Only for administrators, only with Divi active, only while a feature is not usable – and not on the tab
|
||||
* "Compatibility", which shows the same table. Reads the cached matrix (D7), so no check runs on an admin page.
|
||||
*/
|
||||
public function renderNotice(): void
|
||||
{
|
||||
if (! current_user_can('manage_options') || $this->version() === '' || $this->onCompatibilityTab()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$unusable = ReleaseGate::status()->matrix->unusable();
|
||||
if ($unusable === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
printf(
|
||||
'<div class="notice notice-warning"><p>%s <a href="%s">%s</a></p></div>',
|
||||
esc_html(sprintf(
|
||||
/* translators: 1: Divi version, 2: comma separated feature names */
|
||||
__(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong -- WordPress.WP.I18n forbids splitting the text argument.
|
||||
'LIT MCP: Mit Divi %1$s sind diese Fähigkeiten nicht nutzbar: %2$s. Die betroffenen Tools lehnen ab, alle anderen laufen weiter.',
|
||||
'wp-lit-mcp'
|
||||
),
|
||||
$this->version(),
|
||||
implode(', ', array_map(
|
||||
static fn (FeatureStatus $status): string => $status->feature->label(),
|
||||
$unusable
|
||||
))
|
||||
)),
|
||||
esc_url(SettingsPage::url(CompatibilityTab::SLUG)),
|
||||
esc_html__('Details im Reiter „Compatibility“', 'wp-lit-mcp')
|
||||
);
|
||||
}
|
||||
|
||||
private function onCompatibilityTab(): bool
|
||||
{
|
||||
$screen = function_exists('get_current_screen') ? get_current_screen() : null;
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only tab switch.
|
||||
$tab = is_string($_GET['tab'] ?? null) ? sanitize_key(wp_unslash($_GET['tab'])) : '';
|
||||
|
||||
return is_object($screen) && $screen->id === SettingsPage::SCREEN_HOOK && $tab === CompatibilityTab::SLUG;
|
||||
}
|
||||
|
||||
/**
|
||||
* lit_mcp_compat_profile_load('divi'), before any output (the tab fires it for administrators on its own tab):
|
||||
* deletes the check pages a dead check left behind (WriteProbe::removeOrphans(): marked drafts with the check
|
||||
* title, older than ten minutes – no running check uses them). On a GET without nonce on purpose: it removes
|
||||
* only the plugin's own leftovers and is idempotent. renderExtras() only reports what was deleted (renderIntro()).
|
||||
*/
|
||||
public function removeOrphans(string $profileId): void
|
||||
{
|
||||
if ($profileId !== DiviProfile::ID || ! current_user_can('manage_options') || $this->version() === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->removed = WriteProbe::removeOrphans();
|
||||
}
|
||||
|
||||
/**
|
||||
* Columns of Divi's capability table: where the release comes from, the problems of the capability and the
|
||||
* tools it affects. Reads the overview once per table (the matrix is the same for every row).
|
||||
*
|
||||
* @param list<array<string, mixed>> $columns
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
public function columns(array $columns, string $profileId): array
|
||||
{
|
||||
if ($profileId !== DiviProfile::ID) {
|
||||
return $columns;
|
||||
}
|
||||
|
||||
$rows = null;
|
||||
$row = function (Capability $capability, CompatibilityMatrix $matrix) use (&$rows): array {
|
||||
$rows ??= array_column(($this->overview ?? FeatureOverview::withoutTools())->rows($matrix), null, 'id');
|
||||
|
||||
return $rows[$capability->id()] ?? [];
|
||||
};
|
||||
|
||||
return [
|
||||
...$columns,
|
||||
[
|
||||
'heading' => __('Quelle', 'wp-lit-mcp'),
|
||||
'cell' => fn (Capability $c, CompatibilityMatrix $m): array => array_values(array_filter(
|
||||
[$this->sourceLabel($row($c, $m)['source'] ?? null)]
|
||||
)),
|
||||
],
|
||||
[
|
||||
'heading' => __('Probleme', 'wp-lit-mcp'),
|
||||
'cell' => static fn (Capability $c, CompatibilityMatrix $m): array => $row($c, $m)['problems'] ?? [],
|
||||
],
|
||||
[
|
||||
'heading' => __('Betroffene Tools', 'wp-lit-mcp'),
|
||||
'cell' => static fn (Capability $c, CompatibilityMatrix $m): array => $row($c, $m)['tools'] ?? [],
|
||||
'code' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function sourceLabel(?string $source): string
|
||||
{
|
||||
return match ($source) {
|
||||
ReleaseStatus::SOURCE_CODE => __('Im Plugin als getestet hinterlegt', 'wp-lit-mcp'),
|
||||
ReleaseStatus::SOURCE_DATABASE => __('Von dir auf dieser Website freigegeben', 'wp-lit-mcp'),
|
||||
ReleaseStatus::SOURCE_AUTO => __('Automatisch geprüft', 'wp-lit-mcp'),
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* lit_mcp_compat_profile_intro('divi'), above the capability table: the deleted check pages, the next step and
|
||||
* what the check does (it writes to the site, so it says so).
|
||||
*/
|
||||
public function renderIntro(string $profileId): void
|
||||
{
|
||||
if ($profileId !== DiviProfile::ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->removed !== []) {
|
||||
printf(
|
||||
'<div class="notice notice-warning inline"><p>%s</p></div>',
|
||||
esc_html(sprintf(
|
||||
/* translators: %s: comma separated page IDs */
|
||||
__('Liegengebliebene Prüfseiten gelöscht: %s', 'wp-lit-mcp'),
|
||||
implode(', ', $this->removed)
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
$this->renderNextStep(ReleaseGate::status());
|
||||
echo '<p class="description">' . esc_html__(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong -- WordPress.WP.I18n forbids splitting the text argument.
|
||||
'Die Prüfung legt eine private Testseite an, prüft Lesen und Schreiben und löscht sie wieder.',
|
||||
'wp-lit-mcp'
|
||||
) . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter lit_mcp_compat_explanation: Divi says where its release comes from (plugin, automatic, this website)
|
||||
* instead of the generic sentence, so the section does not explain the state twice.
|
||||
*/
|
||||
public function explanation(string $text, string $profileId, ReleaseState $state): string
|
||||
{
|
||||
if ($profileId !== DiviProfile::ID) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$status = ReleaseGate::status();
|
||||
|
||||
return match (true) {
|
||||
$status->state === ReleaseState::Incompatible => __(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong -- WordPress.WP.I18n forbids splitting the text argument.
|
||||
'Divi hat Stellen geändert, die LIT MCP nutzt. Die betroffenen Fähigkeiten (Tabelle unten) bleiben gesperrt, bis ein Plugin-Update von LIT MCP eingespielt ist; alle anderen laufen weiter.',
|
||||
'wp-lit-mcp'
|
||||
),
|
||||
$status->state === ReleaseState::Untested => __(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong -- WordPress.WP.I18n forbids splitting the text argument.
|
||||
'Diese Version ist nicht geprüft. Die Tools der ungetesteten Fähigkeiten (Tabelle unten) lehnen ab, bis die Version freigegeben ist; alle anderen laufen weiter.',
|
||||
'wp-lit-mcp'
|
||||
),
|
||||
$status->source === ReleaseStatus::SOURCE_CODE => __(
|
||||
'Diese Version ist im Plugin als getestet hinterlegt.',
|
||||
'wp-lit-mcp'
|
||||
),
|
||||
$status->source === ReleaseStatus::SOURCE_AUTO => __(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong -- WordPress.WP.I18n forbids splitting the text argument.
|
||||
'Automatisch freigegeben: Alle Divi-Stellen, die LIT MCP nutzt, stimmen mit der geprüften Referenz überein.',
|
||||
'wp-lit-mcp'
|
||||
),
|
||||
default => $this->releasedOnThisSite($status),
|
||||
};
|
||||
}
|
||||
|
||||
private function releasedOnThisSite(ReleaseStatus $status): string
|
||||
{
|
||||
$user = get_userdata((int) ($status->release['user_id'] ?? 0));
|
||||
$releasedAt = (int) strtotime((string) ($status->release['released_at'] ?? ''));
|
||||
|
||||
return sprintf(
|
||||
/* translators: 1: date, 2: user name */
|
||||
__('Auf dieser Website freigegeben am %1$s von %2$s.', 'wp-lit-mcp'),
|
||||
date_i18n((string) get_option('date_format'), $releasedAt),
|
||||
is_object($user) && isset($user->display_name) ? (string) $user->display_name : '?'
|
||||
);
|
||||
}
|
||||
|
||||
/** lit_mcp_compat_profile_extra('divi'): the end of the Divi section (shown only while Divi is active). */
|
||||
public function renderExtras(string $profileId): void
|
||||
{
|
||||
if ($profileId !== DiviProfile::ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->renderAutomationNote();
|
||||
$reports = $this->reports ?? new CheckReportStore(DiviProfile::ID, DiviProfile::REPORT_VERSION_KEY);
|
||||
if ($reports->latest() !== null) {
|
||||
echo '<p class="description">' . esc_html__(
|
||||
'Optional: eine Divi-Seite im Visual Builder öffnen und speichern.',
|
||||
'wp-lit-mcp'
|
||||
) . '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
/** One sentence on what to do now; a released version needs none. */
|
||||
private function renderNextStep(ReleaseStatus $status): void
|
||||
{
|
||||
$text = match ($status->state) {
|
||||
ReleaseState::Untested => sprintf(
|
||||
/* translators: %s: Divi version */
|
||||
__(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong -- WordPress.WP.I18n forbids splitting the text.
|
||||
'Divi %s ist noch nicht geprüft. Starte die Prüfung; danach kannst du die Version freigeben.',
|
||||
'wp-lit-mcp'
|
||||
),
|
||||
$status->version
|
||||
),
|
||||
ReleaseState::Incompatible => sprintf(
|
||||
/* translators: %s: Divi version */
|
||||
__(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong -- WordPress.WP.I18n forbids splitting the text.
|
||||
'Divi %s hat Stellen geändert, die LIT MCP nutzt. Die betroffenen Tools bleiben gesperrt, bis ein Plugin-Update erscheint.',
|
||||
'wp-lit-mcp'
|
||||
),
|
||||
$status->version
|
||||
),
|
||||
default => null,
|
||||
};
|
||||
if ($text === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
printf(
|
||||
'<div class="notice notice-%s inline"><p><strong>%s</strong></p></div>',
|
||||
$status->state === ReleaseState::Incompatible ? 'error' : 'warning',
|
||||
esc_html($text)
|
||||
);
|
||||
}
|
||||
|
||||
/** E1: what the automatic release does and how to switch it off. */
|
||||
private function renderAutomationNote(): void
|
||||
{
|
||||
echo '<p class="description">' . esc_html__(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong -- WordPress.WP.I18n forbids splitting the text argument.
|
||||
'Automatik: Ist die installierte Divi-Version im Plugin nicht als getestet hinterlegt, gibt LIT MCP jede Fähigkeit einzeln frei, wenn alle benutzten Divi-Funktionen vorhanden sind, ihre Aufrufe (Signaturen) und die Fingerabdrücke nachgebauter Divi-Abläufe unverändert sind und alle Konstanten definiert sind. Das prüft Aufrufformen, nicht das Verhalten von Divi. Fehlt für eine Stelle die Referenz, ist die Fähigkeit ungetestet; weicht etwas ab, ist sie inkompatibel.',
|
||||
'wp-lit-mcp'
|
||||
) . '</p>';
|
||||
echo '<p class="description">' . sprintf(
|
||||
/* translators: %s: filter name in a code element */
|
||||
esc_html__(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong -- WordPress.WP.I18n forbids splitting the text argument.
|
||||
'Die Automatik abschalten: Filter %s auf false setzen (z. B. in einem mu-Plugin). Dann gelten nicht hinterlegte Divi-Versionen bis zur Freigabe als ungetestet.',
|
||||
'wp-lit-mcp'
|
||||
),
|
||||
'<code>lit_mcp_divi_auto_release</code>'
|
||||
) . '</p>';
|
||||
}
|
||||
|
||||
private function version(): string
|
||||
{
|
||||
return ($this->version ?? static fn (): string => DiviEnvironment::version() ?? '')();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user