diff --git a/changelog.txt b/changelog.txt
index e1013fc..1700450 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,5 +1,38 @@
== Changelog ==
+= 1.2.2 =
+
+_Release date: 2026-08-24_
+
+**Highlights**
+
+* Manager (by ROBOTSTXT) detection now uses the ecosystem presence constant ROBOTSTXT_MANAGER_NOTICED (Manager 1.6.2+), with a plugin-list scan fallback for older Manager versions
+
+**Changed**
+
+* `robotstxt_docmd_is_manager_active()` now checks the ROBOTSTXT_MANAGER_NOTICED presence constant (Manager 1.6.2+) and falls back to scanning the plugin list (get_plugins() + is_plugin_active()) when the constant is absent; same function name and return type, so callers are unaffected
+
+**Security**
+
+* `composer update` (phpstan 2.2.9, nette/schema 1.3.6 — dev tooling only); `composer audit` clean, no known CVEs
+
+**Compatibility**
+
+* WordPress: 4.2 - 7.1 (floor re-verified with WP-Compat; smoke-tested on 7.2-alpha)
+* PHP: 8.0 - 8.5 (floor re-verified with PHPCompatibility 5.6-8.5 full-range scan)
+
+**Translations**
+
+* Spanish (es_ES) and Catalan (ca): verified complete and in sync (156 strings, no changes needed)
+
+**Tests**
+
+* PHP Coding Standards: PHPCS with WordPress-Core, WordPress-Docs, WordPress-Extra — 0 errors
+* PHPStan: level 9, 0 errors
+* PHPCompatibility: PHP 8.0-8.5 validated (full-range scan 5.6-8.5)
+* WP-Compat: WordPress 4.2 floor validated
+* PHPUnit: 23 tests pass (17 plugin header tests + 6 new Manager-detection tests covering both detection paths)
+
= 1.2.1 =
_Release date: 2026-08-17_
diff --git a/readme.txt b/readme.txt
index 5c061f8..ac59063 100644
--- a/readme.txt
+++ b/readme.txt
@@ -4,7 +4,7 @@ Tags: github, documentation, markdown, sync, automation
Requires at least: 4.2
Tested up to: 7.1
Requires PHP: 8.0
-Stable tag: 1.2.1
+Stable tag: 1.2.2
License: GPLv3 or later
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@@ -189,6 +189,22 @@ Then go to Documentation → Settings, and you'll see a "Debug Tools" section at
For the complete changelog, see [changelog.txt](https://www.robotstxt.software/plugins/robotstxt-documentation-markdown/).
+= 1.2.2 - 2026-08-24 =
+
+**Changed**
+
+* Manager (by ROBOTSTXT) detection now uses the ecosystem presence constant `ROBOTSTXT_MANAGER_NOTICED` (Manager 1.6.2+), falling back to a plugin-list scan for older Manager versions — same function name and behavior for callers
+* New unit tests for both detection paths (constant defined / plugin-list fallback)
+
+**Compatibility**
+
+* WordPress: 4.2 - 7.1 (floor re-verified with WP-Compat; smoke-tested on 7.2-alpha)
+* PHP: 8.0 - 8.5 (floor re-verified with PHPCompatibility 5.6-8.5 full-range scan)
+
+**Translations**
+
+* Spanish (es_ES) and Catalan (ca): verified complete and in sync (156 strings, no changes needed)
+
= 1.2.1 - 2026-08-17 =
**Changed**
@@ -238,42 +254,11 @@ For the complete changelog, see [changelog.txt](https://www.robotstxt.software/p
* Inline `onclick` confirm on Delete replaced with a `data-confirm` attribute + delegated handler (CSP-friendlier, less fragile)
* Add Mapping "existing content" dropdown now queries only public post types (excludes attachments and the internal mapping CPT) with `no_found_rows` for better performance on large sites
-**Compatibility**
-
-* Minimum WordPress raised to 6.8 (latest stable + two previous majors)
-* Verified compatible with WordPress 7.1
-* Declared PHP minimum made consistent across `readme.txt`, `composer.json`, `phpstan.neon`, and the PHPCompatibility scan range — real minimum remains 8.0
-
-= 1.1.1 - 2026-06-08 =
-
-**Security**
-
-* CommonMark: disabled raw HTML passthrough (`html_input: strip`, `allow_unsafe_links: false`) — prevents stored XSS via a compromised upstream repository
-* Settings option now stored with `autoload = false` — encrypted GitHub token no longer loaded on every WordPress request
-* Replaced `serialize()` with `wp_json_encode()` in updater HMAC cache signature
-* Added strict base64 length validation before IV extraction in token decryption
-* Added `base64_decode()` return-value check in GitHub file content fetch
-
-**Fixed**
-
-* Admin notices now display results for all operations: mapping created, updated, deleted, sync complete, and errors — previously only "Settings saved" was shown
-* `wp_update_post()` return value now checked during sync — silent failures no longer reported as success
-* Wrong textdomain in updater class (`'robotstxt-smtp'` → `'robotstxt-documentation-markdown'`)
-
-**Changed**
-
-* Renamed `robotstxt-updater.php` → `class-robotstxt-updater.php` (WordPress file naming convention)
-* Minimum PHP version corrected to 8.0 (real minimum confirmed by PHPCompatibility scan — no PHP 8.1 or 8.2 specific features used)
-
-**Developer**
-
-* Added full tooling: `composer.json`, `phpstan.neon` (level 9), `.phpcs.xml`, `bin/deploy.sh`, `phpunit.xml.dist`
-* PHPCompatibility updated to 10.0.0-alpha2 (PHP 8.x feature detection)
-* PHPUnit: 17 plugin header tests added
-* PHPCS, PHPStan level 9, PHPUnit all pass with 0 errors
-
== Upgrade Notice ==
+= 1.2.2 =
+Manager (by ROBOTSTXT) detection updated: now uses the ROBOTSTXT_MANAGER_NOTICED presence constant (Manager 1.6.2+) with a fallback for older Manager versions.
+
= 1.2.1 =
Automatic updates now require the Manager (by ROBOTSTXT) plugin. A notice with the download link is shown on the Plugins and Settings pages.
diff --git a/robotstxt-documentation-markdown.php b/robotstxt-documentation-markdown.php
index e52d538..a6430a2 100644
--- a/robotstxt-documentation-markdown.php
+++ b/robotstxt-documentation-markdown.php
@@ -3,7 +3,7 @@
* Plugin Name: Documentation Markdown (by ROBOTSTXT)
* Plugin URI: https://www.robotstxt.software/plugins/robotstxt-documentation-markdown/
* Description: Synchronizes Markdown documentation from GitHub repositories to WordPress pages and posts automatically.
- * Version: 1.2.1
+ * Version: 1.2.2
* Requires at least: 4.2
* Requires PHP: 8.0
* Security: robotstxt@robotstxt.es
@@ -25,7 +25,7 @@ if ( ! defined( 'ABSPATH' ) ) {
}
// Define plugin constants.
-define( 'ROBOTSTXT_DOCMD_VERSION', '1.2.1' );
+define( 'ROBOTSTXT_DOCMD_VERSION', '1.2.2' );
define( 'ROBOTSTXT_DOCMD_PLUGIN_FILE', __FILE__ );
define( 'ROBOTSTXT_DOCMD_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'ROBOTSTXT_DOCMD_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
@@ -1676,17 +1676,41 @@ function robotstxt_docmd_debug_fix_crons() {
}
/**
- * Check whether the Manager (by ROBOTSTXT) plugin is active
+ * Check whether the Manager (by ROBOTSTXT) plugin is installed and active
*
* Updates for this plugin are delivered through the Manager plugin. This
- * helper detects whether it is installed and active.
+ * helper detects whether it is installed and active: it uses the ecosystem
+ * presence constant (Manager 1.6.2+) and falls back to a plugin-list scan
+ * for older Manager versions.
*
* @since 1.2.1
+ * @since 1.2.2 Switched to the ROBOTSTXT_MANAGER_NOTICED presence constant
+ * with a plugin-list scan fallback for Manager < 1.6.2.
*
* @return bool True if Manager (by ROBOTSTXT) is active, false otherwise.
*/
function robotstxt_docmd_is_manager_active(): bool {
- return defined( 'ROBOTSTXT_MANAGER_VERSION' );
+ if ( defined( 'ROBOTSTXT_MANAGER_NOTICED' ) && ROBOTSTXT_MANAGER_NOTICED ) {
+ return true;
+ }
+
+ if ( ! function_exists( 'get_plugins' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
+ }
+
+ foreach ( get_plugins() as $file => $data ) {
+ $slug = dirname( $file );
+
+ if ( '.' === $slug ) {
+ $slug = basename( $file, '.php' );
+ }
+
+ if ( 'robotstxt-manager' === $slug ) {
+ return is_plugin_active( $file );
+ }
+ }
+
+ return false;
}
/**
diff --git a/update.json b/update.json
deleted file mode 100644
index 4fa49e4..0000000
--- a/update.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "name": "Documentation Markdown (by ROBOTSTXT)",
- "slug": "robotstxt-documentation-markdown",
- "version": "1.2.1",
- "download_url": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown/releases/download/1.2.1/robotstxt-documentation-markdown-1.2.1.zip",
- "requires": "6.8",
- "requires_php": "8.0",
- "tested": "7.1",
- "last_updated": "2026-08-07",
- "author": "ROBOTSTXT",
- "author_profile": "https://www.robotstxt.es/",
- "homepage": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown",
- "description": "Synchronizes Markdown documentation from GitHub repositories to WordPress pages and posts automatically. Perfect for maintaining technical documentation, API references, knowledge bases, and more with version control.",
- "changelog": "",
- "sections": {
- "description": "
Documentation Markdown is a powerful WordPress plugin that enables seamless synchronization of Markdown documentation files from GitHub repositories into your WordPress site.
Key Features
- Automatic Synchronization: Schedule automatic syncs via WordPress Cron (hourly, twice daily, daily)
- Markdown to HTML: Convert GitHub Flavored Markdown to clean HTML using CommonMark
- Flexible Mapping: Map individual MD files to specific WordPress posts or pages
- Title from H1: Post titles are derived from the first Markdown H1 heading
- Link & Image Translation: Repo-relative links are rewritten to permalinks; images are sideloaded into the Media Library
- Secure: Encrypted GitHub token storage (HKDF-SHA256), full input validation & output escaping
- Translatable: Full internationalization support (i18n/l10n ready)
- Multi-Repository: Sync from multiple GitHub repos simultaneously
- Manual Sync: On-demand synchronization from admin interface
- Debug Tools: Built-in debugging tools (visible when WP_DEBUG is enabled)
Use Cases
- API Documentation - Keep your API docs in sync between GitHub and WordPress
- Technical Documentation - Maintain version-controlled technical docs
- Knowledge Base - Build a knowledge base powered by GitHub
- Blog Posts - Write blog posts in Markdown with Git workflow
- Product Documentation - Sync product documentation from your repository
Requirements
- PHP 8.0 or higher
- WordPress 6.8 or higher
- GitHub Personal Access Token (free)
",
- "changelog": ""
- },
- "banners": {
- "low": "",
- "high": ""
- },
- "icons": {
- "1x": "",
- "2x": ""
- }
-}
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 6194647..350ecaf 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -275,17 +275,17 @@
},
{
"name": "nette/schema",
- "version": "v1.3.5",
- "version_normalized": "1.3.5.0",
+ "version": "v1.3.6",
+ "version_normalized": "1.3.6.0",
"source": {
"type": "git",
"url": "https://github.com/nette/schema.git",
- "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002"
+ "reference": "c54350438cd6914616f790a49cb424605f421562"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002",
- "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002",
+ "url": "https://api.github.com/repos/nette/schema/zipball/c54350438cd6914616f790a49cb424605f421562",
+ "reference": "c54350438cd6914616f790a49cb424605f421562",
"shasum": ""
},
"require": {
@@ -299,7 +299,7 @@
"phpstan/phpstan": "^2.1.39@stable",
"tracy/tracy": "^2.8"
},
- "time": "2026-02-23T03:47:12+00:00",
+ "time": "2026-08-16T21:58:41+00:00",
"type": "library",
"extra": {
"branch-alias": {
@@ -339,7 +339,7 @@
],
"support": {
"issues": "https://github.com/nette/schema/issues",
- "source": "https://github.com/nette/schema/tree/v1.3.5"
+ "source": "https://github.com/nette/schema/tree/v1.3.6"
},
"install-path": "../nette/schema"
},
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
index dfced0b..a249c41 100644
--- a/vendor/composer/installed.php
+++ b/vendor/composer/installed.php
@@ -38,9 +38,9 @@
'dev_requirement' => false,
),
'nette/schema' => array(
- 'pretty_version' => 'v1.3.5',
- 'version' => '1.3.5.0',
- 'reference' => 'f0ab1a3cda782dbc5da270d28545236aa80c4002',
+ 'pretty_version' => 'v1.3.6',
+ 'version' => '1.3.6.0',
+ 'reference' => 'c54350438cd6914616f790a49cb424605f421562',
'type' => 'library',
'install_path' => __DIR__ . '/../nette/schema',
'aliases' => array(),
diff --git a/vendor/nette/schema/readme.md b/vendor/nette/schema/readme.md
index ce9d0d1..7e52878 100644
--- a/vendor/nette/schema/readme.md
+++ b/vendor/nette/schema/readme.md
@@ -126,7 +126,7 @@ Expect::null()
Expect::array($default = [])
```
-And then all types [supported by the Validators](https://doc.nette.org/validators#toc-validation-rules) via `Expect::type('scalar')` or abbreviated `Expect::scalar()`. Also class or interface names are accepted, e.g. `Expect::type('AddressEntity')`.
+And then all types [supported by the Validators](https://doc.nette.org/validators#toc-expected-types) via `Expect::type('scalar')` or abbreviated `Expect::scalar()`. Also class or interface names are accepted, e.g. `Expect::type('AddressEntity')`.
You can also use union notation:
diff --git a/vendor/nette/schema/src/Schema/Context.php b/vendor/nette/schema/src/Schema/Context.php
index a002d5c..b42b1ed 100644
--- a/vendor/nette/schema/src/Schema/Context.php
+++ b/vendor/nette/schema/src/Schema/Context.php
@@ -10,6 +10,9 @@ namespace Nette\Schema;
use function count;
+/**
+ * Accumulates errors and warnings during schema validation and tracks the current path.
+ */
final class Context
{
public bool $skipDefaults = false;
@@ -44,7 +47,10 @@ final class Context
}
- /** @return \Closure(): bool */
+ /**
+ * Returns a closure that returns true as long as no new errors have been added since the call.
+ * @return \Closure(): bool
+ */
public function createChecker(): \Closure
{
$count = count($this->errors);
diff --git a/vendor/nette/schema/src/Schema/Elements/AnyOf.php b/vendor/nette/schema/src/Schema/Elements/AnyOf.php
index 09e21c9..582cf13 100644
--- a/vendor/nette/schema/src/Schema/Elements/AnyOf.php
+++ b/vendor/nette/schema/src/Schema/Elements/AnyOf.php
@@ -14,6 +14,9 @@ use Nette\Schema\Schema;
use function array_merge, array_unique, implode, is_array;
+/**
+ * Schema that accepts any of a fixed set of values or sub-schemas (union type / enumeration).
+ */
final class AnyOf implements Schema
{
use Base;
@@ -32,6 +35,9 @@ final class AnyOf implements Schema
}
+ /**
+ * Sets the first variant as the default value (instead of null).
+ */
public function firstIsDefault(): self
{
$this->default = $this->set[0];
@@ -39,6 +45,9 @@ final class AnyOf implements Schema
}
+ /**
+ * Allows null as an accepted value in addition to the defined variants.
+ */
public function nullable(): self
{
$this->set[] = null;
@@ -46,6 +55,9 @@ final class AnyOf implements Schema
}
+ /**
+ * Allows the value to be a DynamicParameter as an accepted variant.
+ */
public function dynamic(): self
{
$this->set[] = new Type(Nette\Schema\DynamicParameter::class);
@@ -88,10 +100,13 @@ final class AnyOf implements Schema
foreach ($this->set as $item) {
if ($item instanceof Schema) {
$dolly = new Context;
+ $dolly->skipDefaults = $context->skipDefaults;
+ $dolly->isKey = $context->isKey;
$dolly->path = $context->path;
$res = $item->complete($item->normalize($value, $dolly), $dolly);
if (!$dolly->errors) {
$context->warnings = array_merge($context->warnings, $dolly->warnings);
+ $context->dynamics = array_merge($context->dynamics, $dolly->dynamics);
return $res;
}
diff --git a/vendor/nette/schema/src/Schema/Elements/Base.php b/vendor/nette/schema/src/Schema/Elements/Base.php
index 6e83eb7..c4e3dd6 100644
--- a/vendor/nette/schema/src/Schema/Elements/Base.php
+++ b/vendor/nette/schema/src/Schema/Elements/Base.php
@@ -43,7 +43,10 @@ trait Base
}
- /** @param callable(mixed): mixed $handler */
+ /**
+ * Sets a pre-normalization callback applied to the raw input value before any validation.
+ * @param callable(mixed): mixed $handler
+ */
public function before(callable $handler): self
{
$this->before = $handler(...);
@@ -51,13 +54,19 @@ trait Base
}
+ /**
+ * Casts the validated value to a built-in type or instantiates the given class.
+ */
public function castTo(string $type): self
{
return $this->transform(Helpers::getCastStrategy($type));
}
- /** @param callable(mixed, Context): mixed $handler */
+ /**
+ * Adds a post-validation transformation callback. The handler may also report errors via Context.
+ * @param callable(mixed, Context): mixed $handler
+ */
public function transform(callable $handler): self
{
$this->transforms[] = $handler(...);
@@ -65,7 +74,10 @@ trait Base
}
- /** @param callable(mixed): bool $handler */
+ /**
+ * Adds a custom validation assertion; optionally describe it for error messages.
+ * @param callable(mixed): bool $handler
+ */
public function assert(callable $handler, ?string $description = null): self
{
$expected = $description ?? (is_string($handler) ? "$handler()" : '#' . count($this->transforms));
@@ -78,11 +90,14 @@ trait Base
Nette\Schema\Message::FailedAssertion,
['value' => $value, 'assertion' => $expected],
);
+ return null;
});
}
- /** Marks as deprecated */
+ /**
+ * Marks the item as deprecated; emits a warning with the given message when the item is used.
+ */
public function deprecated(string $message = 'The item %path% is deprecated.'): self
{
$this->deprecated = $message;
@@ -138,7 +153,7 @@ trait Base
}
- /** @deprecated use Nette\Schema\Validators::validateType() */
+ /** @deprecated use Nette\Schema\Helpers::validateType() */
private function doValidate(mixed $value, string $expected, Context $context): bool
{
$isOk = $context->createChecker();
@@ -148,7 +163,7 @@ trait Base
/**
- * @deprecated use Nette\Schema\Validators::validateRange()
+ * @deprecated use Nette\Schema\Helpers::validateRange()
* @param array{?float, ?float} $range
*/
private static function doValidateRange(mixed $value, array $range, Context $context, string $types = ''): bool
diff --git a/vendor/nette/schema/src/Schema/Elements/Structure.php b/vendor/nette/schema/src/Schema/Elements/Structure.php
index 1abf49f..a6cf02d 100644
--- a/vendor/nette/schema/src/Schema/Elements/Structure.php
+++ b/vendor/nette/schema/src/Schema/Elements/Structure.php
@@ -39,6 +39,9 @@ final class Structure implements Schema
}
+ /**
+ * Not supported for structures; always throws.
+ */
public function default(mixed $value): self
{
throw new Nette\InvalidStateException('Structure cannot have default value.');
@@ -59,6 +62,9 @@ final class Structure implements Schema
}
+ /**
+ * Allows extra keys not defined in the shape, validating their values against the given type.
+ */
public function otherItems(string|Schema $type = 'mixed'): self
{
$this->otherItems = $type instanceof Schema ? $type : new Type($type);
@@ -66,6 +72,9 @@ final class Structure implements Schema
}
+ /**
+ * When enabled, properties whose value equals the default are omitted from the output.
+ */
public function skipDefaults(bool $state = true): self
{
$this->skipDefaults = $state;
@@ -73,7 +82,10 @@ final class Structure implements Schema
}
- /** @param Schema[]|self $shape */
+ /**
+ * Creates a new structure by merging this shape with additional properties.
+ * @param Schema[]|self $shape
+ */
public function extend(array|self $shape): self
{
$shape = $shape instanceof self ? $shape->items : $shape;
@@ -203,8 +215,17 @@ final class Structure implements Schema
public function completeDefault(Context $context): mixed
{
- return $this->required
- ? $this->complete([], $context)
- : null;
+ if (!$this->required) {
+ return null;
+ }
+
+ // the item is missing in the input, do not report it as used deprecated
+ $deprecated = $this->deprecated;
+ $this->deprecated = null;
+ try {
+ return $this->complete([], $context);
+ } finally {
+ $this->deprecated = $deprecated;
+ }
}
}
diff --git a/vendor/nette/schema/src/Schema/Elements/Type.php b/vendor/nette/schema/src/Schema/Elements/Type.php
index 4413f69..880f4bb 100644
--- a/vendor/nette/schema/src/Schema/Elements/Type.php
+++ b/vendor/nette/schema/src/Schema/Elements/Type.php
@@ -11,6 +11,7 @@ use Nette\Schema\Context;
use Nette\Schema\DynamicParameter;
use Nette\Schema\Helpers;
use Nette\Schema\Schema;
+use Nette\Utils\Validators;
use function array_key_exists, array_pop, implode, is_array, str_replace, strpos;
@@ -36,6 +37,9 @@ final class Type implements Schema
}
+ /**
+ * Allows the value to be null in addition to the declared type.
+ */
public function nullable(): self
{
$this->type = 'null|' . $this->type;
@@ -43,6 +47,9 @@ final class Type implements Schema
}
+ /**
+ * Controls whether the default value is merged with the input array (enabled by default).
+ */
public function mergeDefaults(bool $state = true): self
{
$this->merge = $state;
@@ -50,6 +57,9 @@ final class Type implements Schema
}
+ /**
+ * Allows the value to be a DynamicParameter, which is recorded for deferred validation.
+ */
public function dynamic(): self
{
$this->type = DynamicParameter::class . '|' . $this->type;
@@ -86,6 +96,9 @@ final class Type implements Schema
}
+ /**
+ * Sets a regex pattern the string value must match entirely (anchored to start and end).
+ */
public function pattern(?string $pattern): self
{
$this->pattern = $pattern;
@@ -162,7 +175,7 @@ final class Type implements Schema
$merge = false;
}
- if ($value === null && is_array($this->default)) {
+ if ($value === null && is_array($this->default) && !Validators::is(null, $this->type)) {
$value = []; // is unable to distinguish null from array in NEON
}
@@ -173,13 +186,13 @@ final class Type implements Schema
$isOk() && Helpers::validateRange($value, $this->range, $context, $this->type);
$isOk() && $value !== null && $this->pattern !== null && Helpers::validatePattern($value, $this->pattern, $context);
$isOk() && is_array($value) && $this->validateItems($value, $context);
- $isOk() && $merge && $value = Helpers::merge($value, $this->default);
+ $isOk() && $merge && $value !== null && $value = Helpers::merge($value, $this->default);
$isOk() && $value = $this->doTransform($value, $context);
if (!$isOk()) {
return null;
}
- if ($value instanceof DynamicParameter) {
+ if ($value instanceof DynamicParameter && $this->type !== DynamicParameter::class) {
$expected = $this->type . ($this->range === [null, null] ? '' : ':' . implode('..', $this->range));
$context->dynamics[] = [$value, str_replace(DynamicParameter::class . '|', '', $expected), $context->path];
}
@@ -190,7 +203,7 @@ final class Type implements Schema
/** @param array $value */
private function validateItems(array &$value, Context $context): void
{
- if (!$this->itemsValue) {
+ if (!($itemsValue = $this->itemsValue)) {
return;
}
@@ -198,9 +211,15 @@ final class Type implements Schema
foreach ($value as $key => $val) {
$context->path[] = $key;
$context->isKey = true;
+ $isKeyOk = $context->createChecker();
$key = $this->itemsKey ? $this->itemsKey->complete($key, $context) : $key;
$context->isKey = false;
- $res[$key ?? ''] = $this->itemsValue->complete($val, $context);
+ $keyOk = $isKeyOk();
+ $val = $itemsValue->complete($val, $context);
+ if ($keyOk) {
+ $res[$key] = $val;
+ }
+
array_pop($context->path);
}
$value = $res;
diff --git a/vendor/nette/schema/src/Schema/Expect.php b/vendor/nette/schema/src/Schema/Expect.php
index 64e31f2..44cf25b 100644
--- a/vendor/nette/schema/src/Schema/Expect.php
+++ b/vendor/nette/schema/src/Schema/Expect.php
@@ -42,26 +42,38 @@ final class Expect
}
+ /**
+ * Creates a schema for a custom type expression (e.g., 'int|string', 'null|float').
+ */
public static function type(string $type): Type
{
return new Type($type);
}
+ /**
+ * Creates a union schema that accepts any of the given values or sub-schemas.
+ */
public static function anyOf(mixed ...$set): AnyOf
{
return new AnyOf(...$set);
}
- /** @param Schema[] $shape */
+ /**
+ * Creates a structure schema with defined properties; output is stdClass.
+ * @param Schema[] $shape
+ */
public static function structure(array $shape): Structure
{
return new Structure($shape);
}
- /** @param array $items */
+ /**
+ * Generates a structure schema from a class instance by reflecting its properties or constructor parameters.
+ * @param array $items Optional overrides for specific properties.
+ */
public static function from(object $object, array $items = []): Structure
{
$ro = new \ReflectionObject($object);
@@ -95,6 +107,8 @@ final class Expect
/**
+ * Creates an array schema. When passed Schema elements, behaves like structure() but outputs an array.
+ * Without Schema elements, creates a plain array type with the given default value.
* @param mixed[] $shape
*/
public static function array(?array $shape = []): Structure|Type
@@ -106,12 +120,18 @@ final class Expect
}
+ /**
+ * Creates an associative or indexed array schema where every value matches the given type.
+ */
public static function arrayOf(string|Schema $valueType, string|Schema|null $keyType = null): Type
{
return (new Type('array'))->items($valueType, $keyType);
}
+ /**
+ * Creates a list schema (sequentially indexed from 0) where every element matches the given type.
+ */
public static function listOf(string|Schema $type): Type
{
return (new Type('list'))->items($type);
diff --git a/vendor/nette/schema/src/Schema/Helpers.php b/vendor/nette/schema/src/Schema/Helpers.php
index 79a4a3c..e15bf50 100644
--- a/vendor/nette/schema/src/Schema/Helpers.php
+++ b/vendor/nette/schema/src/Schema/Helpers.php
@@ -9,7 +9,7 @@ namespace Nette\Schema;
use Nette;
use Nette\Utils\Reflection;
-use function count, explode, get_debug_type, implode, in_array, is_array, is_float, is_int, is_object, is_scalar, is_string, method_exists, preg_match, preg_quote, preg_replace, preg_replace_callback, settype, str_replace, strlen, trim, var_export;
+use function array_map, count, explode, get_debug_type, implode, in_array, is_array, is_float, is_int, is_object, is_scalar, is_string, is_subclass_of, method_exists, preg_match, preg_quote, preg_replace, preg_replace_callback, settype, str_replace, strlen, trim, var_export;
/**
@@ -54,6 +54,9 @@ final class Helpers
}
+ /**
+ * Returns the type of a property or parameter as a string, or null if not determinable.
+ */
public static function getPropertyType(\ReflectionProperty|\ReflectionParameter $prop): ?string
{
if ($type = Nette\Utils\Type::fromReflection($prop)) {
@@ -89,6 +92,9 @@ final class Helpers
}
+ /**
+ * Formats a value for use in error messages (e.g., 'hello', true, object stdClass).
+ */
public static function formatValue(mixed $value): string
{
if ($value instanceof DynamicParameter) {
@@ -105,6 +111,9 @@ final class Helpers
}
+ /**
+ * Adds a TypeMismatch error to the context if the value does not match the expected type.
+ */
public static function validateType(mixed $value, string $expected, Context $context): void
{
if (!Nette\Utils\Validators::is($value, $expected)) {
@@ -119,7 +128,10 @@ final class Helpers
}
- /** @param array{?float, ?float} $range */
+ /**
+ * Adds a range error to the context if the value (or its length for strings/arrays) is outside the given range.
+ * @param array{?float, ?float} $range
+ */
public static function validateRange(mixed $value, array $range, Context $context, string $types = ''): void
{
if (is_array($value) || is_string($value)) {
@@ -146,7 +158,10 @@ final class Helpers
}
- /** @param array{?float, ?float} $range */
+ /**
+ * Checks whether a value falls within the given [min, max] range (null means no bound).
+ * @param array{?float, ?float} $range
+ */
public static function isInRange(mixed $value, array $range): bool
{
return ($range[0] === null || $value >= $range[0])
@@ -154,6 +169,9 @@ final class Helpers
}
+ /**
+ * Adds a PatternMismatch error to the context if the value does not match the pattern.
+ */
public static function validatePattern(string $value, string $pattern, Context $context): void
{
if (!preg_match("\x01^(?:$pattern)$\x01Du", $value)) {
@@ -166,7 +184,10 @@ final class Helpers
}
- /** @return \Closure(mixed): mixed */
+ /**
+ * Returns a closure that casts a value to the given type (built-in, backed enum, class with constructor, or plain class).
+ * @return \Closure(mixed, Context): mixed
+ */
public static function getCastStrategy(string $type): \Closure
{
if (Nette\Utils\Validators::isBuiltinType($type)) {
@@ -174,12 +195,37 @@ final class Helpers
settype($value, $type);
return $value;
};
- } elseif (method_exists($type, '__construct')) {
- return static fn($value) => is_array($value) || $value instanceof \stdClass
- ? new $type(...(array) $value)
- : new $type($value);
- } else {
- return static fn($value) => Nette\Utils\Arrays::toObject((array) $value, new $type);
+
+ } elseif (is_subclass_of($type, \BackedEnum::class)) {
+ return static function ($value, Context $context) use ($type) {
+ try {
+ return $type::from($value);
+ } catch (\TypeError | \ValueError) {
+ $context->addError(
+ 'The %label% %path% expects to be %expected%, %value% given.',
+ Message::TypeMismatch,
+ ['value' => $value, 'expected' => implode('|', array_map(fn(\BackedEnum $case) => self::formatValue($case->value), $type::cases()))],
+ );
+ return null;
+ }
+ };
+
+ } elseif (is_subclass_of($type, \UnitEnum::class)) {
+ throw new Nette\InvalidStateException("Cannot cast value to pure enum $type.");
}
+
+ $factory = method_exists($type, '__construct')
+ ? static fn($value) => is_array($value) || $value instanceof \stdClass
+ ? new $type(...(array) $value)
+ : new $type($value)
+ : static fn($value) => Nette\Utils\Arrays::toObject((array) $value, new $type);
+
+ return static function ($value) use ($factory, $type) {
+ try {
+ return $factory($value);
+ } catch (\Error $e) {
+ throw new Nette\InvalidStateException("Unable to cast value to $type: " . $e->getMessage(), 0, $e);
+ }
+ };
}
}
diff --git a/vendor/nette/schema/src/Schema/Message.php b/vendor/nette/schema/src/Schema/Message.php
index 4923f14..f06f172 100644
--- a/vendor/nette/schema/src/Schema/Message.php
+++ b/vendor/nette/schema/src/Schema/Message.php
@@ -7,9 +7,12 @@
namespace Nette\Schema;
-use function implode, preg_replace_callback;
+use function array_key_exists, implode, preg_replace_callback;
+/**
+ * Represents a single validation error or warning with a message template, error code, path, and variables.
+ */
final class Message
{
/** variables: {value: mixed, expected: string} */
@@ -72,6 +75,9 @@ final class Message
}
+ /**
+ * Formats the message template by substituting %variable% placeholders with their values.
+ */
public function toString(): string
{
$vars = $this->variables;
@@ -83,7 +89,9 @@ final class Message
return preg_replace_callback('~( ?)%(\w+)%~', function ($m) use ($vars) {
[, $space, $key] = $m;
- return $vars[$key] === null ? '' : $space . $vars[$key];
+ return array_key_exists($key, $vars)
+ ? ($vars[$key] === null ? '' : $space . $vars[$key])
+ : $m[0];
}, $this->message);
}
}
diff --git a/vendor/nette/schema/src/Schema/Processor.php b/vendor/nette/schema/src/Schema/Processor.php
index 4a2ef7b..3ce576a 100644
--- a/vendor/nette/schema/src/Schema/Processor.php
+++ b/vendor/nette/schema/src/Schema/Processor.php
@@ -21,6 +21,9 @@ final class Processor
private bool $skipDefaults = false;
+ /**
+ * When enabled, properties with default values are omitted from the output.
+ */
public function skipDefaults(bool $value = true): void
{
$this->skipDefaults = $value;
@@ -35,9 +38,9 @@ final class Processor
{
$this->createContext();
$data = $schema->normalize($data, $this->context);
- $this->throwsErrors();
+ $this->throwErrors();
$data = $schema->complete($data, $this->context);
- $this->throwsErrors();
+ $this->throwErrors();
return $data;
}
@@ -54,30 +57,35 @@ final class Processor
$first = true;
foreach ($dataset as $data) {
$data = $schema->normalize($data, $this->context);
- $this->throwsErrors();
+ $this->throwErrors();
$flatten = $first ? $data : $schema->merge($data, $flatten);
$first = false;
}
$data = $schema->complete($flatten, $this->context);
- $this->throwsErrors();
+ $this->throwErrors();
return $data;
}
- /** @return list */
+ /**
+ * Returns all deprecation warnings collected during the last processing run.
+ * @return list
+ */
public function getWarnings(): array
{
$res = [];
- foreach ($this->context->warnings as $message) {
- $res[] = $message->toString();
+ if (isset($this->context)) {
+ foreach ($this->context->warnings as $message) {
+ $res[] = $message->toString();
+ }
}
return $res;
}
- private function throwsErrors(): void
+ private function throwErrors(): void
{
if ($this->context->errors) {
throw new ValidationException(null, $this->context->errors);
diff --git a/vendor/nette/schema/src/Schema/Schema.php b/vendor/nette/schema/src/Schema/Schema.php
index 1616f64..619ebcf 100644
--- a/vendor/nette/schema/src/Schema/Schema.php
+++ b/vendor/nette/schema/src/Schema/Schema.php
@@ -8,27 +8,31 @@
namespace Nette\Schema;
+/**
+ * Defines the contract for schema elements used in data validation and normalization.
+ */
interface Schema
{
/**
- * Normalization.
+ * Applies pre-processing transformations to the raw input value (e.g., via before() hooks).
* @return mixed
*/
function normalize(mixed $value, Context $context);
/**
- * Merging.
+ * Merges two normalized values, with $value taking priority over $base.
* @return mixed
*/
function merge(mixed $value, mixed $base);
/**
- * Validation and finalization.
+ * Validates the value and applies defaults, transforms, and assertions.
* @return mixed
*/
function complete(mixed $value, Context $context);
/**
+ * Returns the default value, or adds a missing-item error if the field is required.
* @return mixed
*/
function completeDefault(Context $context);
diff --git a/vendor/nette/schema/src/Schema/ValidationException.php b/vendor/nette/schema/src/Schema/ValidationException.php
index 396320f..cefca43 100644
--- a/vendor/nette/schema/src/Schema/ValidationException.php
+++ b/vendor/nette/schema/src/Schema/ValidationException.php
@@ -18,13 +18,16 @@ class ValidationException extends Nette\InvalidStateException
public function __construct(
?string $message,
/** @var list */
- private array $messages = [],
+ private readonly array $messages = [],
) {
parent::__construct($message ?? $messages[0]->toString());
}
- /** @return list */
+ /**
+ * Returns all validation error messages as formatted strings.
+ * @return list
+ */
public function getMessages(): array
{
$res = [];
@@ -36,7 +39,10 @@ class ValidationException extends Nette\InvalidStateException
}
- /** @return list */
+ /**
+ * Returns all validation error messages as Message objects.
+ * @return list
+ */
public function getMessageObjects(): array
{
return $this->messages;