- m-1: the no-change Divi result carries preview_url/edit_url too - m-2: overview names replace-text's per-page tree_rev - m-3: divi-shortform documents validation_failed vs not_found for unknown IDs - m-4: DiviWriter/DiviPageLoader final (no mocks left), neutral NewDraft log detail, snapshots are Etappe 4 - m-5: Tested up to 7.1, changelog completed - m-6: content-create warns when the extras changed the stored layout - m-7: deleting a whole section outlines nothing instead of the next one - m-8: uninstall removes the user meta lit_mcp_log_per_page; readme lists it Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
92 lines
2.7 KiB
PHP
92 lines
2.7 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace LIT\MCP;
|
||
|
||
defined('ABSPATH') || exit;
|
||
|
||
/**
|
||
* Admin-configurable limits (settings tab "Limits", spec §8.4).
|
||
*/
|
||
final class Limits
|
||
{
|
||
public const OPTION = 'lit_mcp_limits';
|
||
public const DEFAULTS = ['upload_mb' => 5, 'log_retention_days' => 90, 'snapshot_count' => 20];
|
||
/**
|
||
* Hard cap for transfers held in memory as a whole and passed through the MCP response or
|
||
* request as Base64 (lit-media-download, lit/media-upload): above it, PHP memory and the
|
||
* JSON payload become the problem, whatever upload_mb says. URL imports stream to disk
|
||
* and only use upload_mb.
|
||
*/
|
||
public const INLINE_MAX_MB = 10;
|
||
private const BOUNDS = ['upload_mb' => [1, 32], 'log_retention_days' => [1, 3650], 'snapshot_count' => [1, 100]];
|
||
|
||
public function uploadBytes(): int
|
||
{
|
||
return $this->uploadMegabytes() * 1024 * 1024;
|
||
}
|
||
|
||
public function uploadMegabytes(): int
|
||
{
|
||
return $this->all()['upload_mb'];
|
||
}
|
||
|
||
/** Limit for Base64 transfers: the upload limit, at most INLINE_MAX_MB. */
|
||
public function inlineBytes(): int
|
||
{
|
||
return $this->inlineMegabytes() * 1024 * 1024;
|
||
}
|
||
|
||
public function inlineMegabytes(): int
|
||
{
|
||
return min($this->uploadMegabytes(), self::INLINE_MAX_MB);
|
||
}
|
||
|
||
public function logRetentionDays(): int
|
||
{
|
||
return $this->all()['log_retention_days'];
|
||
}
|
||
|
||
/** Reserved for design-system snapshots (Etappe 4, spec §12); LimitsTab keeps the field hidden until then. */
|
||
public function snapshotCount(): int
|
||
{
|
||
return $this->all()['snapshot_count'];
|
||
}
|
||
|
||
/** @return array{upload_mb: int, log_retention_days: int, snapshot_count: int} */
|
||
public function all(): array
|
||
{
|
||
return self::sanitize(get_option(self::OPTION, []));
|
||
}
|
||
|
||
/**
|
||
* The [min, max] each field is clamped to by sanitize() – for admin UI form attributes,
|
||
* so they can't drift from the actual bounds.
|
||
*
|
||
* @return array<string, array{int, int}>
|
||
*/
|
||
public static function bounds(): array
|
||
{
|
||
return self::BOUNDS;
|
||
}
|
||
|
||
/**
|
||
* Settings API sanitize callback: always returns all keys, clamped to their bounds.
|
||
*
|
||
* @return array{upload_mb: int, log_retention_days: int, snapshot_count: int}
|
||
*/
|
||
public static function sanitize(mixed $input): array
|
||
{
|
||
$input = is_array($input) ? $input : [];
|
||
$clean = self::DEFAULTS;
|
||
foreach (self::BOUNDS as $key => [$min, $max]) {
|
||
if (isset($input[$key]) && is_numeric($input[$key])) {
|
||
$clean[$key] = max($min, min($max, (int) $input[$key]));
|
||
}
|
||
}
|
||
|
||
return $clean;
|
||
}
|
||
}
|