-
- HTML;
- }
-
- /**
- * Do not enforce unique pinned posts if it is not enabled.
- */
- public function test_do_not_enforce_unique_pinned_posts_if_not_enabled() {
- $pinned_post = static::factory()->post->as_models()->create_and_get(
- [
- 'post_title' => 'Pinned Post 1',
- ]
- );
- $pinned_post_id = $pinned_post->id();
-
- $content = $this->create_html_content( $pinned_post_id );
-
- $test_post = static::factory()
- ->post
- ->with_meta(
- [
- 'wp_curate_deduplication' => '1',
- 'wp_curate_unique_pinned_posts' => '0',
- ]
- )
- ->create_and_get( [ 'post_content' => $content ] );
-
- $page = $this->get( $test_post )->assertOk();
-
- $this->assertEquals( 2, substr_count( $page->get_content(), 'Pinned Post 1' ) );
- }
-
- /**
- * If configured, pinned posts should only display once regardless of where on the page
- * the post was pinned at.
- */
- public function test_pinned_post_only_displays_once() {
- $pinned_post = static::factory()->post->as_models()->create_and_get(
- [
- 'post_title' => 'Pinned Post 2',
- ]
- );
- $pinned_post_id = $pinned_post->id();
-
- $content = $this->create_html_content( $pinned_post_id );
-
- $test_post = static::factory()
- ->post
- ->with_meta(
- [
- 'wp_curate_deduplication' => '1',
- 'wp_curate_unique_pinned_posts' => '1',
- ]
- )
- ->create_and_get( [ 'post_content' => $content ] );
-
- $page = $this->get( $test_post )->assertOk();
-
- $this->assertEquals( 1, substr_count( $page->get_content(), 'Pinned Post 2' ) );
- }
-}
diff --git a/tests/unit/ExampleUnitTest.php b/tests/unit/ExampleUnitTest.php
deleted file mode 100644
index e9c1bc97..00000000
--- a/tests/unit/ExampleUnitTest.php
+++ /dev/null
@@ -1,24 +0,0 @@
-assertTrue( true );
- }
-}
diff --git a/vendor/alleyinteractive/composer-wordpress-autoloader/CHANGELOG.md b/vendor/alleyinteractive/composer-wordpress-autoloader/CHANGELOG.md
new file mode 100644
index 00000000..1bd5d96c
--- /dev/null
+++ b/vendor/alleyinteractive/composer-wordpress-autoloader/CHANGELOG.md
@@ -0,0 +1,62 @@
+# Changelog
+
+All notable changes to `alleyinteractive/composer-wordpress-autoloader` will be
+documented in this file.
+
+## Unreleased
+
+## v1.1.0
+
+### Changed
+
+- Dropped PHP 7.4 support, adding PHP 8.2 and 8.3 support explicitly with testing.
+- Added testing against Windows.
+
+### Fixed
+
+- Fixed issue with `wordpress-autoload.php` file not properly loading on Windows.
+
+## v1.0.0
+
+No changes, tagging a stable release.
+
+## v0.8.0
+
+- Automatically translate the `vendor-dir` and set the autoloaded files relative to the root directory of the project.
+
+## v0.7.0
+
+- Add APCu autoloader.
+- Bump `alleyinteractive/wordpress-autoloader` to `v1.1.0`.
+
+## v0.6.0
+
+- Simplify injection of autoloader.
+- Automatically load the autoloader inside of `vendor/autoload.php` without the
+ need to load `vendor/wordpress-autoload.php` manually.
+
+## v0.4.1
+
+### Updated
+
+* Fix Composer Injection to `vendor/autoload.php` in https://github.com/alleyinteractive/composer-wordpress-autoloader/pull/10
+
+## v0.4.0
+
+### Added
+
+- Bump alleyinteractive/wordpress-autoloader to 0.2 by @srtfisher in https://github.com/alleyinteractive/composer-wordpress-autoloader/pull/7
+- Supports PHP 8.1
+
+## 0.3.0
+
+- Remove specific Composer version dependency.
+
+## 0.2.0
+
+- Updates autoloader to use non-hard-coded paths.
+- Adds support for dependencies to autoload files as well, fixes [#3](https://github.com/alleyinteractive/composer-wordpress-autoloader/issues/3).
+
+## 0.1.0
+
+- Initial release.
diff --git a/vendor/alleyinteractive/composer-wordpress-autoloader/README.md b/vendor/alleyinteractive/composer-wordpress-autoloader/README.md
new file mode 100644
index 00000000..ee4d45ea
--- /dev/null
+++ b/vendor/alleyinteractive/composer-wordpress-autoloader/README.md
@@ -0,0 +1,94 @@
+# Composer WordPress Autoloader
+
+[![Latest Version on Packagist](https://img.shields.io/packagist/v/alleyinteractive/composer-wordpress-autoloader.svg?style=flat-square)](https://packagist.org/packages/alleyinteractive/composer-wordpress-autoloader)
+[![Tests](https://github.com/alleyinteractive/composer-wordpress-autoloader/actions/workflows/tests.yml/badge.svg)](https://github.com/alleyinteractive/composer-wordpress-autoloader/actions/workflows/tests.yml)
+
+Autoload WordPress files configured via Composer that support the [Wordpress
+Coding
+Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/php/)
+using
+[alleyinteractive/wordpress-autoloader](https://github.com/alleyinteractive/wordpress-autoloader).
+Will load the autoloaded classes defined in your package and all autoloaded
+classes in your dependencies.
+
+## Installation
+
+You can install the package via composer:
+
+```bash
+composer require alleyinteractive/composer-wordpress-autoloader
+```
+
+## Usage
+
+```json
+{
+ "autoload": {
+ "wordpress": {
+ "My_Plugin_Namespace\\": "src/",
+ }
+ },
+ "autoload-dev": {
+ "wordpress": {
+ "My_Plugin_Namespace\\Tests\\": "tests/",
+ }
+ }
+}
+```
+
+Once added, you can load `vendor/autoload.php` as normal and the autoloader will
+handle the rest. If that doesn't work, see [Automatically Injecting WordPress
+Autoloader](#automatically-injecting-wordpress-autoloader).
+
+### Use Inside Packages Published to Packagist
+
+Packages published to Packagist are required to be valid and have a
+`composer.json` that passed a `composer validate`. Composer does not consider
+`wordpress` to be a valid value inside of the `autoload` or `autoload-dev`
+property. To allow packages to register autoloading in a valid format, you can
+use the following format:
+
+```json
+{
+ "extra": {
+ "wordpress-autoloader": {
+ "autoload": {
+ "My_Plugin_Namespace\\": "src/",
+ },
+ "autoload-dev": {
+ "My_Plugin_Namespace\\Tests\\": "tests/",
+ }
+ }
+ }
+}
+```
+
+### Automatically Injecting WordPress Autoloader
+
+By default Composer WordPress Autoloader will automatically load the WordPress
+autoloader. This is done by adding `src/autoload.php` as an autoloaded file to
+Composer. However, this may not always work under some circumstances including
+symlinks. When necessary, you can opt to inject the
+`vendor/wordpress-autoload.php` file into your `vendor/autoload.php` file. This
+is disabled by default and be enabled by setting `inject` to `true` in your
+`composer.json`.
+
+```json
+{
+ "extra": {
+ "wordpress-autoloader": {
+ "inject": true
+ }
+ }
+}
+```
+
+## Testing
+
+```bash
+composer test
+```
+
+## Changelog
+
+Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
diff --git a/vendor/alleyinteractive/composer-wordpress-autoloader/composer.json b/vendor/alleyinteractive/composer-wordpress-autoloader/composer.json
new file mode 100644
index 00000000..779de3d5
--- /dev/null
+++ b/vendor/alleyinteractive/composer-wordpress-autoloader/composer.json
@@ -0,0 +1,55 @@
+{
+ "name": "alleyinteractive/composer-wordpress-autoloader",
+ "type": "composer-plugin",
+ "description": "Autoload files using WordPress File Conventions using Composer",
+ "license": "GPL-2.0-or-later",
+ "authors": [
+ {
+ "name": "Alley Interactive",
+ "email": "info@alley.co"
+ },
+ {
+ "name": "Sean Fisher",
+ "email": "sean@alley.co"
+ }
+ ],
+ "require": {
+ "php": "^8.0",
+ "alleyinteractive/wordpress-autoloader": "^1.1.1",
+ "composer-plugin-api": "^2.0"
+ },
+ "require-dev": {
+ "composer/composer": "*",
+ "phpunit/phpunit": "^9.5.8",
+ "squizlabs/php_codesniffer": "^4.0"
+ },
+ "extra": {
+ "class": "ComposerWordPressAutoloader\\Plugin"
+ },
+ "autoload": {
+ "files": [
+ "src/autoload.php"
+ ],
+ "psr-4": {
+ "ComposerWordPressAutoloader\\": "src/"
+ }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "scripts": {
+ "lint": "@phpcs",
+ "lint:fix": "@phpcbf",
+ "phpcbf": "phpcbf --standard=./phpcs.xml.dist .",
+ "phpcs": "phpcs --standard=./phpcs.xml.dist .",
+ "phpunit": "phpunit",
+ "test": [
+ "@phpcs",
+ "@phpunit"
+ ]
+ },
+ "config": {
+ "allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true
+ }
+ }
+}
diff --git a/vendor/alleyinteractive/composer-wordpress-autoloader/phpcs.xml.dist b/vendor/alleyinteractive/composer-wordpress-autoloader/phpcs.xml.dist
new file mode 100644
index 00000000..616aa35a
--- /dev/null
+++ b/vendor/alleyinteractive/composer-wordpress-autoloader/phpcs.xml.dist
@@ -0,0 +1,21 @@
+
+
+ Coding standards for Composer WordPress Autoload Plugin
+
+
+
+
+
+
+
+ .
+ */.github/*
+ */vendor/*
+ tests/fixtures/*
+
+
+
+
+
+
diff --git a/vendor/alleyinteractive/composer-wordpress-autoloader/src/AutoloadFactory.php b/vendor/alleyinteractive/composer-wordpress-autoloader/src/AutoloadFactory.php
new file mode 100644
index 00000000..6d741dd0
--- /dev/null
+++ b/vendor/alleyinteractive/composer-wordpress-autoloader/src/AutoloadFactory.php
@@ -0,0 +1,61 @@
+> $rules Array of rules.
+ * @return array
+ */
+ public static function generateFromRules(array $rules)
+ {
+ $loaders = [];
+
+ foreach ($rules as $namespace => $paths) {
+ $loaders = array_merge(
+ $loaders,
+ array_map(function ($path) use ($namespace) {
+ $loader = new Autoloader($namespace, $path);
+
+ if (static::$apcuPrefix) {
+ $loader->set_apcu_prefix(static::$apcuPrefix);
+ }
+
+ return $loader;
+ }, $paths),
+ );
+ }
+
+ return $loaders;
+ }
+
+ /**
+ * Register autoloaders from rules.
+ *
+ * @param array $rules Array of rules.
+ * @return void
+ */
+ public static function registerFromRules(array $rules)
+ {
+ foreach (static::generateFromRules($rules) as $autoloader) {
+ $autoloader->register();
+ }
+ }
+}
diff --git a/vendor/alleyinteractive/composer-wordpress-autoloader/src/AutoloadGenerator.php b/vendor/alleyinteractive/composer-wordpress-autoloader/src/AutoloadGenerator.php
new file mode 100644
index 00000000..e6d8c279
--- /dev/null
+++ b/vendor/alleyinteractive/composer-wordpress-autoloader/src/AutoloadGenerator.php
@@ -0,0 +1,327 @@
+getEventDispatcher(), $io);
+
+ $this->composer = $composer;
+ }
+
+ /**
+ * @param bool $devMode
+ * @return void
+ */
+ public function setDevMode($devMode = true)
+ {
+ parent::setDevMode($devMode);
+
+ $this->devMode = (bool) $devMode;
+ }
+
+ /**
+ * Whether generated autoloader considers APCu caching.
+ *
+ * @param bool $apcu
+ * @param string|null $apcuPrefix
+ * @return void
+ */
+ public function setApcuMode(bool $apcu, ?string $apcuPrefix = null)
+ {
+ parent::setApcu($apcu, $apcuPrefix);
+
+ $this->apcu = $apcu;
+ $this->apcuPrefix = $apcuPrefix !== null ? $apcuPrefix : $apcuPrefix;
+ }
+
+ /**
+ * Generate the autoload file.
+ *
+ * @param boolean $beingInjected Flag if the autoload file is being injected.
+ * @param boolean $isDevMode Flag if dev dependencies are being included.
+ * @return string
+ */
+ public function generate(bool $beingInjected, bool $isDevMode = true): string
+ {
+ $filesystem = new Filesystem();
+ $basePath = $filesystem->normalizePath(realpath(realpath(getcwd())));
+ $vendorPath = $filesystem->normalizePath(realpath(realpath($this->composer->getConfig()->get('vendor-dir'))));
+
+ $vendorPathCode = '__DIR__';
+
+ $appBaseDirCode = $filesystem->findShortestPathCode($vendorPath, $basePath, true);
+ $appBaseDirCode = str_replace('__DIR__', '$vendorDir', $appBaseDirCode);
+
+ $this->setDevMode($isDevMode);
+
+ // Collect all the rules from all the packages.
+ $rules = array_merge_recursive(
+ $this->collectAutoloaderRules(),
+ $this->collectExtraAutoloaderRules(),
+ );
+
+ foreach ($rules as $namespace => $paths) {
+ // Convert the paths to be relative to the vendor/wordpress-autoload.php file.
+ $rules[$namespace] = array_values(array_unique(
+ array_map(fn ($path) => $this->getPathCode($filesystem, $basePath, $vendorPath, $path), $paths),
+ ));
+ }
+
+ return $this->getAutoloadFileContents($rules, $beingInjected, $vendorPathCode, $appBaseDirCode);
+ }
+
+ /**
+ * Create the autoloader file contents to write to vendor/wordpress-autoload.php.
+ *
+ * @param array $rules The rules to use to generate the autoloader.
+ * @param boolean $beingInjected Flag if the autoload file is being injected.
+ * @param string $vendorPathCode Vendor path code.
+ * @param string $appBaseDirCode App base dir code.
+ * @return string
+ */
+ protected function getAutoloadFileContents(
+ array $rules,
+ bool $beingInjected,
+ string $vendorPathCode,
+ string $appBaseDirCode
+ ): string {
+ $autoloadFileContents = <<apcu) {
+ $apcuPrefix = var_export(
+ $this->apcuPrefix !== null
+ ? $this->apcuPrefix
+ : substr(base64_encode(md5(uniqid('', true), true)), 0, -3),
+ true,
+ );
+ $autoloadFileContents .= "\n\\ComposerWordPressAutoloader\AutoloadFactory::setApcuPrefix($apcuPrefix);\n";
+ }
+
+ // Inject the file paths for the autoloader.
+ $autoloadFileContents .= << $paths) {
+ $autoloadFileContents .= sprintf(
+ ' %s => array(%s),',
+ var_export($namespace, true),
+ implode(', ', $paths),
+ ) . PHP_EOL;
+ }
+
+ $autoloadFileContents .= "));\n";
+
+ if (!$beingInjected) {
+ $autoloadFileContents .= "\nreturn \$autoload;";
+ }
+
+ return $autoloadFileContents . "\n";
+ }
+
+ /**
+ * Collect the autoloader rules from 'autoload' and 'autoload-dev' to
+ * generate rules for.
+ *
+ * @return array
+ */
+ protected function collectAutoloaderRules(): array
+ {
+ return $this->parseAutoloads(
+ $this->buildPackageMap(
+ $this->composer->getInstallationManager(),
+ $this->composer->getPackage(),
+ $this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages(),
+ ),
+ $this->composer->getPackage(),
+ !$this->devMode,
+ )['wordpress'] ?? [];
+ }
+
+ /**
+ * Collect the autoloader rules registered via 'extra' to generate for.
+ *
+ * @return array
+ */
+ protected function collectExtraAutoloaderRules(): array
+ {
+ return $this->parseExtraAutoloads(
+ $this->buildPackageMap(
+ $this->composer->getInstallationManager(),
+ $this->composer->getPackage(),
+ $this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages(),
+ ),
+ $this->composer->getPackage(),
+ !$this->devMode,
+ )['wordpress'] ?? [];
+ }
+
+ /**
+ * Compiles an ordered list of namespace => path mappings
+ *
+ * @param array $packageMap
+ * @param PackageInterface $rootPackage
+ * @param boolean $filteredDevPackages
+ * @return array>
+ */
+ public function parseAutoloads(array $packageMap, PackageInterface $rootPackage, $filteredDevPackages = false)
+ {
+ $rootPackageMap = array_shift($packageMap);
+
+ // Mirroring existing logic in the Composer AutoloadGenerator.
+ if (is_array($filteredDevPackages)) {
+ $packageMap = array_filter($packageMap, function ($item) use ($filteredDevPackages) {
+ return !in_array($item[0]->getName(), $filteredDevPackages, true);
+ });
+ } elseif ($filteredDevPackages) {
+ $packageMap = $this->filterPackageMap($packageMap, $rootPackage);
+ }
+
+ if ($filteredDevPackages) {
+ $packageMap = $this->filterPackageMap($packageMap, $rootPackage);
+ }
+
+ $sortedPackageMap = $this->sortPackageMap($packageMap);
+ $sortedPackageMap[] = $rootPackageMap;
+ array_unshift($packageMap, $rootPackageMap);
+
+ $wordpress = $this->parseAutoloadsType($sortedPackageMap, 'wordpress', $rootPackage);
+
+ krsort($wordpress);
+
+ return [
+ 'wordpress' => $wordpress,
+ ];
+ }
+
+ /**
+ * Compiles an ordered list of namespace => path mappings of autoloads defined in the 'extra' part of a package.
+ *
+ * @param array $packageMap
+ * @param PackageInterface $rootPackage
+ * @param boolean $filteredDevPackages
+ * @return array
+ */
+ public function parseExtraAutoloads(array $packageMap, PackageInterface $rootPackage, $filteredDevPackages = false)
+ {
+ $rootPackageMap = array_shift($packageMap);
+
+ // Mirroring existing logic in the Composer AutoloadGenerator.
+ if (is_array($filteredDevPackages)) {
+ $packageMap = array_filter($packageMap, function ($item) use ($filteredDevPackages) {
+ return !in_array($item[0]->getName(), $filteredDevPackages, true);
+ });
+ } elseif ($filteredDevPackages) {
+ $packageMap = $this->filterPackageMap($packageMap, $rootPackage);
+ }
+
+ if ($filteredDevPackages) {
+ $packageMap = $this->filterPackageMap($packageMap, $rootPackage);
+ }
+
+ $sortedPackageMap = $this->sortPackageMap($packageMap);
+ $sortedPackageMap[] = $rootPackageMap;
+ array_unshift($packageMap, $rootPackageMap);
+
+ return [
+ 'wordpress' => $this->parseExtraAutoloadsType($sortedPackageMap, 'wordpress', $rootPackage),
+ ];
+ }
+
+ /**
+ * A modified port of the {@see AutoloadGenerator::parseAutoloadsType()} method from Composer.
+ *
+ * Imports autoload rules from a package's extra path.
+ *
+ * @param array $packageMap
+ * @param string $type one of: 'wordpress'
+ * @return array|array>|array
+ */
+ protected function parseExtraAutoloadsType(array $packageMap, $type, RootPackageInterface $rootPackage)
+ {
+ $autoloads = [];
+
+ foreach ($packageMap as $item) {
+ [$package, $installPath] = $item;
+ $autoload = [
+ 'wordpress' => $package->getExtra()['wordpress-autoloader']['autoload'] ?? [],
+ ];
+
+ // Include autoload dev if we're in dev mode and this is the root package.
+ // Non-root package dev dependencies are not loaded.
+ if ($this->devMode && $package === $rootPackage) {
+ $autoload = array_merge_recursive(
+ $autoload,
+ [
+ 'wordpress' => $package->getExtra()['wordpress-autoloader']['autoload-dev'] ?? [],
+ ],
+ );
+ }
+
+ // Skip misconfigured packages.
+ if (!isset($autoload[$type]) || !is_array($autoload[$type])) {
+ continue;
+ }
+
+ if (null !== $package->getTargetDir() && $package !== $rootPackage) {
+ $installPath = substr($installPath, 0, -strlen('/' . $package->getTargetDir()));
+ }
+
+ if ($package !== $rootPackage && $rootPackage->getTargetDir()) {
+ $installPath = str_replace($rootPackage->getTargetDir(), '', $installPath);
+ }
+
+ foreach ($autoload[$type] as $namespace => $paths) {
+ foreach ((array) $paths as $path) {
+ $relativePath = empty($installPath) ? (empty($path) ? '.' : $path) : $installPath . '/' . $path;
+ $autoloads[$namespace][] = $relativePath;
+ }
+ }
+ }
+
+ return $autoloads;
+ }
+}
diff --git a/vendor/alleyinteractive/composer-wordpress-autoloader/src/Plugin.php b/vendor/alleyinteractive/composer-wordpress-autoloader/src/Plugin.php
new file mode 100644
index 00000000..90c9c021
--- /dev/null
+++ b/vendor/alleyinteractive/composer-wordpress-autoloader/src/Plugin.php
@@ -0,0 +1,209 @@
+composer = $composer;
+ $this->io = $io;
+ $this->filesystem = new Filesystem();
+ }
+
+ /**
+ * Remove any hooks from Composer
+ *
+ * This will be called when a plugin is deactivated before being
+ * uninstalled, but also before it gets upgraded to a new version
+ * so the old one can be deactivated and the new one activated.
+ *
+ * @param Composer $composer
+ * @param IOInterface $io
+ *
+ * @return void
+ */
+ public function deactivate(Composer $composer, IOInterface $io)
+ {
+ }
+
+ /**
+ * Prepare the plugin to be uninstalled
+ *
+ * This will be called after deactivate.
+ *
+ * @param Composer $composer
+ * @param IOInterface $io
+ *
+ * @return void
+ */
+ public function uninstall(Composer $composer, IOInterface $io)
+ {
+ if ($this->filesystem->remove($this->getAutoloaderFilePath())) {
+ $this->io->write('Removed WordPress autoloader.');
+ }
+ }
+
+ /**
+ * Returns an array of event names this subscriber wants to listen to.
+ *
+ * @return array
+ */
+ public static function getSubscribedEvents()
+ {
+ return [
+ 'post-autoload-dump' => 'generateAutoloaderFile',
+ ];
+ }
+
+ /**
+ * Generate the autoloader file.
+ *
+ * @param Event $event
+ * @return void
+ */
+ public function generateAutoloaderFile(Event $event): void
+ {
+ $this->filesystem->ensureDirectoryExists($this->composer->getConfig()->get('vendor-dir'));
+
+ $this->generator = new AutoloadGenerator(
+ $this->composer,
+ $this->io,
+ );
+
+ $this->generator->setApcuMode(
+ $this->composer->getConfig()->get('apcu-autoloader')
+ );
+
+ // Merge default configuration with the one provided in the composer.json file.
+ $extra = array_merge(
+ [
+ 'inject' => false,
+ ],
+ $this->composer->getPackage()->getExtra()['wordpress-autoloader'] ?? [],
+ );
+
+ /**
+ * Determine if we should inject our autoloader into the
+ * vendor/autoload.php from Composer.
+ *
+ * Injecting is not generally necessary any more since the file will
+ * automatically be loaded. However, it is still possible to inject it
+ * for circumstances where it is needed such as when dealing with symlinks.
+ */
+ $injecting = $extra['inject'] ?? false;
+
+ $autoloaderFile = $this->generator->generate($injecting, $event->isDevMode());
+
+ $partyEmoji = [
+ '🪩',
+ '🎉',
+ '🎊',
+ '🍾',
+ ];
+
+ $partyEmoji = $partyEmoji[array_rand($partyEmoji)];
+
+ if (
+ $this->filesystem->filePutContentsIfModified(
+ $this->getAutoloaderFilePath(),
+ $autoloaderFile,
+ )
+ ) {
+ if (!$injecting) {
+ $this->io->write("{$partyEmoji} WordPress autoloader generated");
+ }
+ }
+
+ // Inject the autoloader into the existing autoloader.
+ if ($injecting) {
+ if (
+ $this->filesystem->filePutContentsIfModified(
+ $this->composer->getConfig()->get('vendor-dir') . '/autoload.php',
+ $this->getInjectedAutoloaderFileContents($this->getAutoloaderFilePath()),
+ )
+ ) {
+ $this->io->write(
+ "{$partyEmoji} WordPress autoloader genearted and injected into vendor/autoload.php."
+ );
+ } else {
+ $this->io->write('⚠️ Error injecting Wordpress Autoloader.');
+ }
+ }
+ }
+
+ /**
+ * Retrieve the file path for the autoloader file.
+ *
+ * @return string
+ */
+ protected function getAutoloaderFilePath(): string
+ {
+ $vendorDir = $this->composer->getConfig()->get('vendor-dir');
+ return "{$vendorDir}/wordpress-autoload.php";
+ }
+
+ /**
+ * Generate the injected autoloader file.
+ *
+ * @param string $autoloaderFileName The path to the WordPress autoloader file.
+ * @return string
+ */
+ protected function getInjectedAutoloaderFileContents(string $autoloaderFileName): string
+ {
+ $filename = basename($autoloaderFileName);
+ $autoloader = file_get_contents($this->composer->getConfig()->get('vendor-dir') . '/autoload.php');
+
+ $contents = preg_replace_callback(
+ '/^return (.*);$/m',
+ function ($matches) use ($filename) {
+ $autoloader = << wordpress-autoloader'
+ section of your composer.json file to false. Injecting the autoloader is
+ not generally necessary as the autoloader is automatically loaded for you.
+*/
+require_once __DIR__ . '/{$filename}';
+
+return \$loader;
+AUTOLOADER;
+
+ return "$autoloader\n";
+ },
+ $autoloader,
+ 1,
+ $count,
+ );
+
+ if (!$count) {
+ throw new RuntimeException('Error finding proper place to inject autoloader.');
+ }
+
+ return $contents;
+ }
+}
diff --git a/vendor/alleyinteractive/composer-wordpress-autoloader/src/autoload.php b/vendor/alleyinteractive/composer-wordpress-autoloader/src/autoload.php
new file mode 100644
index 00000000..64922229
--- /dev/null
+++ b/vendor/alleyinteractive/composer-wordpress-autoloader/src/autoload.php
@@ -0,0 +1,30 @@
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/vendor/alleyinteractive/laminas-validator-extensions/README.md b/vendor/alleyinteractive/laminas-validator-extensions/README.md
new file mode 100644
index 00000000..9084c84b
--- /dev/null
+++ b/vendor/alleyinteractive/laminas-validator-extensions/README.md
@@ -0,0 +1,377 @@
+# Laminas Validator Extensions
+
+This package provides additional validation classes for [the Laminas Validator framework](https://docs.laminas.dev/laminas-validator/), plus a custom base validation class.
+
+## Installation
+
+Install the latest version with:
+
+```bash
+$ composer require alleyinteractive/laminas-validator-extensions
+```
+
+## Basic usage
+
+For more information about what validators do, how to use them, and how to write your own, [visit the Laminas documentation](https://docs.laminas.dev/laminas-validator/).
+
+## Base validators
+
+### `ExtendedAbstractValidator`
+
+The abstract `Alley\Validator\ExtendedAbstractValidator` class standardizes the implementation of custom validators with `\Laminas\Validator\AbstractValidator`.
+
+When extending `ExtendedAbstractValidator`, validation logic goes into a new `testValue()` method, which is responsible only for applying the logic and adding any validation errors. It's no longer necessary to call `setValue()` prior to evaluating the input, and `isValid()` will return `true` if there are no error messages after evaluating the input and `false` if there are any messages.
+
+Before:
+
+```php
+ "'%value%' is not a floating point value",
+ ];
+
+ public function isValid($value)
+ {
+ $this->setValue($value);
+
+ if (! is_float($value)) {
+ $this->error(self::FLOAT);
+ return false;
+ }
+
+ return true;
+ }
+}
+```
+
+After:
+
+```php
+ "'%value%' is not a floating point value",
+ ];
+
+ public function testValue($value): void
+ {
+ if (! is_float($value)) {
+ $this->error(self::FLOAT);
+ }
+ }
+}
+```
+
+### `FreeformValidator`
+
+The standalone, abstract `Alley\Validator\FreeformValidator` class leaves most of the implementation details to your discretion, but it's often easier to use for validators that are project-specific or not ready for wider distribution.
+
+Like the `ExtendedAbstractValidator` class, the `FreeformValidator` expects that validation logic goes into a `testValue()` method, and `isValid()` will return `true` or `false` based on whether there are error messages.
+
+Validation errors can be added using the `error()` method, which accepts the message key and text.
+
+```php
+error('float', 'Please enter a floating point value');
+ }
+ }
+}
+```
+
+## "Any Validator" chains
+
+`\Alley\Validator\AnyValidator` is like a [Laminas validator chain](https://docs.laminas.dev/laminas-validator/validator-chains/) except that it connects the validators with "OR," marking input as valid as soon it passes one of the given validators.
+
+Unlike a Laminas validator chain, validators can only be attached, not prepended, and there is no `$priority` argument.
+
+### Basic usage
+
+```php
+ 10])]);
+$valid->attach(new \Laminas\Validator\GreaterThan(['min' => 90]));
+
+$valid->isValid(9); // true
+$valid->isValid(99); // true
+$valid->isValid(42); // false
+```
+
+## "Fast fail" validator chains
+
+`\Alley\Validator\FastFailValidatorChain` is like a [Laminas validator chain](https://docs.laminas.dev/laminas-validator/validator-chains/) except that if a validator fails, the chain will automatically be broken; there is no `$breakChainOnFailure` parameter.
+
+Unlike a Laminas validator chain, validators can only be attached, not prepended, and there is no `$priority` argument.
+
+### Basic usage
+
+```php
+$valid = new \Alley\Validator\FastFailValidatorChain([new \Laminas\Validator\LessThan(['max' => 10])]);
+$valid->attach(new \Laminas\Validator\GreaterThan(['min' => 90]));
+
+$valid->isValid(42); // false
+count($valid->getMessages()); // 1
+```
+
+## Validators by operator name
+
+`\Alley\Validator\ValidatorByOperator` allows you to access a validator using a readable operator name, such as `REGEX` or `NOT IN`.
+
+Its primary use case is to allow you to write functions that accept the readable operator names as parameters while using validators internally. Here's a demonstrative function call from [the wp-match-blocks library](https://github.com/alleyinteractive/wp-match-blocks):
+
+```php
+ 'core/image',
+ 'attrs' => [
+ [
+ 'key' => 'credit',
+ 'value' => '/(The )?Associated Press/i',
+ 'operator' => 'REGEX',
+ ],
+ ],
+ ],
+);
+```
+
+The supported operator names are:
+
+* `CONTAINS` and `NOT CONTAINS`, which forward to `\Alley\Validator\ContainsString` using a case-sensitive search.
+* `IN` and `NOT IN`, which forward to `\Alley\Validator\OneOf`.
+* `LIKE` and `NOT LIKE`, which forward to `\Alley\Validator\ContainsString` using a case-insensitive search.
+* `REGEX` and `NOT REGEX`, which forward to `\Laminas\Validator\Regex`.
+* `===`, `!==`, and the other operators supported by `\Alley\Validator\Comparison`.
+
+Any operator name that isn't forwarded to a different validator must be a valid `Comparison` operator.
+
+### Basic usage
+
+```php
+$valid = new \Alley\Validator\ValidatorByOperator('REGEX', '/^foo/');
+$valid->isValid('foobar'); // true
+
+$valid = new \Alley\Validator\ValidatorByOperator('NOT IN', ['bar', 'baz']);
+$valid->isValid('bar'); // false
+
+$valid = new \Alley\Validator\ValidatorByOperator('!==', 42);
+$valid->isValid(43); // true
+```
+
+## Validators
+
+### `AlwaysValid`
+
+`\Alley\Validator\AlwaysValid` marks all input as valid. It can be used to satisfy type requirements when full validation needs to be disabled or is impractical.
+
+#### Supported options
+
+None.
+
+#### Basic usage
+
+```php
+isValid(42); // true
+$valid->isValid(false); // true
+$valid->isValid('abcdefghijklmnopqrstuvwxyz'); // true
+```
+
+### `Comparison`
+
+`\Alley\Validator\Comparison` compares input to another value using a PHP [comparison operator](https://www.php.net/manual/en/language.operators.comparison.php). The input passes validation if the comparison is true. Input is placed on the left side of the operator.
+
+#### Supported options
+
+The following options are supported for `\Alley\Validator\Comparison`:
+
+- `compared`: The value the inputs are compared to. It is placed on the right side of the operator.
+- `operator`: The PHP comparison operator used to compare the input and `compared`.
+
+#### Basic usage
+
+```php
+ '<=',
+ 'compared' => 100,
+ ]
+);
+$valid->isValid(101); // false
+
+$valid = new \Alley\Validator\Comparison(
+ [
+ 'operator' => '!==',
+ 'compared' => false,
+ ]
+);
+$valid->isValid(true); // true
+```
+
+### `ContainsString`
+
+`\Alley\Validator\ContainsString` is a validator around the `str_contains()` function. Each instance of the validator represents the "needle" string and validates whether the string is found within the input "haystack." Inputs will automatically be cast to strings.
+
+#### Supported options
+
+The following options are supported for `\Alley\Validator\ContainsString`:
+
+- `needle`: The string or instance of `\Stringable` the inputs are searched for. It will automatically be cast to a string at the time of validation.
+- `ignoreCase`: Whether to perform a case-insensitive search. False by default.
+
+#### Basic usage
+
+```php
+ 'foo',
+ ],
+);
+
+$valid->isValid('foobar'); // true
+$valid->isValid('barbaz'); // false
+```
+
+### `DivisibleBy`
+
+`\Alley\Validator\DivisibleBy` allows you to validate whether the input is evenly divisible by a given numeric value. Inputs will automatically be cast to integers.
+
+#### Supported options
+
+The following options are supported for `\Alley\Validator\DivisibleBy`:
+
+- `divisor`: The value the inputs are divided by. It will automatically be cast to an integer.
+
+#### Basic usage
+
+```php
+ 3,
+ ],
+);
+
+$valid->isValid(9); // true
+$valid->isValid(10); // false
+```
+
+### `Not`
+
+`Alley\Validator\Not` inverts the validity of a given validator. It allows for creating validators that test whether input is, for example, "not one of" in addition to "one of."
+
+#### Supported options
+
+None.
+
+#### Basic usage
+
+```php
+ ['foo', 'bar']]);
+$valid = new \Alley\Validator\Not($origin, 'The input was invalid.');
+
+$valid->isValid('foo'); // false
+$valid->isValid('baz'); // true
+```
+
+### `OneOf`
+
+`Alley\Validator\OneOf` validates whether an array of scalar values contains the input.
+
+`OneOf` is a simpler version of `\Laminas\Validator\InArray` that accepts only scalar values in the haystack and does only strict comparisons. In return, it produces a friendlier error message that lists the allowed values.
+
+#### Supported options
+
+The following options are supported for `\Alley\Validator\OneOf`:
+
+- `haystack`: The array to be searched for the input.
+
+#### Basic Usage
+
+```php
+ ['one', 'two', 'three']]);
+$valid->isValid('four'); // false
+$valid->getMessages(); // ['notOneOf' => 'Must be one of [one, two, three] but is four.']
+```
+
+### `Type`
+
+`\Alley\Validator\Type` allows you to validate whether the input is of the given PHP type. The input passes if it is the expected type.
+
+This validator is inspired by PHPUnit's `\PHPUnit\Framework\Constraint\IsType` class.
+
+#### Supported options
+
+The following options are supported for `\Alley\Validator\Type`:
+
+- `type`: The expected PHP type. Supported types are `array`, `bool`, `boolean`, `callable`, `double`, `float`, `int`, `integer`, `iterable`, `null`, `numeric`, `object`, `real`, `resource`, `string`, and `scalar`.
+
+#### Basic usage
+
+```php
+ 'callable']);
+$valid->isValid('date_create_immutable'); // true
+
+$valid = new \Alley\Validator\Type(['type' => 'bool']);
+$valid->isValid([]); // false
+```
+
+### `WithMessage`
+
+`Alley\Validator\WithMessage` allows you to decorate a validator with a custom failure code and message, replacing the validator's usual failure messages.
+
+#### Supported options
+
+None.
+
+#### Basic usage
+
+```php
+isValid(41); // false
+$valid->getMessages(); // ['tooSmall' => 'Please enter a number greater than 42.']
+```
+
+## About
+
+### License
+
+[GPL-2.0-or-later](https://github.com/alleyinteractive/laminas-validator-extensions/blob/main/LICENSE)
+
+### Maintainers
+
+[Alley Interactive](https://github.com/alleyinteractive)
diff --git a/vendor/alleyinteractive/laminas-validator-extensions/composer.json b/vendor/alleyinteractive/laminas-validator-extensions/composer.json
new file mode 100644
index 00000000..7334a9e2
--- /dev/null
+++ b/vendor/alleyinteractive/laminas-validator-extensions/composer.json
@@ -0,0 +1,40 @@
+{
+ "name": "alleyinteractive/laminas-validator-extensions",
+ "description": "Additional validation classes for the laminas-validator framework.",
+ "type": "library",
+ "license": "GPL-2.0-or-later",
+ "authors": [
+ {
+ "name": "Alley",
+ "email": "info@alley.com"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "Alley\\": "src/Alley/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Alley\\": "tests/Alley/"
+ }
+ },
+ "config": {
+ "lock": false
+ },
+ "require": {
+ "php": "^8.0",
+ "laminas/laminas-validator": "^2.20"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.8",
+ "phpunit/phpunit": "^9.5",
+ "squizlabs/php_codesniffer": "^3.6"
+ },
+ "scripts": {
+ "fixer": "php-cs-fixer -v fix --allow-risky=yes",
+ "phpcbf": "phpcbf",
+ "phpcs": "phpcs",
+ "phpunit": "phpunit"
+ }
+}
diff --git a/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/AlwaysValid.php b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/AlwaysValid.php
new file mode 100644
index 00000000..c2f142a9
--- /dev/null
+++ b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/AlwaysValid.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Alley\Validator;
+
+use Laminas\Validator\ValidatorInterface;
+
+final class AlwaysValid implements ValidatorInterface
+{
+ public function isValid($value)
+ {
+ return true;
+ }
+
+ public function getMessages()
+ {
+ return [];
+ }
+}
diff --git a/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/AnyValidator.php b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/AnyValidator.php
new file mode 100644
index 00000000..dd9b0c5d
--- /dev/null
+++ b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/AnyValidator.php
@@ -0,0 +1,91 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Alley\Validator;
+
+use Countable;
+use Laminas\Validator\ValidatorInterface;
+use ReturnTypeWillChange;
+
+final class AnyValidator implements Countable, ValidatorInterface
+{
+ /**
+ * Validator chain.
+ *
+ * @var ValidatorInterface[]
+ */
+ protected array $validators = [];
+
+ /**
+ * Array of validation failure messages.
+ *
+ * @var string[]
+ */
+ protected $messages = [];
+
+ /**
+ * @param ValidatorInterface[] $validators
+ */
+ public function __construct(array $validators)
+ {
+ foreach ($validators as $validator) {
+ $this->attach($validator);
+ }
+ }
+
+ /**
+ * Attach a validator to the end of the chain.
+ *
+ * @param ValidatorInterface $validator
+ * @return self
+ */
+ public function attach(ValidatorInterface $validator)
+ {
+ $this->validators[] = $validator;
+
+ return $this;
+ }
+
+ public function isValid($value)
+ {
+ $this->messages = [];
+
+ if ($this->count() === 0) {
+ // Consistent with `\Laminas\Validator\ValidatorChain()`.
+ return true;
+ }
+
+ foreach ($this->validators as $validator) {
+ if ($validator->isValid($value)) {
+ return true;
+ }
+ }
+
+ foreach ($this->validators as $validator) {
+ $this->messages = array_replace($this->messages, $validator->getMessages());
+ }
+
+ return false;
+ }
+
+ public function getMessages()
+ {
+ return $this->messages;
+ }
+
+ #[ReturnTypeWillChange]
+ public function count()
+ {
+ return \count($this->validators);
+ }
+}
diff --git a/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/Comparison.php b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/Comparison.php
new file mode 100644
index 00000000..d3d45256
--- /dev/null
+++ b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/Comparison.php
@@ -0,0 +1,109 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Alley\Validator;
+
+use Laminas\Validator\Exception\InvalidArgumentException;
+use Laminas\Validator\ValidatorInterface;
+
+final class Comparison extends ExtendedAbstractValidator
+{
+ private const SUPPORTED_OPERATORS = [
+ '==',
+ '===',
+ '!=',
+ '<>',
+ '!==',
+ '<',
+ '>',
+ '<=',
+ '>=',
+ ];
+
+ private const OPERATOR_ERROR_CODES = [
+ '==' => 'notEqual',
+ '===' => 'notIdentical',
+ '!=' => 'isEqual',
+ '<>' => 'isEqual',
+ '!==' => 'isIdentical',
+ '<' => 'notLessThan',
+ '>' => 'notGreaterThan',
+ '<=' => 'notLessThanOrEqualTo',
+ '>=' => 'notGreaterThanOrEqualTo',
+ ];
+
+ protected $messageTemplates = [
+ 'notEqual' => 'Must be equal to %compared% but is %value%.',
+ 'notIdentical' => 'Must be identical to %compared% but is %value%.',
+ 'isEqual' => 'Must not be equal to %compared% but is %value%.',
+ 'isIdentical' => 'Must not be identical to %compared%.',
+ 'notLessThan' => 'Must be less than %compared% but is %value%.',
+ 'notGreaterThan' => 'Must be greater than %compared% but is %value%.',
+ 'notLessThanOrEqualTo' => 'Must be less than or equal to %compared% but is %value%.',
+ 'notGreaterThanOrEqualTo' => 'Must be greater than or equal to %compared% but is %value%.',
+ ];
+
+ protected $messageVariables = [
+ 'compared' => ['options' => 'compared'],
+ ];
+
+ protected $options = [
+ 'compared' => null,
+ 'operator' => '===',
+ ];
+
+ protected function testValue($value): void
+ {
+ switch ($this->options['operator']) {
+ case '==':
+ $result = $value == $this->options['compared'];
+ break;
+ case '!=':
+ case '<>':
+ $result = $value != $this->options['compared'];
+ break;
+ case '!==':
+ $result = $value !== $this->options['compared'];
+ break;
+ case '<':
+ $result = $value < $this->options['compared'];
+ break;
+ case '>':
+ $result = $value > $this->options['compared'];
+ break;
+ case '<=':
+ $result = $value <= $this->options['compared'];
+ break;
+ case '>=':
+ $result = $value >= $this->options['compared'];
+ break;
+ case '===':
+ default:
+ $result = $value === $this->options['compared'];
+ break;
+ }
+
+ if (!$result) {
+ $this->error(self::OPERATOR_ERROR_CODES[$this->options['operator']]);
+ }
+ }
+
+ protected function setOperator(string $operator)
+ {
+ if (!\in_array($operator, self::SUPPORTED_OPERATORS, true)) {
+ throw new InvalidArgumentException("Invalid 'operator': {$operator}.");
+ }
+
+ $this->options['operator'] = $operator;
+ }
+}
diff --git a/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/ContainsString.php b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/ContainsString.php
new file mode 100644
index 00000000..fa478404
--- /dev/null
+++ b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/ContainsString.php
@@ -0,0 +1,64 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Alley\Validator;
+
+use Laminas\Validator\Exception\InvalidArgumentException;
+use Laminas\Validator\IsInstanceOf;
+use Laminas\Validator\ValidatorInterface;
+
+final class ContainsString extends ExtendedAbstractValidator
+{
+ public const NOT_CONTAINS_STRING = 'notContainsString';
+
+ protected $messageTemplates = [
+ self::NOT_CONTAINS_STRING => 'Must contain string "%needle%".',
+ ];
+
+ protected $messageVariables = [
+ 'needle' => ['options' => 'needle'],
+ ];
+
+ protected $options = [
+ 'needle' => '',
+ 'ignoreCase' => false,
+ ];
+
+ protected function testValue($value): void
+ {
+ if (\is_scalar($value)) {
+ $haystack = (string) $value;
+ $needle = (string) $this->options['needle'];
+
+ if ($this->options['ignoreCase']) {
+ $haystack = strtolower($haystack);
+ $needle = strtolower($needle);
+ }
+
+ if (str_contains($haystack, $needle)) {
+ return;
+ }
+ }
+
+ $this->error(self::NOT_CONTAINS_STRING);
+ }
+
+ protected function setNeedle($needle)
+ {
+ if (!\is_string($needle) && !\is_null($needle) && !$needle instanceof \Stringable) {
+ throw new InvalidArgumentException("Invalid 'needle': Must be string or instance of \Stringable");
+ }
+
+ $this->options['needle'] = $needle;
+ }
+}
diff --git a/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/DivisibleBy.php b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/DivisibleBy.php
new file mode 100644
index 00000000..ddc668d1
--- /dev/null
+++ b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/DivisibleBy.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Alley\Validator;
+
+use Laminas\Validator\Exception\InvalidArgumentException;
+use Laminas\Validator\ValidatorInterface;
+
+final class DivisibleBy extends ExtendedAbstractValidator
+{
+ public const NOT_DIVISIBLE_BY = 'notDivisibleBy';
+
+ protected $messageTemplates = [
+ self::NOT_DIVISIBLE_BY => 'Must be evenly divisible by %divisor% but %value% is not.',
+ ];
+
+ protected $messageVariables = [
+ 'divisor' => ['options' => 'divisor'],
+ ];
+
+ protected $options = [
+ 'divisor' => 1,
+ ];
+
+ protected function testValue($value): void
+ {
+ $value = (int) $value;
+ $actual = $value % $this->options['divisor'];
+
+ if ($actual !== 0) {
+ $this->error(self::NOT_DIVISIBLE_BY);
+ }
+ }
+
+ protected function setDivisor($divisor)
+ {
+ $divisor = (int) $divisor;
+
+ if ($divisor === 0) {
+ throw new InvalidArgumentException("Invalid 'divisor': {$divisor}");
+ }
+
+ $this->options['divisor'] = $divisor;
+ }
+}
diff --git a/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/ExtendedAbstractValidator.php b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/ExtendedAbstractValidator.php
new file mode 100644
index 00000000..32efb54e
--- /dev/null
+++ b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/ExtendedAbstractValidator.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Alley\Validator;
+
+use Laminas\Validator\AbstractValidator;
+
+abstract class ExtendedAbstractValidator extends AbstractValidator
+{
+ final public function isValid($value): bool
+ {
+ $this->setValue($value);
+ $this->testValue($this->value);
+ return \count($this->getMessages()) === 0;
+ }
+
+ /**
+ * Apply validation logic and add any validation errors.
+ *
+ * @param mixed $value
+ * @return void
+ */
+ abstract protected function testValue($value): void;
+}
diff --git a/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/FastFailValidatorChain.php b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/FastFailValidatorChain.php
new file mode 100644
index 00000000..c5d36798
--- /dev/null
+++ b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/FastFailValidatorChain.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Alley\Validator;
+
+use Laminas\Validator\ValidatorChain;
+use Laminas\Validator\ValidatorInterface;
+
+final class FastFailValidatorChain implements ValidatorInterface
+{
+ private ValidatorChain $origin;
+
+ /**
+ * @param ValidatorInterface[] $validators
+ */
+ public function __construct(array $validators)
+ {
+ $this->origin = new ValidatorChain();
+
+ foreach ($validators as $validator) {
+ $this->attach($validator);
+ }
+ }
+
+ /**
+ * Attach a validator to the end of the chain.
+ *
+ * @param ValidatorInterface $validator
+ * @return self
+ */
+ public function attach(ValidatorInterface $validator)
+ {
+ $this->origin->attach($validator, true);
+
+ return $this;
+ }
+
+ public function isValid($value): bool
+ {
+ return $this->origin->isValid($value);
+ }
+
+ public function getMessages(): array
+ {
+ return $this->origin->getMessages();
+ }
+}
diff --git a/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/FreeformValidator.php b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/FreeformValidator.php
new file mode 100644
index 00000000..69cc2b1b
--- /dev/null
+++ b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/FreeformValidator.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Alley\Validator;
+
+use Laminas\Validator\ValidatorInterface;
+use Stringable;
+
+abstract class FreeformValidator implements ValidatorInterface
+{
+ private array $messages = [];
+
+ /**
+ * Apply validation logic and add any validation errors.
+ *
+ * @param mixed $value
+ * @return void
+ */
+ abstract protected function testValue($value): void;
+
+ final public function isValid($value): bool
+ {
+ $this->messages = [];
+ $this->testValue($value);
+ return \count($this->getMessages()) === 0;
+ }
+
+ final public function getMessages()
+ {
+ return $this->messages;
+ }
+
+ /**
+ * Add validation error.
+ *
+ * @param string $key Error key.
+ * @param string $message Message text.
+ * @return void
+ */
+ final protected function error(string $key, $message): void
+ {
+ $this->messages[$key] = (string) $message;
+ }
+}
diff --git a/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/Not.php b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/Not.php
new file mode 100644
index 00000000..b6f94c11
--- /dev/null
+++ b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/Not.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Alley\Validator;
+
+use Laminas\Validator\ValidatorInterface;
+
+final class Not implements ValidatorInterface
+{
+ public const NOT_VALID = 'notValid';
+
+ private ValidatorInterface $origin;
+
+ private string $message;
+
+ private bool $ran = false;
+
+ public function __construct(ValidatorInterface $origin, string $message)
+ {
+ $this->origin = $origin;
+ $this->message = $message;
+ }
+
+ public function isValid($value)
+ {
+ $this->ran = true;
+ return !$this->origin->isValid($value);
+ }
+
+ public function getMessages()
+ {
+ $messages = [];
+
+ if ($this->ran && \count($this->origin->getMessages()) === 0) {
+ $messages[self::NOT_VALID] = $this->message;
+ }
+
+ return $messages;
+ }
+}
diff --git a/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/OneOf.php b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/OneOf.php
new file mode 100644
index 00000000..9bf0d6a2
--- /dev/null
+++ b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/OneOf.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Alley\Validator;
+
+use Laminas\Validator\Callback;
+use Laminas\Validator\Exception\InvalidArgumentException;
+use Laminas\Validator\Explode;
+use Laminas\Validator\ValidatorInterface;
+
+final class OneOf extends ExtendedAbstractValidator
+{
+ public const NOT_ONE_OF = 'notOneOf';
+
+ protected $messageTemplates = [
+ self::NOT_ONE_OF => "Must be one of %haystack% but is %value%.",
+ ];
+
+ protected $messageVariables = [
+ 'haystack' => ['options' => 'haystack'],
+ ];
+
+ protected $options = [
+ 'haystack' => [],
+ ];
+
+ protected function testValue($value): void
+ {
+ if (!\in_array($value, $this->options['haystack'], true)) {
+ $this->error(self::NOT_ONE_OF);
+ }
+ }
+
+ protected function setHaystack(array $haystack)
+ {
+ foreach ($haystack as $item) {
+ if (!\is_scalar($item)) {
+ throw new InvalidArgumentException('Haystack must contain only scalar values.');
+ }
+ }
+
+ $this->options['haystack'] = $haystack;
+ }
+}
diff --git a/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/Type.php b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/Type.php
new file mode 100644
index 00000000..aef48485
--- /dev/null
+++ b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/Type.php
@@ -0,0 +1,113 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Alley\Validator;
+
+use Laminas\Validator\Exception\InvalidArgumentException;
+use Laminas\Validator\ValidatorInterface;
+
+final class Type extends ExtendedAbstractValidator
+{
+ public const NOT_OF_TYPE = 'notOfType';
+
+ private const SUPPORTED_TYPES = [
+ 'array',
+ 'bool',
+ 'boolean',
+ 'callable',
+ 'double',
+ 'float',
+ 'int',
+ 'integer',
+ 'iterable',
+ 'null',
+ 'numeric',
+ 'object',
+ 'real',
+ 'resource',
+ 'scalar',
+ 'string',
+ ];
+
+ protected $messageTemplates = [
+ self::NOT_OF_TYPE => "Must be of PHP type '%type%' but %value% is not.",
+ ];
+
+ protected $messageVariables = [
+ 'type' => ['options' => 'type'],
+ ];
+
+ protected $options = [
+ 'type' => 'null',
+ ];
+
+ protected function testValue($value): void
+ {
+ switch ($this->options['type']) {
+ case 'array':
+ $result = \is_array($value);
+ break;
+ case 'bool':
+ case 'boolean':
+ $result = \is_bool($value);
+ break;
+ case 'int':
+ case 'integer':
+ $result = \is_int($value);
+ break;
+ case 'double':
+ case 'float':
+ case 'real':
+ $result = \is_float($value);
+ break;
+ case 'numeric':
+ $result = is_numeric($value);
+ break;
+ case 'object':
+ $result = \is_object($value);
+ break;
+ case 'resource':
+ $result = \is_resource($value);
+ break;
+ case 'string':
+ $result = \is_string($value);
+ break;
+ case 'scalar':
+ $result = \is_scalar($value);
+ break;
+ case 'callable':
+ $result = \is_callable($value);
+ break;
+ case 'iterable':
+ $result = is_iterable($value);
+ break;
+ case 'null':
+ default:
+ $result = \is_null($value);
+ break;
+ }
+
+ if (!$result) {
+ $this->error(self::NOT_OF_TYPE);
+ }
+ }
+
+ protected function setType(string $type)
+ {
+ if (!\in_array($type, self::SUPPORTED_TYPES, true)) {
+ throw new InvalidArgumentException("Invalid 'type': {$type}.");
+ }
+
+ $this->options['type'] = $type;
+ }
+}
diff --git a/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/ValidatorByOperator.php b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/ValidatorByOperator.php
new file mode 100644
index 00000000..de45e48c
--- /dev/null
+++ b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/ValidatorByOperator.php
@@ -0,0 +1,85 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Alley\Validator;
+
+use Laminas\Validator\Regex;
+use Laminas\Validator\ValidatorInterface;
+
+final class ValidatorByOperator implements ValidatorInterface
+{
+ private ValidatorInterface $final;
+
+ public function __construct(string $operator, $param)
+ {
+ // Build validator now so that its constructor runs, just as if the validator had been instantiated directly.
+ $this->final = $this->validator($operator, $param);
+ }
+
+ public function isValid($value)
+ {
+ return $this->final->isValid($value);
+ }
+
+ public function getMessages()
+ {
+ return $this->final->getMessages();
+ }
+
+ private function validator(string $operator, $param)
+ {
+ switch ($operator) {
+ case 'CONTAINS':
+ case 'NOT CONTAINS':
+ $validator = new ContainsString([
+ 'needle' => $param,
+ 'ignoreCase' => false,
+ ]);
+ break;
+
+ case 'IN':
+ case 'NOT IN':
+ $validator = new OneOf([
+ 'haystack' => $param,
+ ]);
+ break;
+
+ case 'LIKE':
+ case 'NOT LIKE':
+ $validator = new ContainsString([
+ 'needle' => $param,
+ 'ignoreCase' => true,
+ ]);
+ break;
+
+ case 'REGEX':
+ case 'NOT REGEX':
+ $validator = new Regex([
+ 'pattern' => $param,
+ ]);
+ break;
+
+ default:
+ $validator = new Comparison([
+ 'operator' => $operator,
+ 'compared' => $param,
+ ]);
+ }
+
+ if (str_starts_with($operator, 'NOT ')) {
+ $validator = new Not($validator, 'Invalid comparison.');
+ }
+
+ return $validator;
+ }
+}
diff --git a/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/WithMessage.php b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/WithMessage.php
new file mode 100644
index 00000000..6c4520a5
--- /dev/null
+++ b/vendor/alleyinteractive/laminas-validator-extensions/src/Alley/Validator/WithMessage.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Alley\Validator;
+
+use Laminas\Validator\ValidatorInterface;
+
+final class WithMessage implements ValidatorInterface
+{
+ private string $code;
+
+ private string $message;
+
+ private ValidatorInterface $origin;
+
+ public function __construct(string $code, string $message, ValidatorInterface $origin)
+ {
+ $this->origin = $origin;
+ $this->code = $code;
+ $this->message = $message;
+ }
+
+ public function isValid($value)
+ {
+ return $this->origin->isValid($value);
+ }
+
+ public function getMessages()
+ {
+ $messages = [];
+
+ if (\count($this->origin->getMessages()) > 0) {
+ $messages[$this->code] = $this->message;
+ }
+
+ return $messages;
+ }
+}
diff --git a/vendor/alleyinteractive/traverse-reshape/.editorconfig b/vendor/alleyinteractive/traverse-reshape/.editorconfig
new file mode 100644
index 00000000..4dd55cbe
--- /dev/null
+++ b/vendor/alleyinteractive/traverse-reshape/.editorconfig
@@ -0,0 +1,18 @@
+# EditorConfig helps maintain consistent coding styles for developers working on the same project across various editors and IDEs.
+# See https://editorconfig.org.
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.php]
+indent_size = 4
+
+[*.xml]
+indent_size = 4
diff --git a/vendor/alleyinteractive/traverse-reshape/.gitattributes b/vendor/alleyinteractive/traverse-reshape/.gitattributes
new file mode 100644
index 00000000..ff89b167
--- /dev/null
+++ b/vendor/alleyinteractive/traverse-reshape/.gitattributes
@@ -0,0 +1,27 @@
+#
+# Exclude these files from release archives.
+#
+# This will also make the files unavailable when using Composer with `--prefer-dist`.
+# If you develop using Composer, use `--prefer-source`.
+#
+# Via WPCS.
+#
+/.php_cs-fixer.dist.php export-ignore
+/phpcs.xml export-ignore
+/phpunit.xml export-ignore
+/.github export-ignore
+/tests export-ignore
+
+#
+# Auto detect text files and perform LF normalization.
+#
+# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/
+#
+* text=auto
+
+#
+# The above will handle all files not found below.
+#
+*.md text
+*.php text
+*.inc text
diff --git a/vendor/alleyinteractive/traverse-reshape/.gitignore b/vendor/alleyinteractive/traverse-reshape/.gitignore
new file mode 100644
index 00000000..f7334559
--- /dev/null
+++ b/vendor/alleyinteractive/traverse-reshape/.gitignore
@@ -0,0 +1,5 @@
+vendor
+composer.lock
+.php_cs.cache
+.phpunit.result.cache
+.php-cs-fixer.cache
diff --git a/vendor/alleyinteractive/traverse-reshape/.php-cs-fixer.dist.php b/vendor/alleyinteractive/traverse-reshape/.php-cs-fixer.dist.php
new file mode 100644
index 00000000..5a56cba5
--- /dev/null
+++ b/vendor/alleyinteractive/traverse-reshape/.php-cs-fixer.dist.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+$finder = PhpCsFixer\Finder::create()->in([
+ __DIR__ . '/src/',
+ __DIR__ . '/tests/',
+]);
+
+$config = new PhpCsFixer\Config();
+$config->setRules([
+ '@PSR12' => true,
+ '@PHP81Migration' => true,
+
+ 'final_class' => true,
+ 'native_constant_invocation' => true,
+ 'native_function_casing' => true,
+ 'native_function_invocation' => true,
+ 'native_function_type_declaration_casing' => true,
+]);
+$config->setFinder($finder);
+
+return $config;
diff --git a/vendor/alleyinteractive/traverse-reshape/CHANGELOG.md b/vendor/alleyinteractive/traverse-reshape/CHANGELOG.md
new file mode 100644
index 00000000..f9f211ea
--- /dev/null
+++ b/vendor/alleyinteractive/traverse-reshape/CHANGELOG.md
@@ -0,0 +1,17 @@
+# Changelog
+
+This library adheres to [Semantic Versioning](https://semver.org/) and [Keep a CHANGELOG](https://keepachangelog.com/en/1.0.0/).
+
+## Unreleased
+
+Nothing yet.
+
+## 2.0.0
+
+### Removed
+
+- PHP 7.4 support.
+
+## 1.0.0
+
+Initial release.
diff --git a/vendor/alleyinteractive/traverse-reshape/LICENSE b/vendor/alleyinteractive/traverse-reshape/LICENSE
new file mode 100644
index 00000000..d159169d
--- /dev/null
+++ b/vendor/alleyinteractive/traverse-reshape/LICENSE
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/vendor/alleyinteractive/traverse-reshape/README.md b/vendor/alleyinteractive/traverse-reshape/README.md
new file mode 100644
index 00000000..f4a39999
--- /dev/null
+++ b/vendor/alleyinteractive/traverse-reshape/README.md
@@ -0,0 +1,154 @@
+# traverse/reshape
+
+`traverse()` and `reshape()` are companion functions that safely break down arrays or objects and put them back together in new shapes.
+
+## Installation
+
+Install the latest version with:
+
+```bash
+$ composer require alleyinteractive/traverse-reshape
+```
+
+## Basic usage
+
+### traverse
+
+Traverse an array or an object using a delimiter to find one value or many values.
+
+```php
+ [
+ 'red' => [
+ 'gala',
+ 'mcintosh',
+ ],
+ 'green' => [
+ 'granny_smith',
+ ],
+ ],
+];
+
+$obj = (object) [
+ 'apples' => (object) [
+ 'red' => [
+ 'gala',
+ 'mcintosh',
+ ],
+ 'green' => [
+ 'granny_smith',
+ ],
+ ],
+];
+
+
+$green = \Alley\traverse($arr, 'apples.green');
+// ['granny_smith']
+
+$red = \Alley\traverse($obj, 'apples.red');
+// ['gala', 'mcintosh']
+
+[$red, $green] = \Alley\traverse($obj, ['apples.red', 'apples.green']);
+// ['gala', 'mcintosh'], ['granny_smith']
+
+[[$red, $green]] = \Alley\traverse(
+ $obj,
+ [
+ 'apples' => [
+ 'red',
+ 'green',
+ ],
+ ],
+);
+// ['gala', 'mcintosh'], ['granny_smith']
+// note the extra depth of the return value -- values are nested according to the nesting of the given paths
+
+[$red] = \Alley\traverse($obj, ['apples' => 'red']);
+// ['gala', 'mcintosh']
+
+$sweet = \Alley\traverse($arr, 'apples.green.sweet');
+// NULL
+
+$pears = \Alley\traverse($arr, 'pears');
+// NULL
+
+$req = getRemoteData();
+[$title, $date] = \Alley\traverse($req, ['title', 'date']);
+// $title and $date variables are guaranteed defined regardless of $req
+
+[[$red, $green], $title] = \Alley\traverse(
+ [$arr, $req],
+ [
+ '0.apples' => ['red', 'green'],
+ '1.title',
+ ]
+);
+```
+
+### reshape
+
+Declare the shape of a new array or object whose values are extracted from a source array or object with `traverse()`.
+
+Shapes can be multidimensional. Paths that do not resolve in the source will be `null` in the result. If a path is given without a key, the key will be inferred from the path. Passing an object for a shape returns an object instead of an array.
+
+```php
+ 1,
+ 'title' => [
+ 'rendered' => 'Hello world!',
+ ],
+ 'content' => [
+ 'rendered' => '
Welcome to WordPress. This is your first post. Edit or delete it, then start writing!
Welcome to WordPress...',
+ 'term_ids' => (object) [
+ 'category' => [1],
+ 'post_tag' => [],
+ ],
+ 'json' => 'https://www.example.com/...',
+ ]
+*/
+```
+
+## About
+
+### License
+
+[GPL-2.0-or-later](https://github.com/alleyinteractive/traverse-reshape/blob/main/LICENSE)
+
+### Maintainers
+
+[Alley Interactive](https://github.com/alleyinteractive)
diff --git a/vendor/alleyinteractive/traverse-reshape/composer.json b/vendor/alleyinteractive/traverse-reshape/composer.json
new file mode 100644
index 00000000..29aa463c
--- /dev/null
+++ b/vendor/alleyinteractive/traverse-reshape/composer.json
@@ -0,0 +1,43 @@
+{
+ "name": "alleyinteractive/traverse-reshape",
+ "description": "Safely break down arrays or objects, and put them back together in new shapes.",
+ "type": "library",
+ "license": "GPL-2.0-or-later",
+ "authors": [
+ {
+ "name": "Alley",
+ "email": "info@alley.com"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "Alley\\": "src/Alley/"
+ },
+ "files": [
+ "src/Alley/reshape.php",
+ "src/Alley/traverse.php"
+ ]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Alley\\": "tests/Alley/"
+ }
+ },
+ "config": {
+ "lock": false
+ },
+ "require": {
+ "php": "^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.8",
+ "phpunit/phpunit": "^9.5",
+ "squizlabs/php_codesniffer": "^3.6"
+ },
+ "scripts": {
+ "fixer": "php-cs-fixer -v fix --allow-risky=yes",
+ "phpcbf": "phpcbf",
+ "phpcs": "phpcs",
+ "phpunit": "phpunit"
+ }
+}
diff --git a/vendor/alleyinteractive/traverse-reshape/src/Alley/Internals/Traverser.php b/vendor/alleyinteractive/traverse-reshape/src/Alley/Internals/Traverser.php
new file mode 100644
index 00000000..b0dd5a09
--- /dev/null
+++ b/vendor/alleyinteractive/traverse-reshape/src/Alley/Internals/Traverser.php
@@ -0,0 +1,179 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Alley\Internals;
+
+/**
+ * Contains the implementation details for the `traverse()` functions.
+ *
+ * Internals are not subject to semantic-versioning constraints.
+ */
+final class Traverser
+{
+ /**
+ * Data source.
+ *
+ * @var array|object
+ */
+ private $source;
+
+ /**
+ * Path or array of paths to data.
+ *
+ * @var array|string
+ */
+ private $path;
+
+ /**
+ * Path delimiter.
+ *
+ * @var string
+ */
+ private string $delimiter;
+
+ /**
+ * Found result.
+ *
+ * @var mixed
+ */
+ private $result = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array|object $source Data source.
+ * @param string|array $path The path or array of paths.
+ * @param string $delimiter Delimiter.
+ */
+ public function __construct($source, $path, string $delimiter)
+ {
+ $this->source = $source;
+ $this->path = $path;
+ $this->delimiter = $delimiter;
+ }
+
+ /**
+ * Create an instance and return the traversed value.
+ *
+ * @param mixed ...$args Constructor arguments.
+ * @return mixed
+ */
+ public static function createAndGet(...$args)
+ {
+ $instance = new self(...$args);
+ return $instance->get();
+ }
+
+ /**
+ * Get the traversed value.
+ *
+ * @return mixed
+ */
+ public function get()
+ {
+ $this->run();
+
+ return $this->result;
+ }
+
+ /**
+ * Run the traversal.
+ */
+ private function run()
+ {
+ if (\is_array($this->path)) {
+ $this->result = array_reduce(array_keys($this->path), [$this, 'reducePaths'], []);
+ return;
+ }
+
+ if (!\is_string($this->path) && !\is_int($this->path)) {
+ return;
+ }
+
+ /*
+ * Convert the path supplied to this instance into an array using the
+ * delimiter supplied by this instance:
+ *
+ * - `0` becomes `[ 0 ]`
+ * - `foo` becomes `[ 'foo' ]`
+ * - `foo.bar` becomes `[ 'foo', 'bar' ]` if the delimiter is `.`
+ */
+ $path_pieces = explode($this->delimiter, $this->path);
+
+ /*
+ * Take the first element from the newly generated array of path pieces,
+ * and use it as a key to try to find the matching value in the source
+ * data supplied to this instance. If there is no matching array key or
+ * object property in the source data, stop.
+ */
+ $key = array_shift($path_pieces);
+
+ if (\is_array($this->source) || $this->source instanceof \ArrayAccess) {
+ if (!isset($this->source[$key])) {
+ /*
+ * `isset()` is used instead of `array_key_exists()` for
+ * compatibility with `\ArrayAccess`. Nothing is lost by the
+ * fact that `isset()` returns false for a null value because
+ * the default value for `$result` is also null, and attempting
+ * to continue traversing null would still return null.
+ */
+ return;
+ }
+
+ $this->result = $this->source[$key];
+ } elseif (\is_object($this->source)) {
+ if (!isset($this->source->{$key})) {
+ return;
+ }
+
+ $this->result = $this->source->{$key};
+ }
+
+ // If there are no more path pieces left to traverse, stop.
+ if (0 === \count($path_pieces)) {
+ return;
+ }
+
+ /*
+ * Otherwise, there are more pieces left from the path supplied to the
+ * instance. Traverse the value found for the previous key, using the
+ * remaining path pieces and the delimiter to create a new string path.
+ * This process repeats until there are no more path pieces.
+ */
+ $this->result = self::createAndGet($this->result, implode($this->delimiter, $path_pieces), $this->delimiter);
+ }
+
+ /**
+ * `array_reduce()` callback to reduce an array of paths into the array of values.
+ *
+ * @param array $carry Traversal result.
+ * @param string|int $key Key in the array of paths to fetch.
+ * @return array Updated array of results.
+ */
+ private function reducePaths(array $carry, $key): array
+ {
+ $source = $this->source;
+
+ /*
+ * If the key is a non-numeric string, traverse the value in the
+ * original source at the path specified by the key.
+ */
+ if (!is_numeric($key)) {
+ $source = self::createAndGet($source, $key, $this->delimiter);
+ }
+
+ $carry[] = self::createAndGet($source, $this->path[$key], $this->delimiter);
+
+ return $carry;
+ }
+}
diff --git a/vendor/alleyinteractive/traverse-reshape/src/Alley/reshape.php b/vendor/alleyinteractive/traverse-reshape/src/Alley/reshape.php
new file mode 100644
index 00000000..d602856a
--- /dev/null
+++ b/vendor/alleyinteractive/traverse-reshape/src/Alley/reshape.php
@@ -0,0 +1,77 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Alley;
+
+/**
+ * Reshape an array or object into new keys with values traversed from within the original.
+ *
+ * @param array|object $source Data source.
+ * @param array|object $shape An array or object of the new keys for the data and the paths to traverse in
+ * the source data to find the new values. Multidimensional values can be created
+ * by passing a fully-formed shape as a value in the array or object. A string value
+ * without a named key will use the final segment of the string as the key and the
+ * full string path as the value.
+ * @param string $delimiter Delimiter. Default is a '.'.
+ * @return array|object A new associative array or object from the keys and traversal paths of $shape.
+ */
+function reshape($source, $shape, string $delimiter = '.')
+{
+ /*
+ * If a string is included in $shape without a string key, use the last segment
+ * of the string as the reshaped key and the full string as the path.
+ */
+ $final = array_reduce(
+ array_keys((array) $shape),
+ function ($carry, $key) use ($shape, $delimiter) {
+ $path = traverse($shape, (string) $key);
+
+ if (\is_int($key)) {
+ $key_pieces = explode($delimiter, $path);
+ $key = array_pop($key_pieces);
+ }
+
+ // Later keys replace earlier ones but remain in their given position in the shape.
+ unset($carry[$key]);
+
+ return $carry + [$key => $path];
+ },
+ []
+ );
+
+ // Create the desired shape now to preserve the order of its keys during the final merge.
+ $out = array_fill_keys(array_keys($final), null);
+
+ // Key-value pairs for values that are strings and can be traversed in bulk.
+ $deferred = [];
+
+ foreach ($final as $key => $path) {
+ if (\is_string($path)) {
+ $deferred[$key] = $path;
+ } else {
+ // Fill in nested arrays now.
+ $out[$key] = reshape($source, $path, $delimiter);
+ }
+ }
+
+ // Merge the remaining keys on top.
+ $out = array_merge(
+ $out,
+ array_combine(
+ array_keys($deferred),
+ traverse($source, array_values($deferred), $delimiter)
+ )
+ );
+
+ return \is_object($shape) ? (object) $out : $out;
+}
diff --git a/vendor/alleyinteractive/traverse-reshape/src/Alley/traverse.php b/vendor/alleyinteractive/traverse-reshape/src/Alley/traverse.php
new file mode 100644
index 00000000..8ecb056d
--- /dev/null
+++ b/vendor/alleyinteractive/traverse-reshape/src/Alley/traverse.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace Alley;
+
+/**
+ * Pluck one or many values from nested arrays or objects using 'dot' notation.
+ *
+ * @param array|object $source Data source.
+ * @param string|array $path The path or array of paths to values separated by $delimiter.
+ * Any path in array of paths can itself be an array of paths.
+ * @param string $delimiter Delimiter. Default is a '.'.
+ * @return mixed The value or values if found or null.
+ */
+function traverse($source, $path, string $delimiter = '.')
+{
+ return Internals\Traverser::createAndGet($source, $path, $delimiter);
+}
diff --git a/vendor/alleyinteractive/wordpress-autoloader/.editorconfig b/vendor/alleyinteractive/wordpress-autoloader/.editorconfig
new file mode 100644
index 00000000..0a064551
--- /dev/null
+++ b/vendor/alleyinteractive/wordpress-autoloader/.editorconfig
@@ -0,0 +1,12 @@
+root = true
+
+[*]
+indent_style = tab
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.{js,css,scss,yml,json,.babelrc}]
+indent_style = space
+indent_size = 2
diff --git a/vendor/alleyinteractive/wordpress-autoloader/.gitattributes b/vendor/alleyinteractive/wordpress-autoloader/.gitattributes
new file mode 100644
index 00000000..f1a5ab0c
--- /dev/null
+++ b/vendor/alleyinteractive/wordpress-autoloader/.gitattributes
@@ -0,0 +1,26 @@
+#
+# Exclude these files from release archives.
+#
+# This will also make the files unavailable when using Composer with `--prefer-dist`.
+#
+# Via WPCS.
+#
+/.github export-ignore
+/phpcs.xml.dist export-ignore
+/.phpcs export-ignore
+/phpunit.xml export-ignore
+/tests export-ignore
+
+#
+# Auto detect text files and perform LF normalization.
+#
+# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/
+#
+* text=auto
+
+#
+# The above will handle all files not found below.
+#
+*.md text
+*.php text
+*.inc text
diff --git a/vendor/alleyinteractive/wordpress-autoloader/.gitignore b/vendor/alleyinteractive/wordpress-autoloader/.gitignore
new file mode 100644
index 00000000..0afacaed
--- /dev/null
+++ b/vendor/alleyinteractive/wordpress-autoloader/.gitignore
@@ -0,0 +1,6 @@
+vendor/
+composer.lock
+.phpcs.xml
+.phpunit.result.cache
+.vscode
+.idea
diff --git a/vendor/alleyinteractive/wordpress-autoloader/CHANGELOG.md b/vendor/alleyinteractive/wordpress-autoloader/CHANGELOG.md
new file mode 100644
index 00000000..1a2777c0
--- /dev/null
+++ b/vendor/alleyinteractive/wordpress-autoloader/CHANGELOG.md
@@ -0,0 +1,34 @@
+# Changelog
+
+All notable changes to `alleyinteractive/wordpress-autoloader` will be
+documented in this file.
+
+## [Unreleased]
+
+## 1.1.1 - 2022-08-31
+
+- Ensure file is still loaded with APCu.
+
+## 1.1.0 - 2022-08-09
+
+- Adding APCu caching of autoloaded classes.
+- Adds check to prevent multiple failed calls to autoload a class.
+
+## 1.0.0 - 2022-05-25
+
+## 0.2.0
+
+- Supporting PHP 8.1
+- Removing `preg_replace` with `str_*` functions.
+
+## 0.1.2
+
+- Small performance improvement.
+
+## 0.1.1
+
+- Ensure autoloader root path always has a trailing slash.
+
+## 0.1.0
+
+- Initial release.
diff --git a/vendor/alleyinteractive/wordpress-autoloader/README.md b/vendor/alleyinteractive/wordpress-autoloader/README.md
new file mode 100644
index 00000000..7cc50e69
--- /dev/null
+++ b/vendor/alleyinteractive/wordpress-autoloader/README.md
@@ -0,0 +1,51 @@
+# WordPress Autoloader
+
+[![Latest Version on Packagist](https://img.shields.io/packagist/v/alleyinteractive/wordpress-autoloader.svg?style=flat-square)](https://packagist.org/packages/alleyinteractive/wordpress-autoloader)
+[![Tests](https://github.com/alleyinteractive/wordpress-autoloader/actions/workflows/tests.yml/badge.svg)](https://github.com/alleyinteractive/wordpress-autoloader/actions/workflows/tests.yml)
+
+A PHP Autoloader that supports the [Wordpress Coding
+Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/php/). For example, a folder that looks like this would be autoloaded as:
+
+```
+src/class-example-class.php -> Root_Namespace\Example_Class
+src/trait-reusable-feature.php -> Root_Namesace\Reusable_Feature
+src/feature/class-example-feature.php -> Root_Namespace\Feature\Example_Feature
+```
+
+Supports `class`, `trait`, `interface`, and `enum` files and any level of
+namespaces.
+
+## Installation
+
+You can install the package via composer:
+
+```bash
+composer require alleyinteractive/wordpress-autoloader
+```
+
+## Usage
+
+```php
+Alley_Interactive\Autoloader\Autoloader::generate(
+ 'Plugin\\Namespace',
+ __DIR__ . '/src',
+)->register();
+
+// Or register the autoloader manually.
+spl_autoload_register(
+ Alley_Interactive\Autoloader\Autoloader::generate(
+ 'Plugin\\Namespace',
+ __DIR__ . '/src',
+ )
+);
+```
+
+## Testing
+
+```bash
+composer test
+```
+
+## Changelog
+
+Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
diff --git a/vendor/alleyinteractive/wordpress-autoloader/composer.json b/vendor/alleyinteractive/wordpress-autoloader/composer.json
new file mode 100644
index 00000000..ee1441e9
--- /dev/null
+++ b/vendor/alleyinteractive/wordpress-autoloader/composer.json
@@ -0,0 +1,43 @@
+{
+ "name": "alleyinteractive/wordpress-autoloader",
+ "type": "library",
+ "description": "Autoload files using WordPress File Conventions",
+ "license": "GPL-2.0-or-later",
+ "authors": [
+ {
+ "name": "Alley Interactive",
+ "email": "info@alley.co"
+ }
+ ],
+ "require": {
+ "php": "^7.4.0|^8.0|^8.1"
+ },
+ "require-dev": {
+ "alleyinteractive/alley-coding-standards": "^0.3",
+ "phpunit/phpunit": "^9.5.8"
+ },
+ "config": {
+ "sort-packages": true,
+ "allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/class-autoloader.php"
+ ]
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "scripts": {
+ "lint": "@phpcs",
+ "lint:fix": "@phpcbf",
+ "phpcbf": "phpcbf --standard=./phpcs.xml.dist .",
+ "phpcs": "phpcs --standard=./phpcs.xml.dist .",
+ "phpunit": "vendor/bin/phpunit",
+ "test": [
+ "@phpcs",
+ "@phpunit"
+ ]
+ }
+}
diff --git a/vendor/alleyinteractive/wordpress-autoloader/src/class-autoloader.php b/vendor/alleyinteractive/wordpress-autoloader/src/class-autoloader.php
new file mode 100644
index 00000000..f2a08085
--- /dev/null
+++ b/vendor/alleyinteractive/wordpress-autoloader/src/class-autoloader.php
@@ -0,0 +1,203 @@
+
+ */
+ protected array $missing_classes = [];
+
+ /**
+ * APCu cache key prefix.
+ *
+ * @var ?string
+ */
+ protected ?string $apcu_prefix;
+
+ /**
+ * Generate an autoloader for the WordPress file naming conventions.
+ *
+ * @param string $namespace Namespace to autoload.
+ * @param string $root_path Path in which to look for files.
+ * @return static Function for spl_autoload_register().
+ */
+ public static function generate( string $namespace, string $root_path ): callable {
+ return new static( $namespace, $root_path );
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param string $namespace Namespace to register.
+ * @param string $root_path Root path of the namespace.
+ */
+ public function __construct( string $namespace, string $root_path ) {
+ $this->namespace = $namespace;
+
+ // Ensure consistent root.
+ $this->root_path = rtrim( $root_path, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $prefix Prefix to use.
+ * @return static
+ */
+ public function set_apcu_prefix( string $prefix ) {
+ $this->apcu_prefix = function_exists( 'apcu_fetch' ) && filter_var( ini_get( 'apc.enabled' ), FILTER_VALIDATE_BOOLEAN )
+ ? $prefix
+ : null;
+
+ return $this;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function get_apcu_prefix(): ?string {
+ return $this->apcu_prefix;
+ }
+
+ /**
+ * Check if a class was missing from the autoloader.
+ *
+ * @param string $classname Class to check.
+ * @return bool
+ */
+ public function is_missing_class( string $classname ): bool {
+ return isset( $this->missing_classes[ $classname ] );
+ }
+
+ /**
+ * Register the autoloader.
+ */
+ public function register() {
+ spl_autoload_register( $this );
+ }
+
+ /**
+ * Unregister the autoloader.
+ */
+ public function unregister() {
+ spl_autoload_unregister( $this );
+ }
+
+ /**
+ * Invoke method of the class.
+ *
+ * @param string $classname Class being autoloaded.
+ */
+ public function __invoke( string $classname ) {
+ // Ignore if the base namespace doesn't match.
+ if ( 0 !== \strpos( $classname, $this->namespace ) ) {
+ return;
+ }
+
+ // Check if the class was previously not found.
+ if ( isset( $this->missing_classes[ $classname ] ) ) {
+ return;
+ }
+
+ // Check if the class was previously found with APCu caching.
+ if ( isset( $this->apcu_prefix ) ) {
+ $hit = false;
+ $file = apcu_fetch( $this->apcu_prefix . $classname, $hit );
+
+ if ( $hit ) {
+ require_once $file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
+ return $file;
+ }
+ }
+
+ $file = $this->find_file( $classname );
+
+ if ( $file ) {
+ require_once $file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
+
+ // Cache the found file with APCu if enabled.
+ if ( isset( $this->apcu_prefix ) ) {
+ apcu_add( $this->apcu_prefix . $classname, $file );
+ }
+ } else {
+ // Mark the class as not found to save future lookups.
+ $this->missing_classes[ $classname ] = true;
+ }
+ }
+
+ /**
+ * Find a file for the given class.
+ *
+ * @param string $classname Class to find.
+ * @return string|null
+ */
+ protected function find_file( string $classname ): ?string {
+ // Break up the classname into parts.
+ $parts = \explode( '\\', $classname );
+
+ // Retrieve the class name (last item) and convert it to a filename.
+ $class = \strtolower( \str_replace( '_', '-', \array_pop( $parts ) ) );
+
+ $base_path = '';
+
+ // Build the base path relative to the sub-namespace.
+ $sub_namespace = \substr( \implode( DIRECTORY_SEPARATOR, $parts ), \strlen( $this->namespace ) );
+
+ if ( ! empty( $sub_namespace ) ) {
+ $base_path = \str_replace( '_', '-', \strtolower( $sub_namespace ) );
+ }
+
+ // Support multiple locations since the class could be a class, trait or interface.
+ $paths = [
+ '%1$s' . DIRECTORY_SEPARATOR . 'class-%2$s.php',
+ '%1$s' . DIRECTORY_SEPARATOR . 'trait-%2$s.php',
+ '%1$s' . DIRECTORY_SEPARATOR . 'interface-%2$s.php',
+ '%1$s' . DIRECTORY_SEPARATOR . 'enum-%2$s.php',
+ ];
+
+ /*
+ * Attempt to find the file by looping through the various paths.
+ *
+ * Autoloading a class will also cause a trait or interface with the
+ * same fully qualified name to be autoloaded, as it's impossible to
+ * tell which was requested.
+ */
+ foreach ( $paths as $path ) {
+ $path = $this->root_path . \sprintf( $path, $base_path, $class );
+
+ if ( \file_exists( $path ) ) {
+ return $path;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/vendor/alleyinteractive/wp-match-blocks/.editorconfig b/vendor/alleyinteractive/wp-match-blocks/.editorconfig
new file mode 100644
index 00000000..2708ec41
--- /dev/null
+++ b/vendor/alleyinteractive/wp-match-blocks/.editorconfig
@@ -0,0 +1,19 @@
+# EditorConfig helps maintain consistent coding styles for developers working on the same project across various editors and IDEs.
+# See https://editorconfig.org.
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.php]
+indent_size = 4
+indent_style = tab
+
+[*.xml]
+indent_size = 4
diff --git a/vendor/alleyinteractive/wp-match-blocks/.gitattributes b/vendor/alleyinteractive/wp-match-blocks/.gitattributes
new file mode 100644
index 00000000..1f5f3ab0
--- /dev/null
+++ b/vendor/alleyinteractive/wp-match-blocks/.gitattributes
@@ -0,0 +1,27 @@
+#
+# Exclude these files from release archives.
+#
+# This will also make the files unavailable when using Composer with `--prefer-dist`.
+# If you develop using Composer, use `--prefer-source`.
+#
+# Via WPCS.
+#
+/.github export-ignore
+/.php-cs-fixer.dist.php export-ignore
+/phpcs.xml export-ignore
+/phpunit.xml export-ignore
+/tests export-ignore
+
+#
+# Auto detect text files and perform LF normalization.
+#
+# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/
+#
+* text=auto
+
+#
+# The above will handle all files not found below.
+#
+*.md text
+*.php text
+*.inc text
diff --git a/vendor/alleyinteractive/wp-match-blocks/.gitignore b/vendor/alleyinteractive/wp-match-blocks/.gitignore
new file mode 100644
index 00000000..f7334559
--- /dev/null
+++ b/vendor/alleyinteractive/wp-match-blocks/.gitignore
@@ -0,0 +1,5 @@
+vendor
+composer.lock
+.php_cs.cache
+.phpunit.result.cache
+.php-cs-fixer.cache
diff --git a/vendor/alleyinteractive/wp-match-blocks/CHANGELOG.md b/vendor/alleyinteractive/wp-match-blocks/CHANGELOG.md
new file mode 100644
index 00000000..37d79b99
--- /dev/null
+++ b/vendor/alleyinteractive/wp-match-blocks/CHANGELOG.md
@@ -0,0 +1,52 @@
+# Changelog
+
+This library adheres to [Semantic Versioning](https://semver.org/) and [Keep a CHANGELOG](https://keepachangelog.com/en/1.0.0/).
+
+## Unreleased
+
+Nothing yet.
+
+## 3.1.0
+
+### Changed
+
+* Reduce uses of validators within validators.
+
+## 3.0.0
+
+### Changed
+
+* "Classic" blocks, which have inner HTML but no block name, are no longer considered empty.
+
+## 2.0.1
+
+### Fixed
+
+- Missing `alleyinteractive/composer-wordpress-autoloader` dependency.
+
+## 2.0.0
+
+### Added
+
+- `with_innerhtml` parameter for matching blocks by their inner HTML. Includes companion `\Alley\WP\Validator\Block_InnerHTML` validator.
+- `has_innerblocks` parameter for matching blocks by whether they have inner blocks. Includes companion `\Alley\WP\Validator\Block_InnerBlocks_Count` validator.
+- `is_valid` parameter for matching blocks by custom validation logic.
+- `CONTAINS` and `NOT CONTAINS` (case-sensitive), and `LIKE` and `NOT LIKE` (case-insensitive) operators to `attrs` parameter.
+
+### Changed
+
+- Passing a single block instance will return matches within its inner blocks.
+
+### Removed
+
+- PHP 7.4 support.
+
+## 1.0.1
+
+### Fixed
+
+- Incorrect namespace in `README.md` examples.
+
+## 1.0.0
+
+Initial release.
diff --git a/vendor/alleyinteractive/wp-match-blocks/LICENSE b/vendor/alleyinteractive/wp-match-blocks/LICENSE
new file mode 100644
index 00000000..d159169d
--- /dev/null
+++ b/vendor/alleyinteractive/wp-match-blocks/LICENSE
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/vendor/alleyinteractive/wp-match-blocks/README.md b/vendor/alleyinteractive/wp-match-blocks/README.md
new file mode 100644
index 00000000..9d4505f1
--- /dev/null
+++ b/vendor/alleyinteractive/wp-match-blocks/README.md
@@ -0,0 +1,669 @@
+# Match Blocks
+
+`match_blocks()` selects the blocks in post content, or in a given set of blocks, inner blocks, or block HTML, that match the given criteria, such as the block name, block attributes, or position within the set of blocks.
+
+Blocks can be matched by:
+
+* Block name or names (`name`)
+* Block attributes (`attrs`, `with_attrs`)
+* Block inner HTML (`with_innerhtml`)
+* The block's positive or negative index within the set (`position`)
+* Whether the block represents only space (`skip_empty_blocks`)
+* Whether the block has inner blocks (`has_innerblocks`)
+* Custom validation classes (`is_valid`)
+
+Passing matching parameters is optional; all non-empty blocks match by default.
+
+Additionally:
+
+* Recursion into inner blocks is supported (`flatten`).
+* The set of matching blocks can be limited by size (`limit`) or their position in the set of matches (`nth_of_type`).
+* The number of matches can be returned instead of the matched blocks (`count`).
+* The companion `match_block()` function reduces the filtered set of results to a single parsed block.
+* Passing a single block instance will return matches from its inner blocks.
+
+`match_blocks()` is powered by a set of block validation classes that utilize the [Laminas Validator](https://docs.laminas.dev/laminas-validator/) framework and [Laminas Validator Extensions](https://github.com/alleyinteractive/laminas-validator-extensions) package. These validators, along with a base class for validating blocks, are included here. [See the validators section for their documentation](#validators).
+
+## Installation
+
+Install the latest version with:
+
+```bash
+$ composer require alleyinteractive/wp-match-blocks
+```
+
+## Basic usage
+
+Find all paragraph blocks in a post:
+
+```php
+ 'core/paragraph' ] );
+```
+
+Include paragraphs in inner blocks:
+
+```php
+ true,
+ 'name' => 'core/paragraph',
+ ]
+);
+```
+
+Get the number of paragraph blocks:
+
+```php
+ true,
+ 'name' => 'core/paragraph',
+ ]
+);
+```
+
+Get the number of paragraph blocks that are inner blocks of the given group block:
+
+```php
+
…' );
+
+$count = \Alley\WP\match_blocks(
+ $blocks[0],
+ [
+ 'count' => true,
+ 'name' => 'core/paragraph',
+ ]
+);
+```
+
+Get all paragraphs and headings:
+
+```php
+…',
+ [
+ 'name' => [ 'core/heading', 'core/paragraph' ],
+ ]
+);
+```
+
+Get only paragraphs that have been explicitly aligned:
+
+```php
+ 'core/paragraph',
+ 'with_attrs' => 'align',
+ ]
+);
+```
+
+Get only paragraphs that have been right-aligned:
+
+```php
+ [
+ [
+ 'key' => 'align',
+ 'value' => 'right',
+ ],
+ ],
+ 'name' => 'core/paragraph',
+ ]
+);
+```
+
+Get only paragraphs that have been aligned, but not to the right:
+
+```php
+ [
+ [
+ 'key' => 'align',
+ 'value' => 'right',
+ 'operator' => '!==',
+ ],
+ ],
+ 'name' => 'core/paragraph',
+ ]
+);
+```
+
+Get only paragraphs that have been aligned to the left or the right:
+
+```php
+ [
+ [
+ 'key' => 'align',
+ 'value' => [ 'left', 'right' ],
+ 'operator' => 'IN',
+ ],
+ ],
+ 'name' => 'core/paragraph',
+ ]
+);
+```
+
+Get all images credited to the Associated Press:
+
+```php
+ [
+ [
+ 'key' => 'credit',
+ 'value' => '/(The )?Associated Press/i',
+ 'operator' => 'REGEX',
+ ],
+ [
+ 'key' => 'credit',
+ 'value' => 'AP',
+ ],
+ 'relation' => 'OR',
+ ],
+ 'name' => 'core/image',
+ ]
+);
+```
+
+Get shortcode blocks with a specific shortcode:
+
+```php
+ 'core/shortcode',
+ 'with_innerhtml' => '[bc_video',
+ ]
+);
+```
+
+Audit a post for YouTube embed blocks that reference videos that are no longer accessible.
+
+```php
+ 'core/embed',
+ 'attrs' => [
+ [
+ 'key' => 'providerNameSlug',
+ 'value' => 'youtube',
+ ],
+ ],
+ 'is_valid' => new \Alley\Validator\Not( new YouTube_Video_Exists(), '…' ),
+ ],
+);
+```
+
+Get the first three blocks:
+
+```php
+ 3,
+ ]
+);
+```
+
+Get the first three paragraph blocks:
+
+```php
+ 3,
+ 'name' => 'core/paragraph',
+ ]
+);
+```
+
+Get the third paragraph:
+
+```php
+ 'core/paragraph',
+ 'nth_of_type' => 3,
+ ]
+);
+
+// Or, skip straight to the parsed block:
+
+$block = \Alley\WP\match_block(
+ $post,
+ [
+ 'name' => 'core/paragraph',
+ 'nth_of_type' => '3n',
+ ]
+);
+```
+
+Get every third paragraph:
+
+```php
+ 'core/paragraph',
+ 'nth_of_type' => '3n',
+ ]
+);
+```
+
+Get paragraphs 3-8:
+
+```php
+ 'core/paragraph',
+ 'nth_of_type' => [ 'n+3', '-n+8' ],
+ ]
+);
+```
+
+Get the block at position 3 in the set if it's a paragraph:
+
+```php
+ 'core/paragraph',
+ 'position' => 3,
+ ]
+);
+```
+
+Get the last two blocks:
+
+```php
+ [ -1, -2 ],
+ ]
+);
+```
+
+Get all non-empty blocks:
+
+```php
+ null,
+ 'skip_empty_blocks' => false,
+ ]
+);
+```
+
+Get only blocks with inner blocks:
+
+```php
+ true,
+ ]
+);
+```
+
+Get only blocks without inner blocks:
+
+```php
+ false,
+ ]
+);
+```
+
+## Validators
+
+This package provides classes for validating blocks based on the [Laminas Validator](https://docs.laminas.dev/laminas-validator/) framework and [Laminas Validator Extensions](https://github.com/alleyinteractive/laminas-validator-extensions) package, plus a custom base block validation class.
+
+`match_blocks()` also uses these validators internally, and they can be passed as the `is_valid` parameter to `match_blocks()` or used on their own.
+
+### Base Validator
+
+The abstract `Alley\WP\Validator\BlockValidator` class extends `Alley\Validator\BaseValidator` and, much like `BaseValidator`, standardizes validation of blocks.
+
+When extending `BlockValidator`, validation logic goes into a `test_block()` method. `test_block()` always receives a `\WP_Block_Parser_Block` instance; validation will automatically fail if the input is not an instance of `\WP_Block`, `\WP_Block_Parser_Block`, or an array representation of a block.
+
+### `Block_Attribute`
+
+`Alley\WP\Validator\Block_Attribute` validates whether the block contains, or does not contain, the specified attribute name, value, or name-value pair.
+
+The block passes if a name comparison is specified, and the block contains an attribute whose name matches the comparison; if a value comparison is specified, and the block contains an attribute whose value matches the comparison; or if both name and value comparisons are specified, and the block contains an attribute with a matching name and value.
+
+#### Supported options
+
+The following options are supported for `Alley\WP\Validator\Block_Attribute`:
+
+- `key`: The name of a block attribute, or an array of names, or a regular expression pattern. Default none.
+- `value`: A block attribute value, or an array of values, or regular expression pattern. Default none.
+- `operator`: The operator with which to compare `$value` to block attributes. Accepts `CONTAINS`, `NOT CONTAINS` (case-sensitive), `IN`, `NOT IN`, `LIKE`, `NOT LIKE` (case-insensitive), `REGEX`, `NOT REGEX`, or any operator supported by `\Alley\Validator\Comparison`. Default is `===`.
+- `key_operator`: Equivalent to `operator` but for `$key`.
+
+#### Basic usage
+
+```php
+';
+
+$valid = new Alley\WP\Validator\Block_Attribute(
+ [
+ 'key' => 'mediaType',
+ 'value' => 'image',
+ ],
+);
+
+$valid = new Alley\WP\Validator\Block_Attribute(
+ [
+ 'key' => [ 'mediaType', 'mediaId' ],
+ 'key_operator' => 'IN',
+ ],
+);
+
+$valid = new Alley\WP\Validator\Block_Attribute(
+ [
+ 'key' => '/^media/',
+ 'key_operator' => 'REGEX',
+ 'value' => [ 'image', 'video' ],
+ 'operator' => 'IN',
+ ],
+);
+
+$valid = new Alley\WP\Validator\Block_Attribute(
+ [
+ 'key' => '/^media/',
+ 'key_operator' => 'REGEX',
+ 'value' => [ 'audio', 'document' ],
+ 'operator' => 'NOT IN',
+ ],
+);
+```
+### `Block_InnerHTML`
+
+`Alley\WP\Validator\Block_InnerHTML` validates whether the block contains, or does not contain, the specified content in its `innerHTML` property. The block passes if it contains an `innerHTML` value that matches the comparison.
+
+#### Supported options
+
+The following options are supported for `Alley\WP\Validator\Block_InnerHTML`:
+
+- `content`: The content to find or a regular expression pattern.
+- `operator`: The operator with which to compare `$content` to the block inner HTML. Accepts `CONTAINS`, `NOT CONTAINS` (case-sensitive), `IN`, `NOT IN`, `LIKE`, `NOT LIKE` (case-insensitive), `REGEX`, `NOT REGEX`, or any operator supported by `\Alley\Validator\Comparison`. Default is `LIKE`.
+
+#### Basic usage
+
+```php
+
+//
The goal of this new editor is to make adding rich content to WordPress simple and enjoyable.
/',
+ 'operator' => 'NOT REGEX',
+ ],
+);
+```
+
+### `Block_Name`
+
+`Alley\WP\Validator\Block_Name` validates whether a block has a given name or one of a set of names. The block passes validation if the block name is in the set.
+
+#### Supported options
+
+The following options are supported for `Alley\WP\Validator\Block_Name`:
+
+-`name`: The block name or names.
+
+#### Basic usage
+
+```php
+ 'core/paragraph',
+ ]
+);
+
+$valid = new Alley\WP\Validator\Block_Name(
+ [
+ 'name' => [ 'core/gallery', 'jetpack/slideshow', 'jetpack/tiled-gallery' ],
+ ]
+);
+```
+
+### `Block_Offset`
+
+`Alley\WP\Validator\Block_Offset` validates whether the block appears at one of the given numeric offsets within a list of blocks.
+
+The block matches if it appears at one of the offsets in the list.
+
+Identity is determined by comparing the `\WP_Block_Parser_Block` instances as arrays.
+
+#### Supported options
+
+The following options are supported for `Alley\WP\Validator\Block_Offset`:
+
+- `blocks`: An array or iterable of blocks.
+- `offset`: The expected offset or offsets. Negative offsets count from the end of the list.
+- `skip_empty_blocks`: Whether to skip blocks that are "empty" according to the `Nonempty_Block_Validator` when indexing `$blocks`. Default true.
+
+#### Basic usage
+
+```php
+
Hello, world!
+
+
+
+
+HTML
+);
+
+$valid = new Alley\WP\Validator\Block_Offset(
+ [
+ 'blocks' => $blocks,
+ 'offset' => 1,
+ ],
+);
+$valid->isValid( $blocks[2] ); // true
+
+$valid = new Alley\WP\Validator\Block_Offset(
+ [
+ 'blocks' => $blocks,
+ 'offset' => [ 4 ],
+ 'skip_empty_blocks' => false,
+ ],
+);
+$valid->isValid( $blocks[4] ); // true
+
+$valid = new Alley\WP\Validator\Block_Offset(
+ [
+ 'blocks' => $blocks,
+ 'offset' => -2,
+ ],
+);
+$valid->isValid( $blocks[2] ); // true
+```
+
+### `Block_InnerBlocks_Count`
+
+`Alley\WP\Validator\Block_InnerBlocks_Count` validates whether the number of inner blocks in the given block passes the specified comparison.
+
+The block passes validation if the comparison is true for the count of inner blocks. Inner blocks within inner blocks don't count towards the total.
+
+#### Supported options
+
+The following options are supported for `Alley\WP\Validator\Block_InnerBlocks_Count`:
+
+* `count`: The expected number of inner blocks for the comparison.
+* `operator`: The PHP comparison operator used to compare the input block's inner blocks and `count`.
+
+#### Basic usage
+
+```php
+
+
+
+
+
+HTML
+);
+
+$valid = new \Alley\WP\Validator\Block_InnerBlocks_Count(
+ [
+ 'count' => 1,
+ 'operator' => '===',
+ ]
+);
+$valid->isValid( $blocks[0] ); // true
+
+$valid = new \Alley\WP\Validator\Block_InnerBlocks_Count(
+ [
+ 'count' => 0,
+ 'operator' => '>',
+ ]
+);
+$valid->isValid( $blocks[0] ); // true
+
+$valid = new \Alley\WP\Validator\Block_InnerBlocks_Count(
+ [
+ 'count' => 42,
+ 'operator' => '<=',
+ ]
+);
+$valid->isValid( $blocks[0] ); // true
+```
+
+### `Nonempty_Block`
+
+`Alley\WP\Validator\Nonempty_Block` validates that the given block is not "empty" -- for example, not a block representing only line breaks.
+
+The block passes validation if it has a non-null name.
+
+#### Supported options
+
+None.
+
+#### Basic usage
+
+```php
+isValid( $blocks[0] ); // false
+```
+
+## About
+
+### License
+
+[GPL-2.0-or-later](https://github.com/alleyinteractive/wp-match-blocks/blob/main/LICENSE)
+
+### Maintainers
+
+[Alley Interactive](https://github.com/alleyinteractive)
diff --git a/vendor/alleyinteractive/wp-match-blocks/composer.json b/vendor/alleyinteractive/wp-match-blocks/composer.json
new file mode 100644
index 00000000..37b0535b
--- /dev/null
+++ b/vendor/alleyinteractive/wp-match-blocks/composer.json
@@ -0,0 +1,51 @@
+{
+ "name": "alleyinteractive/wp-match-blocks",
+ "description": "Match WordPress blocks in the given content.",
+ "type": "library",
+ "license": "GPL-2.0-or-later",
+ "authors": [
+ {
+ "name": "Alley",
+ "email": "info@alley.com"
+ }
+ ],
+ "autoload": {
+ "files": [
+ "src/alley/wp/match-blocks.php",
+ "src/alley/wp/internals/internals.php"
+ ]
+ },
+ "config": {
+ "allow-plugins": {
+ "alleyinteractive/composer-wordpress-autoloader": true,
+ "dealerdirect/phpcodesniffer-composer-installer": true
+ },
+ "lock": false
+ },
+ "require": {
+ "php": "^8.0",
+ "alleyinteractive/composer-wordpress-autoloader": "^1.0.0",
+ "alleyinteractive/laminas-validator-extensions": "^2.0.0"
+ },
+ "require-dev": {
+ "alleyinteractive/alley-coding-standards": "^1.0.0",
+ "friendsofphp/php-cs-fixer": "^3.8",
+ "mantle-framework/testkit": "^0.9"
+ },
+ "scripts": {
+ "fixer": "php-cs-fixer -v fix --allow-risky=yes",
+ "phpcbf": "phpcbf",
+ "phpcs": "phpcs",
+ "phpunit": "phpunit"
+ },
+ "extra": {
+ "wordpress-autoloader": {
+ "autoload": {
+ "Alley\\": "src/alley/"
+ },
+ "autoload-dev": {
+ "Alley\\": "tests/alley/"
+ }
+ }
+ }
+}
diff --git a/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/internals/internals.php b/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/internals/internals.php
new file mode 100644
index 00000000..f32265e3
--- /dev/null
+++ b/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/internals/internals.php
@@ -0,0 +1,175 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @package wp-match-blocks
+ */
+
+namespace Alley\WP\Internals;
+
+use Alley\Validator\AnyValidator;
+use Alley\Validator\FastFailValidatorChain;
+use Alley\WP\Validator\Block_Attribute;
+use Laminas\Validator\ValidatorInterface;
+
+/**
+ * Merge inner blocks into the top-level array of blocks.
+ *
+ * @param array $blocks Blocks.
+ * @return array More blocks.
+ */
+function flatten_blocks( array $blocks ): array {
+ $out = [];
+
+ while ( $blocks ) {
+ $block = array_shift( $blocks );
+ $out[] = $block;
+
+ if ( ! empty( $block['innerBlocks'] ) ) {
+ array_unshift( $blocks, ...$block['innerBlocks'] );
+ }
+ }
+
+ return $out;
+}
+
+/**
+ * Parse 'attrs' clauses into validators.
+ *
+ * @throws \Exception If clauses are malformed.
+ *
+ * @param array $args 'attrs' argument.
+ * @return ValidatorInterface
+ */
+function parse_attrs_clauses( array $args ): ValidatorInterface {
+ $relation = 'AND';
+
+ if ( isset( $args['relation'] ) && 'OR' === strtoupper( $args['relation'] ) ) {
+ $relation = 'OR';
+ }
+
+ unset( $args['relation'] );
+
+ $chain = [];
+
+ foreach ( $args as $clause ) {
+ if ( ! \is_array( $clause ) ) {
+ continue;
+ }
+
+ if ( isset( $clause['relation'] ) || isset( $clause[0] ) ) {
+ $chain[] = parse_attrs_clauses( $clause );
+ continue;
+ }
+
+ $chain[] = new Block_Attribute( $clause );
+ }
+
+ if ( \count( $chain ) === 0 ) {
+ throw new \Exception();
+ }
+
+ if ( 'AND' === $relation ) {
+ return new FastFailValidatorChain( $chain );
+ }
+
+ // If it's not AND then it's OR.
+ return new AnyValidator( $chain );
+}
+
+/**
+ * Parse the 'nth_of_type' parameter into the matching 1-based indices.
+ *
+ * @param int|int[]|string|string[] $args 'nth_of_type' argument.
+ * @param int $max Total number of available blocks.
+ * @return int[] Matching indices within the set of available blocks.
+ */
+function parse_nth_of_type( $args, int $max ): array {
+ if ( \is_int( $args ) ) {
+ return [ $args ];
+ }
+
+ $args = (array) $args;
+
+ if ( array_filter( $args, 'is_int' ) === $args ) {
+ return $args;
+ }
+
+ $selectors = $args;
+ unset( $selectors['relation'] );
+
+ $matches = [];
+
+ foreach ( $selectors as $selector ) {
+ if ( 'odd' === $selector ) {
+ $selector = '2n+1';
+ }
+
+ if ( 'even' === $selector ) {
+ $selector = '2n';
+ }
+
+ if ( preg_match( '/^([+-]?\d+)?(-?n)?([+-]\d+)?$/', $selector, $pieces ) ) {
+ $a = isset( $pieces[1] ) && \strlen( $pieces[1] ) ? $pieces[1] : null;
+ $n = $pieces[2] ?? null;
+ $b = $pieces[3] ?? null;
+
+ if ( ! $n ) {
+ // Matches '\d'.
+ return [ (int) $a ];
+ }
+
+ if ( $n && '0' === $a ) {
+ // Matches '0n' or '0n+\d'.
+ return isset( $b ) ? [ (int) $b ] : [];
+ }
+
+ $indices = [];
+ $i = 0;
+
+ while ( true ) {
+ $next = $i;
+
+ if ( '-n' === $n ) {
+ $next *= -1;
+ }
+
+ if ( isset( $a ) ) {
+ $next *= (int) $a;
+ }
+
+ if ( isset( $b ) ) {
+ $next += (int) $b;
+ }
+
+ if ( $max < abs( $next ) ) {
+ break;
+ }
+
+ $indices[] = $next;
+ $i++;
+ }
+
+ $matches[] = $indices;
+ }
+ }
+
+ if ( ! $matches ) {
+ return [];
+ }
+
+ if ( \count( $matches ) === 1 ) {
+ return $matches[0];
+ }
+
+ if ( isset( $args['relation'] ) && 'OR' === $args['relation'] ) {
+ return array_unique( array_merge( ...$matches ) );
+ }
+
+ return array_intersect( ...$matches );
+}
diff --git a/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/match-blocks.php b/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/match-blocks.php
new file mode 100644
index 00000000..2cd52806
--- /dev/null
+++ b/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/match-blocks.php
@@ -0,0 +1,264 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @package wp-match-blocks
+ */
+
+namespace Alley\WP;
+
+use Alley\Validator\FastFailValidatorChain;
+use Alley\WP\Validator\Block_InnerHTML;
+use Alley\WP\Validator\Block_Name;
+use Alley\WP\Validator\Block_Offset;
+use Alley\WP\Validator\Block_InnerBlocks_Count;
+use Alley\WP\Validator\Nonempty_Block;
+use Laminas\Validator\ValidatorInterface;
+
+/**
+ * Match blocks within the given content.
+ *
+ * @param int|\WP_Post|string|array[]|\WP_Block_Parser_Block|array $source Post ID or object with blocks in `post_content`, string of block HTML,
+ * array of blocks, or a single block instance. Passing a single block
+ * will return matches from its inner blocks.
+ * @param array $args {
+ * Optional. Array of arguments for matching which blocks to return. The defaults match all non-empty blocks.
+ *
+ * @type array $attrs {
+ * Match blocks with the given attributes.
+ *
+ * @type string $relation Optional. The keyword used to join the block attribute clauses. Accepts 'AND', or 'OR'. Default 'AND'.
+ * @type array ...$0 {
+ * An array of attribute clause parameters, or another fully formed array of attributes to match.
+ *
+ * @type string|string[] $key The name of a block attribute, or an array of names, or a regular
+ * expression pattern. Default none.
+ * @type mixed $value A block attribute value, or an array of values, or regular
+ * expression pattern. Default none.
+ * @type string $operator The operator with which to compare `$value` to block attributes.
+ * Accepts `CONTAINS`, `NOT CONTAINS` (case-sensitive), `IN`, `NOT IN`,
+ * `LIKE`, `NOT LIKE` (case-insensitive), `REGEX`, `NOT REGEX`, or any
+ * operator supported by `\Alley\Validator\Comparison`. Default `===`.
+ * @type string $key_operator Equivalent to `$operator` but for `$key`.
+ * }
+ * }
+ * @type bool $count Return the number of found blocks instead of the set.
+ * @type bool $has_innerblocks Return only blocks that have, or don't have, inner blocks.
+ * @type ValidatorInterface $is_valid Match blocks that pass the given validator.
+ * @type bool $flatten Recursively descend into inner blocks, test each one against the
+ * criteria, and count each towards totals. Default false.
+ * @type int $limit Extract at most this many blocks. Default `-1`, or no limit.
+ * @type int|int[]|string|string[] $nth_of_type {
+ * Extract blocks based on their position in the set of found blocks.
+ *
+ * @type string $relation Optional. The keyword used to join 'An+B' selectors if more than one is passed.
+ * Accepts 'AND', or 'OR'. Default 'AND'. Integer arrays are always joined with 'OR'.
+ * @type int|int[]|string|string[] ...$0 A 1-based integer index or array of indices, a `An+B` pattern (like the `:nth-child`
+ * pattern in CSS), or an array of `An+B` patterns for matching 1-based indices.
+ * }
+ * @type string|string[] $name Match blocks with this block name or names.
+ * @type int|int[] $position Match blocks that appear at the given index. A negative index counts from the end.
+ * Note that all blocks with identical HTML to the matched block will also match.
+ * @type bool $skip_empty_blocks Ignore blocks representing white space. Default true.
+ * @type string|string[] $with_attrs Match blocks with non-empty values for this attribute or these attributes.
+ * Blocks must match all of the given `$with_attrs` and `$attrs`.
+ * @type string $with_innerhtml Match blocks whose `innerHTML` property contains this content, ignoring case.
+ * }
+ * @return array[]|int Array of found blocks or count thereof.
+ */
+function match_blocks( $source, $args = [] ) {
+ $args = wp_parse_args(
+ $args,
+ [
+ 'attrs' => [],
+ 'count' => false,
+ 'flatten' => false,
+ 'has_innerblocks' => null,
+ 'is_valid' => null,
+ 'limit' => -1,
+ 'name' => '',
+ 'nth_of_type' => null,
+ 'position' => null,
+ 'skip_empty_blocks' => true,
+ 'with_attrs' => [],
+ 'with_innerhtml' => null,
+ ],
+ );
+
+ $blocks = [];
+ $error = $args['count'] ? 0 : [];
+
+ if ( $source instanceof \WP_Block_Parser_Block ) {
+ $source = (array) $source;
+ }
+
+ if ( \is_array( $source ) ) {
+ $blocks = $source;
+ }
+
+ if ( is_numeric( $source ) || $source instanceof \WP_Post ) {
+ $post = get_post( $source );
+
+ if ( ! $post ) {
+ return $error;
+ }
+
+ $blocks = parse_blocks( $post->post_content );
+ }
+
+ if ( \is_string( $source ) ) {
+ $blocks = parse_blocks( $source );
+ }
+
+ if ( \is_array( $blocks ) && isset( $blocks['innerBlocks'] ) ) {
+ $blocks = $blocks['innerBlocks'];
+ }
+
+ if ( ! wp_is_numeric_array( $blocks ) || 0 === \count( $blocks ) ) {
+ return $error;
+ }
+
+ if ( $args['flatten'] ) {
+ $blocks = Internals\flatten_blocks( $blocks );
+ }
+
+ try {
+ $validator = new FastFailValidatorChain( [] );
+
+ if ( $args['skip_empty_blocks'] ) {
+ $validator->attach( new Nonempty_Block() );
+ }
+
+ if ( '' !== $args['name'] ) {
+ $validator->attach(
+ new Block_Name(
+ [
+ 'name' => $args['name'],
+ ],
+ ),
+ );
+ }
+
+ if ( null !== $args['position'] ) {
+ $validator->attach(
+ new Block_Offset(
+ [
+ 'blocks' => $blocks,
+ 'offset' => $args['position'],
+ 'skip_empty_blocks' => $args['skip_empty_blocks'],
+ ],
+ ),
+ );
+ }
+
+ if ( $args['with_attrs'] ) {
+ $args['attrs'] = [
+ 'relation' => 'AND',
+ [
+ 'key' => (array) $args['with_attrs'],
+ 'key_operator' => 'IN',
+ 'value' => '',
+ 'operator' => '!=',
+ ],
+ $args['attrs'],
+ ];
+ }
+
+ if ( $args['attrs'] && \is_array( $args['attrs'] ) ) {
+ $validator->attach(
+ Internals\parse_attrs_clauses( $args['attrs'] ),
+ );
+ }
+
+ if ( \is_string( $args['with_innerhtml'] ) || $args['with_innerhtml'] instanceof \Stringable ) {
+ $validator->attach(
+ new Block_InnerHTML(
+ [
+ 'content' => $args['with_innerhtml'],
+ 'operator' => 'LIKE',
+ ],
+ ),
+ );
+ }
+
+ if ( null !== $args['has_innerblocks'] ) {
+ $validator->attach(
+ new Block_InnerBlocks_Count(
+ [
+ 'count' => 0,
+ 'operator' => $args['has_innerblocks'] ? '>' : '===',
+ ],
+ ),
+ );
+ }
+
+ if ( $args['is_valid'] instanceof ValidatorInterface ) {
+ $validator->attach( $args['is_valid'] );
+ }
+ } catch ( \Exception $exception ) {
+ return $error;
+ }
+
+ // Reduce to matching indices.
+ $matches = array_map( [ $validator, 'isValid' ], $blocks );
+ $matches = array_filter( $matches );
+ $matches = array_keys( $matches );
+
+ if ( null !== $args['nth_of_type'] ) {
+ // These are 1-based indices. Map them to 0-based.
+ $nth_of_type = Internals\parse_nth_of_type( $args['nth_of_type'], \count( $matches ) );
+ $nth_indices = array_map(
+ fn( $nth ) => (int) $nth - 1,
+ $nth_of_type
+ );
+
+ // Flip indices into array keys, then intersect with keys of matched blocks.
+ $nth_as_keys = array_flip( $nth_indices );
+ $matches = array_intersect_key( $matches, $nth_as_keys );
+ }
+
+ if ( $args['limit'] >= 0 ) {
+ $matches = \array_slice( $matches, 0, $args['limit'] );
+ }
+
+ if ( $args['count'] ) {
+ return \count( $matches );
+ }
+
+ // Flip indices into array keys.
+ $matches = array_flip( $matches );
+
+ // Intersect matching keys with keys in original list of blocks.
+ $matches = array_intersect_key( $blocks, $matches );
+
+ // Return matched blocks in a new list.
+ return array_values( $matches );
+}
+
+/**
+ * Return the first matching block from `match_blocks()`, if any.
+ *
+ * @param array|int|\WP_Post|string $source See `match_blocks()`.
+ * @param array $args See `match_blocks()`.
+ * @return array|int|null The found block or null.
+ */
+function match_block( $source, $args = [] ): ?array {
+ $args['limit'] = 1;
+
+ $blocks = match_blocks( $source, $args );
+
+ if ( \is_int( $blocks ) ) {
+ return $blocks;
+ }
+
+ if ( isset( $blocks[0] ) && \is_array( $blocks[0] ) ) {
+ return $blocks[0];
+ }
+
+ return null;
+}
diff --git a/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-block-attribute.php b/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-block-attribute.php
new file mode 100644
index 00000000..ebf3f334
--- /dev/null
+++ b/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-block-attribute.php
@@ -0,0 +1,246 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @package wp-match-blocks
+ */
+
+namespace Alley\WP\Validator;
+
+use Alley\Validator\AlwaysValid;
+use Alley\Validator\ValidatorByOperator;
+use Laminas\Validator\Exception\InvalidArgumentException;
+use Laminas\Validator\ValidatorInterface;
+use Traversable;
+use WP_Block;
+use WP_Block_Parser_Block;
+use WP_Error;
+
+/**
+ * Validates whether the given block contains the specified attribute.
+ */
+final class Block_Attribute extends Block_Validator {
+ /**
+ * Error code.
+ *
+ * @var string
+ */
+ public const NO_MATCHING_KEY = 'no_matching_key';
+
+ /**
+ * Error code.
+ *
+ * @var string
+ */
+ public const NO_MATCHING_VALUE = 'no_matching_value';
+
+ /**
+ * Internal symbol.
+ *
+ * @var string
+ */
+ private const UNDEFINED = '__UNDEFINED__';
+
+ /**
+ * Array of validation failure message templates.
+ *
+ * @var string[]
+ */
+ protected $messageTemplates = [
+ self::NO_MATCHING_KEY => '',
+ self::NO_MATCHING_VALUE => '',
+ ];
+
+ /**
+ * Options for this validator.
+ *
+ * @var array
+ */
+ protected $options = [
+ 'key' => null,
+ 'key_operator' => '===',
+ 'value' => self::UNDEFINED,
+ 'operator' => '===',
+ ];
+
+ /**
+ * Validates attribute keys based on options.
+ *
+ * @var ValidatorInterface
+ */
+ private ValidatorInterface $key_validator;
+
+ /**
+ * Validates attribute values based on options.
+ *
+ * @var ValidatorInterface
+ */
+ private ValidatorInterface $value_validator;
+
+ /**
+ * Set up.
+ *
+ * @param array|Traversable $options Validator options.
+ */
+ public function __construct( $options = null ) {
+ $this->messageTemplates = [
+ self::NO_MATCHING_KEY => __( 'Block must have attribute with eligible key.', 'alley' ),
+ self::NO_MATCHING_VALUE => __( 'Block must have attribute with eligible value.', 'alley' ),
+ ];
+
+ $this->key_validator = new AlwaysValid();
+ $this->value_validator = new AlwaysValid();
+
+ parent::__construct( $options );
+ }
+
+ /**
+ * Sets one or multiple options. Merges new options into existing options, validates them in relation to one
+ * another, and refreshes the cached validators for the comparisons.
+ *
+ * @throws InvalidArgumentException If requested comparisons are invalid.
+ *
+ * @param array|Traversable $options Options to set.
+ * @return self
+ */
+ public function setOptions( $options = [] ) {
+ $next = $this->options;
+
+ foreach ( $options as $key => $value ) {
+ if ( \array_key_exists( $key, $next ) ) {
+ $next[ $key ] = $value;
+ }
+ }
+
+ if ( null !== $next['key'] ) {
+ try {
+ $this->key_validator = new ValidatorByOperator( $next['key_operator'], $next['key'] );
+ } catch ( \Exception $exception ) {
+ throw new InvalidArgumentException( 'Invalid clause for attribute key: ' . $exception->getMessage() );
+ }
+ }
+
+ if ( self::UNDEFINED !== $next['value'] ) {
+ try {
+ $this->value_validator = new ValidatorByOperator( $next['operator'], $next['value'] );
+ } catch ( \Exception $exception ) {
+ throw new InvalidArgumentException( 'Invalid clause for attribute value: ' . $exception->getMessage() );
+ }
+ }
+
+ $options = array_merge( $options, $next );
+
+ /*
+ * Temporarily move 'value' option to a new key so that
+ * `\Laminas\Validator\AbstractValidator::setOptions()`
+ * doesn't attempt to pass it to `::setValue()`.
+ */
+ $options['val'] = $options['value'];
+ unset( $options['value'] );
+
+ return parent::setOptions( $options );
+ }
+
+ /**
+ * Restore 'value' option.
+ *
+ * @param mixed $val Value.
+ */
+ protected function setVal( $val ) {
+ $this->options['value'] = $val;
+ }
+
+ /**
+ * Apply block validation logic and add any validation errors.
+ *
+ * @param WP_Block_Parser_Block $block The block to test.
+ */
+ protected function test_block( WP_Block_Parser_Block $block ): void {
+ $attrs = $block->attrs;
+
+ // For each test performed against the attributes, an array of matching attribute indices.
+ $tests = [];
+
+ // For each test performed against the attributes, the number of failed attributes.
+ $misses = [];
+
+ [ $hit_indices, $miss_count ] = self::test(
+ $this->key_validator,
+ array_keys( $attrs ),
+ );
+
+ $tests[] = $hit_indices;
+ $misses[] = $miss_count;
+
+ if ( ! self::passing( $tests, $misses ) ) {
+ $this->error( self::NO_MATCHING_KEY );
+ return;
+ }
+
+ [ $hit_indices, $miss_count ] = self::test(
+ $this->value_validator,
+ array_values( $attrs )
+ );
+
+ $tests[] = $hit_indices;
+ $misses[] = $miss_count;
+
+ if ( ! self::passing( $tests, $misses ) ) {
+ $this->error( self::NO_MATCHING_VALUE );
+ return;
+ }
+ }
+
+ /**
+ * Test each of the given values against a configured operator.
+ *
+ * @param ValidatorInterface $validator Validator to test against.
+ * @param array $values Indexed list of values to test.
+ * @return array The indices of $values that passed the comparison and the
+ * number of $values that failed the comparison.
+ */
+ private static function test( ValidatorInterface $validator, array $values ): array {
+ $hit_indices = [];
+
+ foreach ( $values as $i => $value ) {
+ if ( $validator->isValid( $value ) ) {
+ $hit_indices[] = $i;
+ }
+ }
+
+ return [ $hit_indices, \count( $values ) - \count( $hit_indices ) ];
+ }
+
+ /**
+ * Whether the block is passing validation.
+ *
+ * @param array[] $tests Zero or more arrays, each containing the indices of
+ * attribute keys or values that passed a comparison.
+ * @param int[] $misses The number of failed comparisons across tests of
+ * attribute keys and values.
+ * @return bool
+ */
+ private static function passing( array $tests, array $misses ): bool {
+ return (
+ (
+ // No comparisons were specified for key or value.
+ \count( $tests ) === 0
+ // At least one attribute was available on the block to test.
+ || ( \count( array_merge( ...$tests ) ) > 0 || array_filter( $misses ) )
+ )
+ && (
+ // Both key and value were tested, and at least one index matched in both.
+ ( \count( $tests ) > 1 && array_intersect( ...$tests ) )
+ // Either key or value was tested, and at least one index matched.
+ || ( \count( $tests ) === 1 ) && \count( ...$tests ) > 0
+ // No tests missed, if any ran.
+ || ! array_filter( $misses )
+ )
+ );
+ }
+}
diff --git a/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-block-innerblocks-count.php b/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-block-innerblocks-count.php
new file mode 100644
index 00000000..4960fc3b
--- /dev/null
+++ b/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-block-innerblocks-count.php
@@ -0,0 +1,248 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @package wp-match-blocks
+ */
+
+namespace Alley\WP\Validator;
+
+use Alley\Validator\AlwaysValid;
+use Alley\Validator\Comparison;
+use Alley\Validator\Not;
+use Alley\Validator\WithMessage;
+use Laminas\Validator\Exception\InvalidArgumentException;
+use Laminas\Validator\ValidatorInterface;
+use Traversable;
+use WP_Block;
+use WP_Block_Parser_Block;
+use WP_Error;
+
+/**
+ * Validates whether the given block has a number of inner blocks.
+ */
+final class Block_InnerBlocks_Count extends Block_Validator {
+ /**
+ * Array of validation failure message templates.
+ *
+ * @var string[]
+ */
+ protected $messageTemplates = [
+ 'not_equal' => '',
+ 'not_identical' => '',
+ 'is_equal' => '',
+ 'is_identical' => '',
+ 'not_less_than' => '',
+ 'not_greater_than' => '',
+ 'not_less_than_or_equal_to' => '',
+ 'not_greater_than_or_equal_to' => '',
+ 'invalid_comparison' => '',
+ 'default' => '',
+ ];
+
+ /**
+ * Array of additional variables available for validation failure messages.
+ *
+ * @var string[]
+ */
+ protected $messageVariables = [
+ 'count' => [
+ 'options' => 'count',
+ ],
+ ];
+
+ /**
+ * Options for this validator.
+ *
+ * @var array
+ */
+ protected $options = [
+ 'operator' => '>=',
+ 'count' => 0,
+ ];
+
+ /**
+ * Valid inner block counts based on options.
+ *
+ * @var ValidatorInterface
+ */
+ private ValidatorInterface $valid_comparisons;
+
+ /**
+ * Set up.
+ *
+ * @param array|Traversable $options Validator options.
+ */
+ public function __construct( $options = null ) {
+ $this->localize_templates();
+
+ $this->valid_comparisons = new Comparison(
+ [
+ 'operator' => $this->options['operator'],
+ 'compared' => $this->options['count'],
+ ],
+ );
+
+ parent::__construct( $options );
+ }
+
+ /**
+ * Sets one or multiple options. Refreshes the cached validators for the comparisons.
+ *
+ * @throws InvalidArgumentException If requested comparisons are invalid.
+ *
+ * @param array|Traversable $options Options to set.
+ * @return self
+ */
+ public function setOptions( $options = [] ) {
+ parent::setOptions( $options );
+
+ try {
+ $this->valid_comparisons = new Comparison(
+ [
+ 'operator' => $this->options['operator'],
+ 'compared' => $this->options['count'],
+ ],
+ );
+ } catch ( \Exception $exception ) {
+ $message = 'Invalid comparison options for count of inner blocks: ' . $exception->getMessage();
+
+ /*
+ * Force all blocks to fail validation while the new options are invalid in relation to one another.
+ * Don't try to undo the work of `parent::setOptions()`, since that might leave the validator in
+ * an unpredictable state.
+ */
+ $this->valid_comparisons = new WithMessage(
+ 'invalidComparison',
+ $message,
+ new Not( new AlwaysValid(), $message ),
+ );
+
+ throw new InvalidArgumentException( $message );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Apply block validation logic and add any validation errors.
+ *
+ * @param WP_Block_Parser_Block $block The block to test.
+ */
+ protected function test_block( WP_Block_Parser_Block $block ): void {
+ $count = \count( $block->innerBlocks );
+
+ if ( ! $this->valid_comparisons->isValid( $count ) ) {
+ $message_keys = array_keys( $this->valid_comparisons->getMessages() );
+
+ foreach ( $message_keys as $key ) {
+ $this->error( $this->message( $key ), $count );
+ }
+ }
+ }
+
+ /**
+ * Sets the 'count' option.
+ *
+ * @param int $count Option.
+ */
+ protected function setCount( $count ) {
+ $this->options['count'] = (int) $count;
+ }
+
+ /**
+ * This validator relies on other validators to perform the final comparison of the inner block count.
+ * Here, map failure message identifiers from those validators to the ones defined by this validator.
+ *
+ * @param string $origin Upstream validator failure message identifier.
+ * @return string This validator's identifier.
+ */
+ private function message( string $origin ): string {
+ switch ( $origin ) {
+ case 'notEqual':
+ return 'not_equal';
+ case 'notIdentical':
+ return 'not_identical';
+ case 'isEqual':
+ return 'is_equal';
+ case 'isIdentical':
+ return 'is_identical';
+ case 'notLessThan':
+ return 'not_less_than';
+ case 'notGreaterThan':
+ return 'not_greater_than';
+ case 'notLessThanOrEqualTo':
+ return 'not_less_than_or_equal_to';
+ case 'notGreaterThanOrEqualTo':
+ return 'not_greater_than_or_equal_to';
+ case 'invalidComparison':
+ return 'invalid_comparison';
+ default:
+ return 'default';
+ }
+ }
+
+ /**
+ * Localize message templates.
+ */
+ private function localize_templates(): void {
+ $neq = sprintf(
+ /* translators: 1: expected count placeholder, 2: actual count placeholder */
+ __( 'Number of inner blocks must be %1$s but is %2$s.', 'alley' ),
+ '%count%',
+ '%value%',
+ );
+
+ $ieq = sprintf(
+ /* translators: 1: expected count placeholder */
+ __( 'Number of inner blocks must not be %1$s.', 'alley' ),
+ '%count%',
+ );
+
+ $nlt = sprintf(
+ /* translators: 1: expected count placeholder, 2: actual count placeholder */
+ __( 'Number of inner blocks must be less than %1$s but is %2$s.', 'alley' ),
+ '%count%',
+ '%value%',
+ );
+
+ $ngt = sprintf(
+ /* translators: 1: expected count placeholder, 2: actual count placeholder */
+ __( 'Number of inner blocks must be greater than %1$s but is %2$s.', 'alley' ),
+ '%count%',
+ '%value%',
+ );
+
+ $nlte = sprintf(
+ /* translators: 1: expected count placeholder, 2: actual count placeholder */
+ __( 'Number of inner blocks must be less than or equal to %1$s but is %2$s.', 'alley' ),
+ '%count%',
+ '%value%',
+ );
+
+ $ngte = sprintf(
+ /* translators: 1: expected count placeholder, 2: actual count placeholder */
+ __( 'Number of inner blocks must be greater than or equal to %1$s but is %2$s.', 'alley' ),
+ '%count%',
+ '%value%',
+ );
+
+ $this->messageTemplates = [
+ 'not_equal' => $neq,
+ 'not_identical' => $neq,
+ 'is_equal' => $ieq,
+ 'is_identical' => $ieq,
+ 'not_less_than' => $nlt,
+ 'not_greater_than' => $ngt,
+ 'not_less_than_or_equal_to' => $nlte,
+ 'not_greater_than_or_equal_to' => $ngte,
+ 'invalid_comparison' => __( 'Invalid comparison options for count of inner blocks.', 'alley' ),
+ 'default' => __( 'Invalid count of inner blocks.', 'alley' ),
+ ];
+ }
+}
diff --git a/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-block-innerhtml.php b/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-block-innerhtml.php
new file mode 100644
index 00000000..429b0279
--- /dev/null
+++ b/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-block-innerhtml.php
@@ -0,0 +1,112 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @package wp-match-blocks
+ */
+
+namespace Alley\WP\Validator;
+
+use Alley\Validator\ValidatorByOperator;
+use Laminas\Validator\Exception\InvalidArgumentException;
+use Laminas\Validator\ValidatorInterface;
+use Traversable;
+use WP_Block;
+use WP_Block_Parser_Block;
+use WP_Error;
+
+/**
+ * Validates whether the given block's inner HTML contains the given content.
+ */
+final class Block_InnerHTML extends Block_Validator {
+ /**
+ * Error code.
+ *
+ * @var string
+ */
+ public const NO_MATCHING_INNERHTML = 'no_matching_innerhtml';
+
+ /**
+ * Array of validation failure message templates.
+ *
+ * @var string[]
+ */
+ protected $messageTemplates = [
+ self::NO_MATCHING_INNERHTML => '',
+ ];
+
+ /**
+ * Options for this validator.
+ *
+ * @var array
+ */
+ protected $options = [
+ 'content' => '',
+ 'operator' => 'LIKE',
+ ];
+
+ /**
+ * Validates block inner HTML based on options.
+ *
+ * @var ValidatorInterface
+ */
+ private ValidatorInterface $valid_html;
+
+ /**
+ * Set up.
+ *
+ * @param array|Traversable $options Validator options.
+ */
+ public function __construct( $options = null ) {
+ $this->messageTemplates[ self::NO_MATCHING_INNERHTML ] = __( 'Block inner HTML does not match.', 'alley' );
+
+ $this->valid_html = new ValidatorByOperator( $this->options['operator'], $this->options['content'] );
+
+ parent::__construct( $options );
+ }
+
+ /**
+ * Sets one or multiple options. Merges new options into existing options, validates them in relation to one
+ * another, and refreshes the cached validators for the comparisons.
+ *
+ * @throws InvalidArgumentException If requested comparisons are invalid.
+ *
+ * @param array|Traversable $options Options to set.
+ * @return self
+ */
+ public function setOptions( $options = [] ) {
+ $next = $this->options;
+
+ foreach ( $options as $key => $value ) {
+ if ( \array_key_exists( $key, $next ) ) {
+ $next[ $key ] = $value;
+ }
+ }
+
+ try {
+ $this->valid_html = new ValidatorByOperator( $next['operator'], $next['content'] );
+ } catch ( \Exception $exception ) {
+ throw new InvalidArgumentException( 'Invalid clause for inner HTML: ' . $exception->getMessage() );
+ }
+
+ $options = array_merge( $options, $next );
+
+ return parent::setOptions( $options );
+ }
+
+ /**
+ * Apply block validation logic and add any validation errors.
+ *
+ * @param WP_Block_Parser_Block $block The block to test.
+ */
+ protected function test_block( WP_Block_Parser_Block $block ): void {
+ if ( ! $this->valid_html->isValid( $block->innerHTML ) ) {
+ $this->error( self::NO_MATCHING_INNERHTML );
+ }
+ }
+}
diff --git a/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-block-name.php b/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-block-name.php
new file mode 100644
index 00000000..8b4330a7
--- /dev/null
+++ b/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-block-name.php
@@ -0,0 +1,138 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @package wp-match-blocks
+ */
+
+namespace Alley\WP\Validator;
+
+use Alley\Validator\Comparison;
+use Laminas\Validator\InArray;
+use Laminas\Validator\ValidatorInterface;
+use Traversable;
+use WP_Block;
+use WP_Block_Parser_Block;
+
+/**
+ * Validates whether a block has a given name or one of a set of names.
+ */
+final class Block_Name extends Block_Validator {
+ /**
+ * Error code.
+ *
+ * @var string
+ */
+ public const NOT_NAMED = 'not_named';
+
+ /**
+ * Error code.
+ *
+ * @var string
+ */
+ public const NAME_NOT_IN = 'name_not_in';
+
+ /**
+ * Array of validation failure message templates.
+ *
+ * @var string[]
+ */
+ protected $messageTemplates = [
+ self::NOT_NAMED => '',
+ self::NAME_NOT_IN => '',
+ ];
+
+ /**
+ * Array of additional variables available for validation failure messages.
+ *
+ * @var string[]
+ */
+ protected $messageVariables = [
+ 'name' => [
+ 'options' => 'name',
+ ],
+ 'block_name' => 'current_block_name',
+ ];
+
+ /**
+ * Options for this validator.
+ *
+ * @var array
+ */
+ protected $options = [
+ 'name' => null,
+ ];
+
+ /**
+ * Name of the block under test for use in error messages.
+ *
+ * @var string|null
+ */
+ protected ?string $current_block_name = '';
+
+ /**
+ * Set up.
+ *
+ * @param array|Traversable $options Validator options.
+ */
+ public function __construct( $options = null ) {
+ $this->messageTemplates[ self::NOT_NAMED ] = sprintf(
+ /* translators: 1: allowed names placeholder, 2: block name placeholder */
+ __( 'Block must be named %1$s; got %2$s.', 'alley' ),
+ '%name%',
+ '%block_name%',
+ );
+ $this->messageTemplates[ self::NAME_NOT_IN ] = sprintf(
+ /* translators: 1: allowed names placeholder, 2: block name placeholder */
+ __( 'Block name must be one of %1$s; got %2$s.', 'alley' ),
+ '%name%',
+ '%block_name%',
+ );
+
+ parent::__construct( $options );
+ }
+
+ /**
+ * Apply block validation logic and add any validation errors.
+ *
+ * @param WP_Block_Parser_Block $block The block to test.
+ */
+ protected function test_block( WP_Block_Parser_Block $block ): void {
+ $allowed = $this->options['name'];
+ $message = self::NAME_NOT_IN;
+
+ if ( ! \is_array( $allowed ) ) {
+ $allowed = [ $allowed ];
+ $message = self::NOT_NAMED;
+ }
+
+ if ( ! \in_array( $block->blockName, $allowed, true ) ) {
+ $this->error( $message );
+ }
+ }
+
+ /**
+ * Converts the input into a parser block instance to be validated.
+ *
+ * @param array|WP_Block|WP_Block_Parser_Block $value Original block.
+ */
+ protected function setValue( $value ) {
+ parent::setValue( $value );
+
+ $this->current_block_name = $this->value->blockName;
+ }
+
+ /**
+ * Sets the 'name' option.
+ *
+ * @param string|string[] $name Names.
+ */
+ protected function setName( $name ) {
+ $this->options['name'] = $name;
+ }
+}
diff --git a/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-block-offset.php b/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-block-offset.php
new file mode 100644
index 00000000..be72ee2c
--- /dev/null
+++ b/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-block-offset.php
@@ -0,0 +1,172 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @package wp-match-blocks
+ */
+
+namespace Alley\WP\Validator;
+
+use Alley\Validator\Type;
+use Laminas\Validator\Exception\InvalidArgumentException;
+use Laminas\Validator\ValidatorInterface;
+use Traversable;
+use WP_Block;
+use WP_Block_Parser_Block;
+use WP_Error;
+
+/**
+ * Validates whether the given block appears at an offset within a set of blocks.
+ */
+final class Block_Offset extends Block_Validator {
+ /**
+ * Error code.
+ *
+ * @var string
+ */
+ public const NOT_AT_OFFSET = 'not_at_offset';
+
+ /**
+ * Array of validation failure message templates.
+ *
+ * @var string[]
+ */
+ protected $messageTemplates = [
+ self::NOT_AT_OFFSET => '',
+ ];
+
+ /**
+ * Array of additional variables available for validation failure messages.
+ *
+ * @var string[]
+ */
+ protected $messageVariables = [
+ 'offset' => [
+ 'options' => 'offset',
+ ],
+ ];
+
+ /**
+ * Options for this validator.
+ *
+ * @var array
+ */
+ protected $options = [
+ 'blocks' => [],
+ 'offset' => 0,
+ 'skip_empty_blocks' => true,
+ ];
+
+ /**
+ * Blocks that will be used in validation based on options.
+ *
+ * @var WP_Block_Parser_Block[]
+ */
+ private array $final_blocks = [];
+
+ /**
+ * Set up.
+ *
+ * @param array|Traversable $options Validator options.
+ */
+ public function __construct( $options = null ) {
+ $this->messageTemplates[ self::NOT_AT_OFFSET ] = sprintf(
+ /* translators: %s: offset placeholder */
+ __( 'Must be at offset %s within the blocks.', 'alley' ),
+ '%offset%'
+ );
+
+ parent::__construct( $options );
+ }
+
+ /**
+ * Sets one or multiple options. Determines the final set of blocks.
+ *
+ * @param array|Traversable $options Options to set.
+ * @return self
+ */
+ public function setOptions( $options = [] ) {
+ parent::setOptions( $options );
+
+ $this->final_blocks = $this->options['blocks'];
+
+ if ( $this->options['skip_empty_blocks'] ) {
+ $this->final_blocks = array_filter( $this->final_blocks, [ new Nonempty_Block(), 'isValid' ] );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Apply block validation logic and add any validation errors.
+ *
+ * @param WP_Block_Parser_Block $block The block to test.
+ */
+ protected function test_block( WP_Block_Parser_Block $block ): void {
+ foreach ( (array) $this->options['offset'] as $offset ) {
+ $check = [];
+
+ /*
+ * Manually checking negative offsets is required because
+ * `array_slice()` will return the first item if the negative offset
+ * is out of bounds.
+ */
+ if ( 0 <= $offset || abs( $offset ) <= \count( $this->final_blocks ) ) {
+ $check = \array_slice( $this->final_blocks, $offset, 1 );
+ }
+
+ if ( isset( $check[0] ) && (array) $check[0] === (array) $block ) {
+ return;
+ }
+ }
+
+ $this->error( self::NOT_AT_OFFSET );
+ }
+
+ /**
+ * Sets the 'blocks' option.
+ *
+ * @throws InvalidArgumentException If blocks aren't iterable.
+ *
+ * @param array[]|WP_Block[]|WP_Block_Parser_Block[] $blocks Blocks.
+ */
+ protected function setBlocks( $blocks ) {
+ if ( ! is_iterable( $blocks ) ) {
+ throw new InvalidArgumentException( 'Blocks must be iterable.' );
+ }
+
+ if ( ! $blocks instanceof \Traversable ) {
+ $blocks = new \ArrayIterator( (array) $blocks );
+ }
+
+ $blocks = iterator_to_array( $blocks );
+ $blocks = array_map( [ self::class, 'to_parser_block' ], $blocks );
+
+ $this->options['blocks'] = array_values( $blocks );
+ }
+
+ /**
+ * Sets the 'offset' option.
+ *
+ * @param int|int[] $offset Offset or offsets.
+ */
+ protected function setOffset( $offset ) {
+ $offset = \is_array( $offset ) ? array_map( 'intval', $offset ) : (int) $offset;
+
+ $this->options['offset'] = $offset;
+ }
+
+ /**
+ * Sets the 'skip_empty_blocks' option.
+ *
+ * @param bool $skip Option.
+ */
+ protected function setSkipEmptyBlocks( $skip ) {
+ $this->options['skip_empty_blocks'] = (bool) $skip;
+ }
+}
diff --git a/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-block-validator.php b/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-block-validator.php
new file mode 100644
index 00000000..707f9129
--- /dev/null
+++ b/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-block-validator.php
@@ -0,0 +1,93 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @package wp-match-blocks
+ */
+
+namespace Alley\WP\Validator;
+
+use Alley\Validator\ExtendedAbstractValidator;
+use Laminas\Validator\Exception\InvalidArgumentException;
+use WP_Block;
+use WP_Block_Parser_Block;
+
+/**
+ * Abstract class for validating WordPress blocks.
+ */
+abstract class Block_Validator extends ExtendedAbstractValidator {
+ /**
+ * Properties that need to be in the submitted block to convert it into a parsed block.
+ *
+ * @var string[]
+ */
+ private const REQUIRED_BLOCK_KEYS = [ 'blockName', 'attrs', 'innerBlocks', 'innerHTML', 'innerContent' ];
+
+ /**
+ * Apply block validation logic and add any validation errors.
+ *
+ * @param WP_Block_Parser_Block $block The block to test.
+ */
+ abstract protected function test_block( WP_Block_Parser_Block $block ): void;
+
+ /**
+ * Apply validation logic and add any validation errors.
+ *
+ * @param mixed $value The value to test.
+ */
+ final protected function testValue( $value ): void {
+ $this->test_block( $value );
+ }
+
+ /**
+ * Converts the input into a parser block instance to be validated.
+ *
+ * @throws InvalidArgumentException If the submitted value can't be parsed into a block.
+ *
+ * @param array|WP_Block|WP_Block_Parser_Block $value Original block.
+ */
+ protected function setValue( $value ) {
+ $value = self::to_parser_block( $value );
+
+ parent::setValue( $value );
+ }
+
+ /**
+ * Convert a block or a representation thereof to a parser block object.
+ *
+ * @throws InvalidArgumentException If input cannot be parsed.
+ *
+ * @param array|WP_Block|WP_Block_Parser_Block $value Original block.
+ * @return WP_Block_Parser_Block|null Block instance.
+ */
+ protected static function to_parser_block( $value ): WP_Block_Parser_Block {
+ if ( $value instanceof WP_Block ) {
+ $value = $value->parsed_block;
+ }
+
+ if ( $value instanceof WP_Block_Parser_Block ) {
+ $value = (array) $value;
+ }
+
+ $actual_keys = array_keys( $value );
+
+ if ( array_diff( self::REQUIRED_BLOCK_KEYS, $actual_keys ) ) {
+ throw new InvalidArgumentException( __( 'Cannot parse block from input.', 'alley' ) );
+ }
+
+ $value = new WP_Block_Parser_Block(
+ $value['blockName'],
+ $value['attrs'],
+ $value['innerBlocks'],
+ $value['innerHTML'],
+ $value['innerContent'],
+ );
+
+ return $value;
+ }
+}
diff --git a/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-nonempty-block.php b/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-nonempty-block.php
new file mode 100644
index 00000000..f327e579
--- /dev/null
+++ b/vendor/alleyinteractive/wp-match-blocks/src/alley/wp/validator/class-nonempty-block.php
@@ -0,0 +1,62 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @package wp-match-blocks
+ */
+
+namespace Alley\WP\Validator;
+
+use Alley\Validator\AnyValidator;
+use Alley\Validator\Not;
+use Laminas\Validator\ValidatorInterface;
+use Traversable;
+use WP_Block_Parser_Block;
+
+/**
+ * Validates that the given block is not "empty" -- for example, not a block representing only line breaks.
+ */
+final class Nonempty_Block extends Block_Validator {
+ /**
+ * Error code.
+ *
+ * @var string
+ */
+ public const EMPTY_BLOCK = 'empty_block';
+
+ /**
+ * Array of validation failure message templates.
+ *
+ * @var string[]
+ */
+ protected $messageTemplates = [
+ self::EMPTY_BLOCK => '',
+ ];
+
+ /**
+ * Set up.
+ *
+ * @param array|Traversable $options Validator options.
+ */
+ public function __construct( $options = null ) {
+ $this->messageTemplates[ self::EMPTY_BLOCK ] = __( 'Block is empty.', 'alley' );
+
+ parent::__construct( $options );
+ }
+
+ /**
+ * Apply block validation logic and add any validation errors.
+ *
+ * @param WP_Block_Parser_Block $block The block to test.
+ */
+ protected function test_block( WP_Block_Parser_Block $block ): void {
+ if ( null === $block->blockName && preg_match( '#\S#', $block->innerHTML ) === 0 ) {
+ $this->error( self::EMPTY_BLOCK );
+ }
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/.editorconfig b/vendor/alleyinteractive/wp-type-extensions/.editorconfig
new file mode 100644
index 00000000..2708ec41
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/.editorconfig
@@ -0,0 +1,19 @@
+# EditorConfig helps maintain consistent coding styles for developers working on the same project across various editors and IDEs.
+# See https://editorconfig.org.
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.php]
+indent_size = 4
+indent_style = tab
+
+[*.xml]
+indent_size = 4
diff --git a/vendor/alleyinteractive/wp-type-extensions/.gitattributes b/vendor/alleyinteractive/wp-type-extensions/.gitattributes
new file mode 100644
index 00000000..8acdd2f5
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/.gitattributes
@@ -0,0 +1,28 @@
+#
+# Exclude these files from release archives.
+#
+# This will also make the files unavailable when using Composer with `--prefer-dist`.
+# If you develop using Composer, use `--prefer-source`.
+#
+# Via WPCS.
+#
+/.github export-ignore
+/.php-cs-fixer.dist.php export-ignore
+/phpcs.xml export-ignore
+/phpstan.neon export-ignore
+/phpunit.xml export-ignore
+/tests export-ignore
+
+#
+# Auto detect text files and perform LF normalization.
+#
+# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/
+#
+* text=auto
+
+#
+# The above will handle all files not found below.
+#
+*.md text
+*.php text
+*.inc text
diff --git a/vendor/alleyinteractive/wp-type-extensions/.gitignore b/vendor/alleyinteractive/wp-type-extensions/.gitignore
new file mode 100644
index 00000000..f7334559
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/.gitignore
@@ -0,0 +1,5 @@
+vendor
+composer.lock
+.php_cs.cache
+.phpunit.result.cache
+.php-cs-fixer.cache
diff --git a/vendor/alleyinteractive/wp-type-extensions/CHANGELOG.md b/vendor/alleyinteractive/wp-type-extensions/CHANGELOG.md
new file mode 100644
index 00000000..e8366294
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/CHANGELOG.md
@@ -0,0 +1,29 @@
+# Changelog
+
+This library adheres to [Semantic Versioning](https://semver.org/) and [Keep a CHANGELOG](https://keepachangelog.com/en/1.0.0/).
+
+## Unreleased
+
+Nothing yet.
+
+## 2.2.0
+
+### Added
+
+- `GTM_Script` feature.
+
+## 2.1.0
+
+### Changed
+
+- Support use of `WP_CLI_Feature` in WP-CLI packages.
+
+## 2.0.0
+
+### Changed
+
+- `Features` class renamed `Group`.
+
+## 1.0.0
+
+Initial release.
diff --git a/vendor/alleyinteractive/wp-type-extensions/LICENSE b/vendor/alleyinteractive/wp-type-extensions/LICENSE
new file mode 100644
index 00000000..d159169d
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/LICENSE
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/vendor/alleyinteractive/wp-type-extensions/README.md b/vendor/alleyinteractive/wp-type-extensions/README.md
new file mode 100644
index 00000000..02793eaa
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/README.md
@@ -0,0 +1,36 @@
+# Type Extensions
+
+Type Extensions provides a vocabulary of objects for WordPress projects in the form of interfaces representing those objects and implementations of those interfaces.
+
+The library is oriented toward a declarative style of development that makes use of object composition, in particular the decorator pattern.
+
+## Installation
+
+Install the latest version with:
+
+```bash
+$ composer require alleyinteractive/wp-type-extensions
+```
+
+## Basic usage
+
+Learn more about the objects included with Type Extensions in the [documentation for each type](#documentation).
+
+## Documentation
+
+- [Feature](docs/feature.md)
+- [Post IDs](docs/post-ids.md)
+- [Post Queries](docs/post-queries.md)
+- [Post Query](docs/post-query.md)
+- [Serialized Blocks](docs/serialized-blocks.md)
+- [Single Block](docs/single-block.md)
+
+## About
+
+### License
+
+[GPL-2.0-or-later](https://github.com/alleyinteractive/wp-type-extensions/blob/main/LICENSE)
+
+### Maintainers
+
+[Alley Interactive](https://github.com/alleyinteractive)
diff --git a/vendor/alleyinteractive/wp-type-extensions/composer.json b/vendor/alleyinteractive/wp-type-extensions/composer.json
new file mode 100644
index 00000000..7d244188
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/composer.json
@@ -0,0 +1,53 @@
+{
+ "name": "alleyinteractive/wp-type-extensions",
+ "description": "PHP interfaces and implementations for WordPress.",
+ "type": "library",
+ "license": "GPL-2.0-or-later",
+ "authors": [
+ {
+ "name": "Alley",
+ "email": "info@alley.com"
+ }
+ ],
+ "config": {
+ "allow-plugins": {
+ "alleyinteractive/composer-wordpress-autoloader": true,
+ "dealerdirect/phpcodesniffer-composer-installer": true
+ },
+ "lock": false
+ },
+ "require": {
+ "php": "^8.1",
+ "alleyinteractive/laminas-validator-extensions": "^2.0",
+ "alleyinteractive/wp-match-blocks": "^3.0",
+ "spatie/once": "^3.1"
+ },
+ "require-dev": {
+ "alleyinteractive/alley-coding-standards": "^1.0.0",
+ "friendsofphp/php-cs-fixer": "^3.8",
+ "mantle-framework/testkit": "^0.9",
+ "szepeviktor/phpstan-wordpress": "^1.1"
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Alley\\": "tests/alley/"
+ }
+ },
+ "extra": {
+ "wordpress-autoloader": {
+ "autoload": {
+ "Alley\\": "src/alley/"
+ },
+ "autoload-dev": {
+ "Alley\\": "tests/alley/"
+ }
+ }
+ },
+ "scripts": {
+ "fixer": "php-cs-fixer -v fix --allow-risky=yes",
+ "phpcbf": "phpcbf",
+ "phpcs": "phpcs",
+ "phpstan": "phpstan --memory-limit=512M",
+ "phpunit": "phpunit"
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/docs/feature.md b/vendor/alleyinteractive/wp-type-extensions/docs/feature.md
new file mode 100644
index 00000000..6d4a38c1
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/docs/feature.md
@@ -0,0 +1,53 @@
+# Feature interface
+
+The `Feature` interface describes a project feature. Features can be large or small, although smaller features can take advantage of decorators more easily. Use the `boot()` method to add actions and filters. Group related features with the `Features` class.
+
+## Definition
+
+```php
+interface Feature {
+ public function boot(): void;
+}
+```
+
+## Bundled implementations
+
+- [Conditional_Feature](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/features/class-conditional-feature.php): Boot a feature only when a condition is met.
+- [Group](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/features/class-group.php): Group related features.
+- [GTM_Script](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/features/class-gtm-script.php): Add the standard Google Tag Manager script and data layer.
+- [Lazy_Feature](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/features/class-lazy-feature.php): Instantiate a feature only when called upon.
+- [Quick_Feature](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/features/class-quick-feature.php): Make a callable a feature.
+- [Template_Feature](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/features/class-template-feature.php): Boot a feature only when templates load.
+- [WP_CLI_Feature](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/features/class-wp-cli-feature.php): Boot a feature only WP-CLI loads.
+
+## Basic usage
+
+```php
+use Alley\WP\Features\Group;
+use Alley\WP\Features\Quick_Feature;
+use Alley\WP\Features\Template_Feature;
+
+$queries = new Project\Post_Queries_Implementation(
+ /* ... */
+);
+
+$project = new Group(
+ new Group(
+ new Project\Ads_Backend_Feature(),
+ new Template_Feature(
+ origin: new Project\Ads_Frontend_Feature(),
+ ),
+ ),
+ new Project\Other_Feature(
+ queries: $queries,
+ ),
+ new Quick_Feature(
+ function () {
+ remove_action( /* ... */ );
+ remove_filter( /* ... */ );
+ },
+ )
+);
+
+$project->boot();
+```
diff --git a/vendor/alleyinteractive/wp-type-extensions/docs/post-ids.md b/vendor/alleyinteractive/wp-type-extensions/docs/post-ids.md
new file mode 100644
index 00000000..ba8c282f
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/docs/post-ids.md
@@ -0,0 +1,21 @@
+# Post IDs interface
+
+The `Post_IDs` interface describes an object containing post IDs, such as the IDs in a query or a curated set of featured posts.
+
+## Definition
+
+```php
+interface Post_IDs {
+ public function post_ids(): array;
+}
+```
+
+## Bundled implementations
+
+- [Empty_Post_IDs](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/post-ids/class-empty-post-ids.php): No post IDs.
+- [Post_IDs_Envelope](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/post-ids/class-post-ids-envelope.php): Instance from an existing set of IDs.
+- [Post_IDs_Once](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/post-ids/class-post-ids-once.php): Always returns the same set of IDs from the original instance.
+- [Used_Post_IDs](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/post-ids/class-used-post-ids.php): Track post IDs that have been used, e.g. while rendering a page.
+- [WP_Query_Post_IDs](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/post-ids/class-wp-query-post-ids.php): The post IDs from a `WP_Query`.
+
+All `Post_Query` implementations also implement `Post_IDs`.
diff --git a/vendor/alleyinteractive/wp-type-extensions/docs/post-queries.md b/vendor/alleyinteractive/wp-type-extensions/docs/post-queries.md
new file mode 100644
index 00000000..4737c0da
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/docs/post-queries.md
@@ -0,0 +1,20 @@
+# Post Queries interface
+
+The `Post_Queries` interface describes an object that contains queries for posts.
+
+## Definition
+
+```php
+interface Post_Queries {
+ public function query( array $args ): Post_Query;
+}
+```
+
+## Bundled implementations
+
+- [Default_Post_Queries](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/post-queries/class-default-post-queries.php): Queries implementation for most cases.
+- [Enforced_Date_Queries](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/post-queries/class-enforced-date-queries.php): Queries that enforce a date query.
+- [Exclude_Queries](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/post-queries/class-exclude-queries.php): Queries that exclude some posts.
+- [Memoized_Post_Queries](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/post-queries/class-memoized-post-queries.php): Reuse queries given the same arguments.
+- [Optimistic_Date_Queries](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/post-queries/class-optimistic-date-queries.php): Speculate (but don't require) that queries can be limited to posts published after the given dates.
+- [Variable_Post_Queries](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/post-queries/class-variable-post-queries.php): Choose queries based on the result of a validation test.
diff --git a/vendor/alleyinteractive/wp-type-extensions/docs/post-query.md b/vendor/alleyinteractive/wp-type-extensions/docs/post-query.md
new file mode 100644
index 00000000..9921a93f
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/docs/post-query.md
@@ -0,0 +1,19 @@
+# Post Query interface
+
+The `Post_Query` interface describes an object that contains a single query for posts.
+
+## Definition
+
+```php
+interface Post_Query extends Post_IDs {
+ public function query_object(): WP_Query;
+
+ public function post_objects(): array;
+}
+```
+
+## Bundled implementations
+
+- [Global_Post_Query](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/post-query/class-global-post-query.php): Post_Query for a query in `$GLOBALS`.
+- [Post_IDs_Query](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/post-query/class-post-ids-query.php): Query from post IDs.
+- [WP_Query_Envelope](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/post-query/class-wp-query-envelope.php): Post_Query from an existing query.
diff --git a/vendor/alleyinteractive/wp-type-extensions/docs/serialized-blocks.md b/vendor/alleyinteractive/wp-type-extensions/docs/serialized-blocks.md
new file mode 100644
index 00000000..315af69c
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/docs/serialized-blocks.md
@@ -0,0 +1,22 @@
+# Serialized Blocks interface
+
+The `Serialized_Blocks` interface describes an object containing blocks that can be serialized to block markup. Any string can be serialized to block markup via the `Block_Content` class.
+
+## Definition
+
+```php
+interface Serialized_Blocks {
+ public function serialized_blocks(): string;
+}
+```
+
+## Bundled implementations
+
+- [Blocks](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/blocks/class-blocks.php): Bundle many blocks.
+- [Each_Replaced_Blocks](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/blocks/class-each-replaced-blocks.php): Replace each matched block with other block content.
+- [Matched_Blocks](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/blocks/class-matched-blocks.php): Blocks matched with `match_blocks()`.
+- [Block_Content](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/blocks/class-block-content.php): Blocks in the given content.
+- [Lazy_Blocks](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/blocks/class-lazy-blocks.php): Instantiate blocks only when called upon.
+
+All `Single_Block` implementations also implement `Serialized_Blocks`.
+
diff --git a/vendor/alleyinteractive/wp-type-extensions/docs/single-block.md b/vendor/alleyinteractive/wp-type-extensions/docs/single-block.md
new file mode 100644
index 00000000..b31a823e
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/docs/single-block.md
@@ -0,0 +1,20 @@
+# Single Block interface
+
+The `Single_Block` interface describes an object containing a single block.
+
+## Definition
+
+```php
+interface Single_Block {
+ public function block_name(): ?string;
+
+ public function parsed_block(): array;
+}
+```
+
+## Bundled implementations
+
+- [Named_Block](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/blocks/class-named-block.php): A single block with the given block name.
+- [Parsed_Block](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/blocks/class-parsed-block.php): A single parsed block.
+- [Default_Classname_Block](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/blocks/class-default-classname-block.php): Block wrapped in the block default classname.
+- [Inner_Blocks_Prepended](https://github.com/alleyinteractive/wp-type-extensions/blob/main/src/alley/wp/blocks/class-inner-blocks-prepended.php): Block with prepended inner blocks.
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-block-content.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-block-content.php
new file mode 100644
index 00000000..a7edbd05
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-block-content.php
@@ -0,0 +1,33 @@
+content;
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-blocks.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-blocks.php
new file mode 100644
index 00000000..545ea9a8
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-blocks.php
@@ -0,0 +1,68 @@
+blocks = $blocks;
+ }
+
+ /**
+ * Serialized block content.
+ *
+ * @return string
+ */
+ public function serialized_blocks(): string {
+ return array_reduce(
+ $this->blocks,
+ fn ( string $carry, Serialized_Blocks $block ) => $carry .= $block->serialized_blocks(),
+ '',
+ );
+ }
+
+ /**
+ * Constructor for creating blocks from a set of values.
+ *
+ * @phpstan-param iterable $values
+ * @phpstan-param callable(Serialized_Blocks[] $carry, mixed $item, mixed $index, iterable $values): Serialized_Blocks[] $reduce
+ *
+ * @param iterable $values Values.
+ * @param callable $reduce Reducer callback that produces block instances.
+ * @return Serialized_Blocks
+ */
+ public static function from_iterable( iterable $values, callable $reduce ): Serialized_Blocks {
+ return new Lazy_Blocks(
+ function () use ( $values, $reduce ) {
+ $carry = [];
+
+ foreach ( $values as $index => $item ) {
+ $carry = ( $reduce )( $carry, $item, $index, $values );
+ }
+
+ return new Blocks( ...$carry );
+ }
+ );
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-default-classname-block.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-default-classname-block.php
new file mode 100644
index 00000000..e8c2f2a9
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-default-classname-block.php
@@ -0,0 +1,65 @@
+origin->block_name();
+ }
+
+ /**
+ * Parsed block.
+ *
+ * @return mixed[]
+ */
+ public function parsed_block(): array {
+ $out = $this->origin->parsed_block();
+
+ $before = sprintf(
+ '<%s class="%s">',
+ tag_escape( $this->element ),
+ wp_get_block_default_classname( (string) $this->block_name() ),
+ );
+ $after = sprintf( '%s>', tag_escape( $this->element ) );
+
+ $out['innerHTML'] = $before . $out['innerHTML'] . $after;
+ $out['innerContent'] = [ $before, ...$out['innerContent'], $after ];
+
+ return $out;
+ }
+
+ /**
+ * Serialized block content.
+ *
+ * @return string
+ */
+ public function serialized_blocks(): string {
+ return serialize_block( $this->parsed_block() );
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-each-replaced-blocks.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-each-replaced-blocks.php
new file mode 100644
index 00000000..f9b66ff9
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-each-replaced-blocks.php
@@ -0,0 +1,50 @@
+origin->serialized_blocks();
+
+ $needles = parse_blocks( $this->find->serialized_blocks() );
+
+ if ( \is_array( $needles ) && count( $needles ) > 0 ) {
+ foreach ( $needles as $needle ) {
+ if ( \is_array( $needle ) ) {
+ $pbn = new Parsed_Block( $needle );
+ $out = str_replace( $pbn->serialized_blocks(), $this->replace->serialized_blocks(), $out );
+ }
+ }
+ }
+
+ return $out;
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-inner-blocks-prepended.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-inner-blocks-prepended.php
new file mode 100644
index 00000000..183a1918
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-inner-blocks-prepended.php
@@ -0,0 +1,71 @@
+target->block_name();
+ }
+
+ /**
+ * Parsed block.
+ *
+ * @return mixed[]
+ */
+ public function parsed_block(): array {
+ $out = $this->target->parsed_block();
+ $add = parse_blocks( $this->block->serialized_blocks() );
+
+ if (
+ \is_array( $add )
+ && isset( $out['innerBlocks'], $out['innerContent'] )
+ && \is_array( $out['innerBlocks'] )
+ && \is_array( $out['innerContent'] )
+ ) {
+ $out['innerBlocks'] = array_merge( $add, $out['innerBlocks'] );
+ $out['innerContent'] = array_merge(
+ array_fill( 0, \count( $add ), null ),
+ $out['innerContent'],
+ );
+ }
+
+ return $out;
+ }
+
+ /**
+ * Serialized block content.
+ *
+ * @return string
+ */
+ public function serialized_blocks(): string {
+ $pb = new Parsed_Block( $this->parsed_block() );
+ return $pb->serialized_blocks();
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-lazy-blocks.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-lazy-blocks.php
new file mode 100644
index 00000000..f4329b4e
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-lazy-blocks.php
@@ -0,0 +1,33 @@
+final )()->serialized_blocks();
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-matched-blocks.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-matched-blocks.php
new file mode 100644
index 00000000..7a8784da
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-matched-blocks.php
@@ -0,0 +1,39 @@
+ $args Args for {@see match_blocks()}.
+ * @param Serialized_Blocks $origin Blocks to search.
+ */
+ public function __construct(
+ private readonly array $args,
+ private readonly Serialized_Blocks $origin,
+ ) {}
+
+ /**
+ * Serialized block content.
+ *
+ * @return string
+ */
+ public function serialized_blocks(): string {
+ $matched = match_blocks( $this->origin->serialized_blocks(), $this->args );
+
+ return \is_array( $matched ) ? serialize_blocks( $matched ) : '';
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-named-block.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-named-block.php
new file mode 100644
index 00000000..6b1c2e2c
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-named-block.php
@@ -0,0 +1,61 @@
+ $attrs Block attributes.
+ * @param string $inner_html Block inner HTML.
+ */
+ public function __construct(
+ private readonly string $block_name,
+ private readonly array $attrs = [],
+ private readonly string $inner_html = '',
+ ) {}
+
+ /**
+ * Block name.
+ *
+ * @return string
+ */
+ public function block_name(): string {
+ return $this->block_name;
+ }
+
+ /**
+ * Parsed block.
+ *
+ * @return mixed[]
+ */
+ public function parsed_block(): array {
+ return [
+ 'blockName' => $this->block_name,
+ 'attrs' => $this->attrs,
+ 'innerBlocks' => [],
+ 'innerHTML' => $this->inner_html,
+ 'innerContent' => [ $this->inner_html ],
+ ];
+ }
+
+ /**
+ * Serialized block content.
+ *
+ * @return string
+ */
+ public function serialized_blocks(): string {
+ return serialize_block( $this->parsed_block() );
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-parsed-block.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-parsed-block.php
new file mode 100644
index 00000000..9acb4679
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/blocks/class-parsed-block.php
@@ -0,0 +1,63 @@
+origin['blockName'] ) && \is_string( $this->origin['blockName'] ) ? $this->origin['blockName'] : null;
+ }
+
+ /**
+ * Parsed block.
+ *
+ * @return mixed[]
+ */
+ public function parsed_block(): array {
+ $attrs = isset( $this->origin['attrs'] ) && \is_array( $this->origin['attrs'] ) ? $this->origin['attrs'] : [];
+ $inner_blocks = isset( $this->origin['innerBlocks'] ) && \is_array( $this->origin['innerBlocks'] ) ? $this->origin['innerBlocks'] : [];
+ $inner_html = isset( $this->origin['innerHTML'] ) && \is_string( $this->origin['innerHTML'] ) ? $this->origin['innerHTML'] : '';
+ $inner_content = isset( $this->origin['innerContent'] ) && \is_array( $this->origin['innerContent'] ) ? $this->origin['innerContent'] : [];
+
+ return (array) new WP_Block_Parser_Block(
+ $this->block_name(), // @phpstan-ignore-line
+ $attrs,
+ $inner_blocks,
+ $inner_html,
+ $inner_content,
+ );
+ }
+
+ /**
+ * Serialized block content.
+ *
+ * @return string
+ */
+ public function serialized_blocks(): string {
+ return serialize_block( $this->parsed_block() );
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/class-legal-object-ids.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/class-legal-object-ids.php
new file mode 100644
index 00000000..9eddb28a
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/class-legal-object-ids.php
@@ -0,0 +1,33 @@
+origin->post_ids(), fn ( $id ) => $id > 0 );
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-conditional-feature.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-conditional-feature.php
new file mode 100644
index 00000000..3e9d82d4
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-conditional-feature.php
@@ -0,0 +1,40 @@
+value ) ? ( $this->value )() : $this->value;
+
+ if ( $this->test->isValid( $value ) ) {
+ $this->if_true->boot();
+ }
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-group.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-group.php
new file mode 100644
index 00000000..81959f26
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-group.php
@@ -0,0 +1,40 @@
+features = $features;
+ }
+
+ /**
+ * Boot the feature.
+ */
+ public function boot(): void {
+ foreach ( $this->features as $feature ) {
+ $feature->boot();
+ }
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-gtm-script.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-gtm-script.php
new file mode 100644
index 00000000..b996700d
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-gtm-script.php
@@ -0,0 +1,81 @@
+ $data_layer
+ *
+ * @param string $tag_id GTM tag ID.
+ * @param array|stdClass|JsonSerializable $data_layer Initial data layer data.
+ */
+ public function __construct(
+ private readonly string $tag_id,
+ private readonly array|stdClass|JsonSerializable $data_layer,
+ ) {}
+
+ /**
+ * Boot the feature.
+ */
+ public function boot(): void {
+ add_action( 'wp_head', [ $this, 'render_head' ] );
+ add_action( 'wp_body_open', [ $this, 'render_body' ] );
+ }
+
+ /**
+ * Render the GTM tag in the document body.
+ */
+ public function render_head(): void {
+ $data = $this->data_layer instanceof JsonSerializable ? $this->data_layer : (object) $this->data_layer;
+ $flags = WP_DEBUG ? JSON_PRETTY_PRINT : 0;
+
+ printf(
+ <<<'HTML'
+
+
+
+
+
+HTML,
+ wp_json_encode( $data, $flags ),
+ wp_json_encode( $this->tag_id, $flags ),
+ );
+ }
+
+ /**
+ * Render the GTM tag in the document body.
+ */
+ public function render_body(): void {
+ printf(
+ <<<'HTML'
+
+
+
+HTML,
+ esc_url( "https://www.googletagmanager.com/ns.html?id={$this->tag_id}" ),
+ );
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-lazy-feature.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-lazy-feature.php
new file mode 100644
index 00000000..5d06aeec
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-lazy-feature.php
@@ -0,0 +1,31 @@
+final )()->boot();
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-quick-feature.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-quick-feature.php
new file mode 100644
index 00000000..8aead9c7
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-quick-feature.php
@@ -0,0 +1,31 @@
+fn )();
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-template-feature.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-template-feature.php
new file mode 100644
index 00000000..93489df7
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-template-feature.php
@@ -0,0 +1,31 @@
+origin, 'boot' ] );
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-wp-cli-feature.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-wp-cli-feature.php
new file mode 100644
index 00000000..127c5013
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/features/class-wp-cli-feature.php
@@ -0,0 +1,42 @@
+origin, 'boot' ] );
+ } elseif ( class_exists( 'WP_CLI' ) ) {
+ /*
+ * This is being invoked in a WP-CLI package or in a similar context where
+ * WordPress hasn't yet been loaded.
+ *
+ * @see https://github.com/buddypress/wp-cli-buddypress/issues/18
+ */
+ WP_CLI::add_hook( 'before_wp_load', [ $this->origin, 'boot' ] );
+ }
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-ids/class-empty-post-ids.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-ids/class-empty-post-ids.php
new file mode 100644
index 00000000..62e4b162
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-ids/class-empty-post-ids.php
@@ -0,0 +1,24 @@
+post_ids );
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-ids/class-post-ids-once.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-ids/class-post-ids-once.php
new file mode 100644
index 00000000..6a807e67
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-ids/class-post-ids-once.php
@@ -0,0 +1,33 @@
+origin, 'post_ids' ] );
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-ids/class-used-post-ids.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-ids/class-used-post-ids.php
new file mode 100644
index 00000000..9f92187c
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-ids/class-used-post-ids.php
@@ -0,0 +1,57 @@
+
+ */
+ private array $ids = [];
+
+ /**
+ * Set up.
+ *
+ * @param Post_IDs $seed Initial post IDs.
+ */
+ public function __construct(
+ private readonly Post_IDs $seed = new Empty_Post_IDs()
+ ) {}
+
+ /**
+ * Post IDs.
+ *
+ * @return int[]
+ */
+ public function post_ids(): array {
+ return array_merge( array_keys( $this->ids ), $this->seed->post_ids() );
+ }
+
+ /**
+ * Record used post IDs.
+ *
+ * @param int|int[] $post_ids Post ID or IDs.
+ */
+ public function record( int|array $post_ids ): void {
+ if ( \is_int( $post_ids ) ) {
+ $post_ids = [ $post_ids ];
+ }
+
+ foreach ( $post_ids as $post_id ) {
+ if ( \is_int( $post_id ) ) {
+ $this->ids[ $post_id ] = true;
+ }
+ }
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-ids/class-wp-query-post-ids.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-ids/class-wp-query-post-ids.php
new file mode 100644
index 00000000..e5d2f4a0
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-ids/class-wp-query-post-ids.php
@@ -0,0 +1,67 @@
+query->posts ) ) {
+ $ids = array_map( [ self::class, 'to_post_id' ], $this->query->posts );
+ }
+
+ return $ids;
+ }
+
+ /**
+ * Get the ID from a post object, ID, or ID-parent object.
+ *
+ * @param WP_Post|int|object $value Post-like object or ID.
+ * @return int Post ID.
+ */
+ private static function to_post_id( $value ): int {
+ $id = 0;
+
+ if ( $value instanceof WP_Post ) {
+ $id = $value->ID;
+ }
+
+ // fields => 'ids'.
+ if ( is_numeric( $value ) ) {
+ $id = $value;
+ }
+
+ // fields => 'id=>parent'.
+ if ( ( $value instanceof \stdClass ) && isset( $value->ID ) ) {
+ $id = $value->ID;
+ }
+
+ return (int) $id;
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-queries/class-default-post-queries.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-queries/class-default-post-queries.php
new file mode 100644
index 00000000..ee99de3e
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-queries/class-default-post-queries.php
@@ -0,0 +1,28 @@
+ $args The arguments to be used in the query.
+ * @return Post_Query
+ */
+ public function query( array $args ): Post_Query {
+ return new WP_Query_Envelope( new WP_Query( $args ) );
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-queries/class-enforced-date-queries.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-queries/class-enforced-date-queries.php
new file mode 100644
index 00000000..3f4096d0
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-queries/class-enforced-date-queries.php
@@ -0,0 +1,64 @@
+ $args The arguments to be used in the query.
+ * @return Post_Query
+ */
+ public function query( array $args ): Post_Query {
+ $with_date_query = $this->with_date_query( $args, $this->after );
+
+ return $this->origin->query( $with_date_query );
+ }
+
+ /**
+ * Add 'after' date query with the given date.
+ *
+ * @param array $args Query arguments.
+ * @param DateTimeInterface $after Date instance.
+ * @return array
+ */
+ private function with_date_query( array $args, DateTimeInterface $after ): array {
+ if ( ! isset( $args['date_query'] ) || ! \is_array( $args['date_query'] ) ) {
+ $args['date_query'] = [];
+ }
+
+ $args['date_query']['relation'] = 'AND';
+ $args['date_query'][] = [
+ 'after' => [
+ 'year' => $after->format( 'Y' ),
+ 'month' => $after->format( 'n' ),
+ 'day' => $after->format( 'j' ),
+ ],
+ ];
+
+ return $args;
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-queries/class-exclude-queries.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-queries/class-exclude-queries.php
new file mode 100644
index 00000000..f37545ec
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-queries/class-exclude-queries.php
@@ -0,0 +1,58 @@
+ $args The arguments to be used in the query.
+ * @return Post_Query
+ */
+ public function query( array $args ): Post_Query {
+ $excluded_post_ids = $this->exclude->post_ids();
+ $expected_per_page = $this->default_per_page;
+
+ if ( isset( $args['posts_per_page'] ) && is_numeric( $args['posts_per_page'] ) ) {
+ $expected_per_page = (int) $args['posts_per_page'];
+ }
+
+ // Ask for the number of posts we expect to return, plus the number of posts to exclude.
+ $args['posts_per_page'] = $expected_per_page + \count( $excluded_post_ids );
+ $overfetched_query = $this->origin->query( $args );
+
+ // Remove the excluded from the overfetched query.
+ $diff_post_ids = array_diff( $overfetched_query->post_ids(), $excluded_post_ids );
+
+ // Slice the number of posts we expect to return from the overfetched query.
+ $per_page_post_ids = \array_slice( $diff_post_ids, 0, $expected_per_page );
+
+ return new Post_IDs_Query( $per_page_post_ids );
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-queries/class-memoized-post-queries.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-queries/class-memoized-post-queries.php
new file mode 100644
index 00000000..f36d9301
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-queries/class-memoized-post-queries.php
@@ -0,0 +1,35 @@
+ $args The arguments to be used in the query.
+ * @return Post_Query
+ */
+ public function query( array $args ): Post_Query {
+ return once( fn () => $this->origin->query( $args ) );
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-queries/class-optimistic-date-queries.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-queries/class-optimistic-date-queries.php
new file mode 100644
index 00000000..bfc9b719
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-queries/class-optimistic-date-queries.php
@@ -0,0 +1,58 @@
+after, fn ( $a, $b ) => $b <=> $a );
+ }
+
+ /**
+ * Query for posts using literal arguments.
+ *
+ * @param array $args The arguments to be used in the query.
+ * @return Post_Query
+ */
+ public function query( array $args ): Post_Query {
+ $expected_count = $this->posts_per_page;
+
+ if ( isset( $args['posts_per_page'] ) && is_numeric( $args['posts_per_page'] ) ) {
+ $expected_count = (int) $args['posts_per_page'];
+ }
+
+ foreach ( $this->after as $after ) {
+ $with_date_query = new Enforced_Date_Queries( $after, $this->origin );
+ $result = $with_date_query->query( $args );
+
+ if ( \count( $result->post_ids() ) === $expected_count ) {
+ return $result;
+ }
+ }
+
+ return $this->origin->query( $args );
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-queries/class-variable-post-queries.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-queries/class-variable-post-queries.php
new file mode 100644
index 00000000..827ff7f5
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-queries/class-variable-post-queries.php
@@ -0,0 +1,51 @@
+ $args The arguments to be used in the query.
+ * @return Post_Query
+ */
+ public function query( array $args ): Post_Query {
+ return $this->final()->query( $args );
+ }
+
+ /**
+ * Post_Queries instance to use.
+ *
+ * @return Post_Queries
+ */
+ private function final(): Post_Queries {
+ return $this->test->isValid( ( $this->input )() ) ? $this->is_true : $this->is_false;
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-query/class-global-post-query.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-query/class-global-post-query.php
new file mode 100644
index 00000000..335139a0
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-query/class-global-post-query.php
@@ -0,0 +1,61 @@
+global ] ) && $GLOBALS[ $this->global ] instanceof WP_Query
+ ? $GLOBALS[ $this->global ]
+ : $this->default;
+ }
+
+ /**
+ * Found post objects.
+ *
+ * @return WP_Post[]
+ */
+ public function post_objects(): array {
+ $query = new WP_Query_Envelope( $this->query_object() );
+
+ return $query->post_objects();
+ }
+
+ /**
+ * Found post IDs.
+ *
+ * @return int[]
+ */
+ public function post_ids(): array {
+ $query = new WP_Query_Envelope( $this->query_object() );
+
+ return $query->post_ids();
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-query/class-post-ids-query.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-query/class-post-ids-query.php
new file mode 100644
index 00000000..9e28dbc7
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-query/class-post-ids-query.php
@@ -0,0 +1,70 @@
+posts = $this->post_ids();
+
+ // Fill in other properties to make it look more like an executed query.
+ $count = \count( $query->posts );
+ $query->found_posts = $count;
+ $query->post_count = $count;
+ $query->max_num_pages = 1;
+ $query->set( 'fields', 'ids' );
+
+ return $query;
+ }
+
+ /**
+ * Found post objects.
+ *
+ * @return WP_Post[]
+ */
+ public function post_objects(): array {
+ $post_ids = $this->post_ids();
+
+ _prime_post_caches( $post_ids, false, false );
+
+ $posts = array_map( 'get_post', $post_ids );
+ $posts = array_filter( $posts, fn ( $p ) => $p instanceof WP_Post );
+
+ return $posts;
+ }
+
+ /**
+ * Found post IDs.
+ *
+ * @return int[]
+ */
+ public function post_ids(): array {
+ return array_map( 'intval', $this->post_ids );
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-query/class-wp-query-envelope.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-query/class-wp-query-envelope.php
new file mode 100644
index 00000000..8c043e61
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/post-query/class-wp-query-envelope.php
@@ -0,0 +1,59 @@
+query;
+ }
+
+ /**
+ * Found post objects.
+ *
+ * @return WP_Post[]
+ */
+ public function post_objects(): array {
+ $posts = array_map( 'get_post', $this->post_ids() );
+ $posts = array_filter( $posts, fn ( $p ) => $p instanceof WP_Post );
+
+ return $posts;
+ }
+
+ /**
+ * Found post IDs.
+ *
+ * @return int[]
+ */
+ public function post_ids(): array {
+ $ids = new WP_Query_Post_IDs( $this->query_object() );
+
+ return $ids->post_ids();
+ }
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/types/interface-feature.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/types/interface-feature.php
new file mode 100644
index 00000000..10f76cf1
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/types/interface-feature.php
@@ -0,0 +1,18 @@
+ $args Query arguments.
+ * @return Post_Query
+ */
+ public function query( array $args ): Post_Query;
+}
diff --git a/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/types/interface-post-query.php b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/types/interface-post-query.php
new file mode 100644
index 00000000..6e5c8138
--- /dev/null
+++ b/vendor/alleyinteractive/wp-type-extensions/src/alley/wp/types/interface-post-query.php
@@ -0,0 +1,30 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ * @see https://www.php-fig.org/psr/psr-0/
+ * @see https://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ /** @var \Closure(string):void */
+ private static $includeFile;
+
+ /** @var string|null */
+ private $vendorDir;
+
+ // PSR-4
+ /**
+ * @var array>
+ */
+ private $prefixLengthsPsr4 = array();
+ /**
+ * @var array>
+ */
+ private $prefixDirsPsr4 = array();
+ /**
+ * @var list
+ */
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ /**
+ * List of PSR-0 prefixes
+ *
+ * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
+ *
+ * @var array>>
+ */
+ private $prefixesPsr0 = array();
+ /**
+ * @var list
+ */
+ private $fallbackDirsPsr0 = array();
+
+ /** @var bool */
+ private $useIncludePath = false;
+
+ /**
+ * @var array
+ */
+ private $classMap = array();
+
+ /** @var bool */
+ private $classMapAuthoritative = false;
+
+ /**
+ * @var array
+ */
+ private $missingClasses = array();
+
+ /** @var string|null */
+ private $apcuPrefix;
+
+ /**
+ * @var array
+ */
+ private static $registeredLoaders = array();
+
+ /**
+ * @param string|null $vendorDir
+ */
+ public function __construct($vendorDir = null)
+ {
+ $this->vendorDir = $vendorDir;
+ self::initializeIncludeClosure();
+ }
+
+ /**
+ * @return array>
+ */
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+ }
+
+ return array();
+ }
+
+ /**
+ * @return array>
+ */
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ /**
+ * @return list
+ */
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ /**
+ * @return list
+ */
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ /**
+ * @return array Array of classname => path
+ */
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ *
+ * @return void
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param list|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @return void
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ $paths = (array) $paths;
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param list|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ $paths = (array) $paths;
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param list|string $paths The PSR-0 base directories
+ *
+ * @return void
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param list|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ *
+ * @return void
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ *
+ * @return void
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ *
+ * @return void
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ *
+ * @return void
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+ if (null === $this->vendorDir) {
+ return;
+ }
+
+ if ($prepend) {
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+ } else {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ self::$registeredLoaders[$this->vendorDir] = $this;
+ }
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ *
+ * @return void
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+
+ if (null !== $this->vendorDir) {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ }
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return true|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ $includeFile = self::$includeFile;
+ $includeFile($file);
+
+ return true;
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ /**
+ * Returns the currently registered loaders keyed by their corresponding vendor directories.
+ *
+ * @return array
+ */
+ public static function getRegisteredLoaders()
+ {
+ return self::$registeredLoaders;
+ }
+
+ /**
+ * @param string $class
+ * @param string $ext
+ * @return string|false
+ */
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath . '\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+
+ /**
+ * @return void
+ */
+ private static function initializeIncludeClosure()
+ {
+ if (self::$includeFile !== null) {
+ return;
+ }
+
+ /**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ *
+ * @param string $file
+ * @return void
+ */
+ self::$includeFile = \Closure::bind(static function($file) {
+ include $file;
+ }, null, null);
+ }
+}
diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php
new file mode 100644
index 00000000..51e734a7
--- /dev/null
+++ b/vendor/composer/InstalledVersions.php
@@ -0,0 +1,359 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ *
+ * @final
+ */
+class InstalledVersions
+{
+ /**
+ * @var mixed[]|null
+ * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null
+ */
+ private static $installed;
+
+ /**
+ * @var bool|null
+ */
+ private static $canGetVendors;
+
+ /**
+ * @var array[]
+ * @psalm-var array}>
+ */
+ private static $installedByVendor = array();
+
+ /**
+ * Returns a list of all package names which are present, either by being installed, replaced or provided
+ *
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackages()
+ {
+ $packages = array();
+ foreach (self::getInstalled() as $installed) {
+ $packages[] = array_keys($installed['versions']);
+ }
+
+ if (1 === \count($packages)) {
+ return $packages[0];
+ }
+
+ return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+ }
+
+ /**
+ * Returns a list of all package names with a specific type e.g. 'library'
+ *
+ * @param string $type
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackagesByType($type)
+ {
+ $packagesByType = array();
+
+ foreach (self::getInstalled() as $installed) {
+ foreach ($installed['versions'] as $name => $package) {
+ if (isset($package['type']) && $package['type'] === $type) {
+ $packagesByType[] = $name;
+ }
+ }
+ }
+
+ return $packagesByType;
+ }
+
+ /**
+ * Checks whether the given package is installed
+ *
+ * This also returns true if the package name is provided or replaced by another package
+ *
+ * @param string $packageName
+ * @param bool $includeDevRequirements
+ * @return bool
+ */
+ public static function isInstalled($packageName, $includeDevRequirements = true)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (isset($installed['versions'][$packageName])) {
+ return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether the given package satisfies a version constraint
+ *
+ * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+ *
+ * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+ *
+ * @param VersionParser $parser Install composer/semver to have access to this class and functionality
+ * @param string $packageName
+ * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+ * @return bool
+ */
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
+ {
+ $constraint = $parser->parseConstraints((string) $constraint);
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+ return $provided->matches($constraint);
+ }
+
+ /**
+ * Returns a version constraint representing all the range(s) which are installed for a given package
+ *
+ * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+ * whether a given version of a package is installed, and not just whether it exists
+ *
+ * @param string $packageName
+ * @return string Version constraint usable with composer/semver
+ */
+ public static function getVersionRanges($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ $ranges = array();
+ if (isset($installed['versions'][$packageName]['pretty_version'])) {
+ $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+ }
+ if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+ }
+ if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+ }
+ if (array_key_exists('provided', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+ }
+
+ return implode(' || ', $ranges);
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getPrettyVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['pretty_version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+ */
+ public static function getReference($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['reference'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['reference'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+ */
+ public static function getInstallPath($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @return array
+ * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
+ */
+ public static function getRootPackage()
+ {
+ $installed = self::getInstalled();
+
+ return $installed[0]['root'];
+ }
+
+ /**
+ * Returns the raw installed.php data for custom implementations
+ *
+ * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+ * @return array[]
+ * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}
+ */
+ public static function getRawData()
+ {
+ @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ self::$installed = include __DIR__ . '/installed.php';
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ return self::$installed;
+ }
+
+ /**
+ * Returns the raw data of all installed.php which are currently loaded for custom implementations
+ *
+ * @return array[]
+ * @psalm-return list}>
+ */
+ public static function getAllRawData()
+ {
+ return self::getInstalled();
+ }
+
+ /**
+ * Lets you reload the static array from another file
+ *
+ * This is only useful for complex integrations in which a project needs to use
+ * this class but then also needs to execute another project's autoloader in process,
+ * and wants to ensure both projects have access to their version of installed.php.
+ *
+ * A typical case would be PHPUnit, where it would need to make sure it reads all
+ * the data it needs from this class, then call reload() with
+ * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+ * the project in which it runs can then also use this class safely, without
+ * interference between PHPUnit's dependencies and the project's dependencies.
+ *
+ * @param array[] $data A vendor/composer/installed.php data set
+ * @return void
+ *
+ * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data
+ */
+ public static function reload($data)
+ {
+ self::$installed = $data;
+ self::$installedByVendor = array();
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return list}>
+ */
+ private static function getInstalled()
+ {
+ if (null === self::$canGetVendors) {
+ self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+ }
+
+ $installed = array();
+
+ if (self::$canGetVendors) {
+ foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+ if (isset(self::$installedByVendor[$vendorDir])) {
+ $installed[] = self::$installedByVendor[$vendorDir];
+ } elseif (is_file($vendorDir.'/composer/installed.php')) {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */
+ $required = require $vendorDir.'/composer/installed.php';
+ $installed[] = self::$installedByVendor[$vendorDir] = $required;
+ if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
+ self::$installed = $installed[count($installed) - 1];
+ }
+ }
+ }
+ }
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */
+ $required = require __DIR__ . '/installed.php';
+ self::$installed = $required;
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ if (self::$installed !== array()) {
+ $installed[] = self::$installed;
+ }
+
+ return $installed;
+ }
+}
diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE
new file mode 100644
index 00000000..f27399a0
--- /dev/null
+++ b/vendor/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
new file mode 100644
index 00000000..0fb0a2c1
--- /dev/null
+++ b/vendor/composer/autoload_classmap.php
@@ -0,0 +1,10 @@
+ $vendorDir . '/composer/InstalledVersions.php',
+);
diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php
new file mode 100644
index 00000000..a71e65fe
--- /dev/null
+++ b/vendor/composer/autoload_files.php
@@ -0,0 +1,17 @@
+ $vendorDir . '/laminas/laminas-servicemanager/src/autoload.php',
+ '22177d82d05723dff5b1903f4496520e' => $vendorDir . '/alleyinteractive/wordpress-autoloader/src/class-autoloader.php',
+ 'd0b4d9ff2237dcc1a532ae9d039c0c2c' => $vendorDir . '/alleyinteractive/composer-wordpress-autoloader/src/autoload.php',
+ 'b4c1393590946316912f1825c4d559f0' => $vendorDir . '/alleyinteractive/wp-match-blocks/src/alley/wp/match-blocks.php',
+ '34b197430e01f74411146b5dd772055d' => $vendorDir . '/alleyinteractive/wp-match-blocks/src/alley/wp/internals/internals.php',
+ 'ed33d19cba977f2a7e321f120d94a872' => $vendorDir . '/spatie/once/src/functions.php',
+ '18ea4761fe239e693375d30a01936633' => $vendorDir . '/alleyinteractive/traverse-reshape/src/Alley/reshape.php',
+ '3e170268241fa56c275e43aec546ca42' => $vendorDir . '/alleyinteractive/traverse-reshape/src/Alley/traverse.php',
+);
diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php
new file mode 100644
index 00000000..15a2ff3a
--- /dev/null
+++ b/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,9 @@
+ array($vendorDir . '/spatie/once/src'),
+ 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
+ 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
+ 'Laminas\\Validator\\' => array($vendorDir . '/laminas/laminas-validator/src'),
+ 'Laminas\\Stdlib\\' => array($vendorDir . '/laminas/laminas-stdlib/src'),
+ 'Laminas\\ServiceManager\\' => array($vendorDir . '/laminas/laminas-servicemanager/src'),
+ 'ComposerWordPressAutoloader\\' => array($vendorDir . '/alleyinteractive/composer-wordpress-autoloader/src'),
+ 'Alley\\' => array($vendorDir . '/alleyinteractive/traverse-reshape/src/Alley', $vendorDir . '/alleyinteractive/laminas-validator-extensions/src/Alley'),
+);
diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php
new file mode 100644
index 00000000..fc21626f
--- /dev/null
+++ b/vendor/composer/autoload_real.php
@@ -0,0 +1,50 @@
+register(true);
+
+ $filesToLoad = \Composer\Autoload\ComposerStaticInit1c9954415aecee54097cf0f5c17da765::$files;
+ $requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
+ if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+ $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+
+ require $file;
+ }
+ }, null, null);
+ foreach ($filesToLoad as $fileIdentifier => $file) {
+ $requireFile($fileIdentifier, $file);
+ }
+
+ return $loader;
+ }
+}
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
new file mode 100644
index 00000000..59dbdbab
--- /dev/null
+++ b/vendor/composer/autoload_static.php
@@ -0,0 +1,95 @@
+ __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/autoload.php',
+ '22177d82d05723dff5b1903f4496520e' => __DIR__ . '/..' . '/alleyinteractive/wordpress-autoloader/src/class-autoloader.php',
+ 'd0b4d9ff2237dcc1a532ae9d039c0c2c' => __DIR__ . '/..' . '/alleyinteractive/composer-wordpress-autoloader/src/autoload.php',
+ 'b4c1393590946316912f1825c4d559f0' => __DIR__ . '/..' . '/alleyinteractive/wp-match-blocks/src/alley/wp/match-blocks.php',
+ '34b197430e01f74411146b5dd772055d' => __DIR__ . '/..' . '/alleyinteractive/wp-match-blocks/src/alley/wp/internals/internals.php',
+ 'ed33d19cba977f2a7e321f120d94a872' => __DIR__ . '/..' . '/spatie/once/src/functions.php',
+ '18ea4761fe239e693375d30a01936633' => __DIR__ . '/..' . '/alleyinteractive/traverse-reshape/src/Alley/reshape.php',
+ '3e170268241fa56c275e43aec546ca42' => __DIR__ . '/..' . '/alleyinteractive/traverse-reshape/src/Alley/traverse.php',
+ );
+
+ public static $prefixLengthsPsr4 = array (
+ 'S' =>
+ array (
+ 'Spatie\\Once\\' => 12,
+ ),
+ 'P' =>
+ array (
+ 'Psr\\Http\\Message\\' => 17,
+ 'Psr\\Container\\' => 14,
+ ),
+ 'L' =>
+ array (
+ 'Laminas\\Validator\\' => 18,
+ 'Laminas\\Stdlib\\' => 15,
+ 'Laminas\\ServiceManager\\' => 23,
+ ),
+ 'C' =>
+ array (
+ 'ComposerWordPressAutoloader\\' => 28,
+ ),
+ 'A' =>
+ array (
+ 'Alley\\' => 6,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'Spatie\\Once\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/spatie/once/src',
+ ),
+ 'Psr\\Http\\Message\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/psr/http-message/src',
+ ),
+ 'Psr\\Container\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/psr/container/src',
+ ),
+ 'Laminas\\Validator\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/laminas/laminas-validator/src',
+ ),
+ 'Laminas\\Stdlib\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/laminas/laminas-stdlib/src',
+ ),
+ 'Laminas\\ServiceManager\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src',
+ ),
+ 'ComposerWordPressAutoloader\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/alleyinteractive/composer-wordpress-autoloader/src',
+ ),
+ 'Alley\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/alleyinteractive/traverse-reshape/src/Alley',
+ 1 => __DIR__ . '/..' . '/alleyinteractive/laminas-validator-extensions/src/Alley',
+ ),
+ );
+
+ public static $classMap = array (
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInit1c9954415aecee54097cf0f5c17da765::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInit1c9954415aecee54097cf0f5c17da765::$prefixDirsPsr4;
+ $loader->classMap = ComposerStaticInit1c9954415aecee54097cf0f5c17da765::$classMap;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
new file mode 100644
index 00000000..86af26ca
--- /dev/null
+++ b/vendor/composer/installed.json
@@ -0,0 +1,767 @@
+{
+ "packages": [
+ {
+ "name": "alleyinteractive/composer-wordpress-autoloader",
+ "version": "v1.1.0",
+ "version_normalized": "1.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/alleyinteractive/composer-wordpress-autoloader.git",
+ "reference": "30acf9e3498a84af478e316ae3869fa362835695"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/alleyinteractive/composer-wordpress-autoloader/zipball/30acf9e3498a84af478e316ae3869fa362835695",
+ "reference": "30acf9e3498a84af478e316ae3869fa362835695",
+ "shasum": ""
+ },
+ "require": {
+ "alleyinteractive/wordpress-autoloader": "^1.1.1",
+ "composer-plugin-api": "^2.0",
+ "php": "^8.0"
+ },
+ "require-dev": {
+ "composer/composer": "*",
+ "phpunit/phpunit": "^9.5.8",
+ "squizlabs/php_codesniffer": "^4.0"
+ },
+ "time": "2023-12-06T20:21:22+00:00",
+ "type": "composer-plugin",
+ "extra": {
+ "class": "ComposerWordPressAutoloader\\Plugin"
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "src/autoload.php"
+ ],
+ "psr-4": {
+ "ComposerWordPressAutoloader\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Alley Interactive",
+ "email": "info@alley.co"
+ },
+ {
+ "name": "Sean Fisher",
+ "email": "sean@alley.co"
+ }
+ ],
+ "description": "Autoload files using WordPress File Conventions using Composer",
+ "support": {
+ "issues": "https://github.com/alleyinteractive/composer-wordpress-autoloader/issues",
+ "source": "https://github.com/alleyinteractive/composer-wordpress-autoloader/tree/v1.1.0"
+ },
+ "install-path": "../alleyinteractive/composer-wordpress-autoloader"
+ },
+ {
+ "name": "alleyinteractive/laminas-validator-extensions",
+ "version": "v2.1.1",
+ "version_normalized": "2.1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/alleyinteractive/laminas-validator-extensions.git",
+ "reference": "09fd68d1fda5179d5e972cf7053edf4eab903313"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/alleyinteractive/laminas-validator-extensions/zipball/09fd68d1fda5179d5e972cf7053edf4eab903313",
+ "reference": "09fd68d1fda5179d5e972cf7053edf4eab903313",
+ "shasum": ""
+ },
+ "require": {
+ "laminas/laminas-validator": "^2.20",
+ "php": "^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.8",
+ "phpunit/phpunit": "^9.5",
+ "squizlabs/php_codesniffer": "^3.6"
+ },
+ "time": "2024-02-14T17:54:59+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Alley\\": "src/Alley/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Alley",
+ "email": "info@alley.com"
+ }
+ ],
+ "description": "Additional validation classes for the laminas-validator framework.",
+ "support": {
+ "issues": "https://github.com/alleyinteractive/laminas-validator-extensions/issues",
+ "source": "https://github.com/alleyinteractive/laminas-validator-extensions/tree/v2.1.1"
+ },
+ "install-path": "../alleyinteractive/laminas-validator-extensions"
+ },
+ {
+ "name": "alleyinteractive/traverse-reshape",
+ "version": "v2.0.0",
+ "version_normalized": "2.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/alleyinteractive/traverse-reshape.git",
+ "reference": "2b5bfd0723c0b9312ce6f7673f0e861fbe2d1567"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/alleyinteractive/traverse-reshape/zipball/2b5bfd0723c0b9312ce6f7673f0e861fbe2d1567",
+ "reference": "2b5bfd0723c0b9312ce6f7673f0e861fbe2d1567",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.8",
+ "phpunit/phpunit": "^9.5",
+ "squizlabs/php_codesniffer": "^3.6"
+ },
+ "time": "2022-12-29T19:25:14+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "src/Alley/reshape.php",
+ "src/Alley/traverse.php"
+ ],
+ "psr-4": {
+ "Alley\\": "src/Alley/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Alley",
+ "email": "info@alley.com"
+ }
+ ],
+ "description": "Safely break down arrays or objects, and put them back together in new shapes.",
+ "support": {
+ "issues": "https://github.com/alleyinteractive/traverse-reshape/issues",
+ "source": "https://github.com/alleyinteractive/traverse-reshape/tree/v2.0.0"
+ },
+ "install-path": "../alleyinteractive/traverse-reshape"
+ },
+ {
+ "name": "alleyinteractive/wordpress-autoloader",
+ "version": "v1.1.1",
+ "version_normalized": "1.1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/alleyinteractive/wordpress-autoloader.git",
+ "reference": "c7599d95f49f1cdc38fad19944a50b19ec0dd6ca"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/alleyinteractive/wordpress-autoloader/zipball/c7599d95f49f1cdc38fad19944a50b19ec0dd6ca",
+ "reference": "c7599d95f49f1cdc38fad19944a50b19ec0dd6ca",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4.0|^8.0|^8.1"
+ },
+ "require-dev": {
+ "alleyinteractive/alley-coding-standards": "^0.3",
+ "phpunit/phpunit": "^9.5.8"
+ },
+ "time": "2022-08-31T20:51:21+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "src/class-autoloader.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Alley Interactive",
+ "email": "info@alley.co"
+ }
+ ],
+ "description": "Autoload files using WordPress File Conventions",
+ "support": {
+ "issues": "https://github.com/alleyinteractive/wordpress-autoloader/issues",
+ "source": "https://github.com/alleyinteractive/wordpress-autoloader/tree/v1.1.1"
+ },
+ "install-path": "../alleyinteractive/wordpress-autoloader"
+ },
+ {
+ "name": "alleyinteractive/wp-match-blocks",
+ "version": "v3.1.0",
+ "version_normalized": "3.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/alleyinteractive/wp-match-blocks.git",
+ "reference": "1fa48dc7059653df7ac46ec1fad05e36da236578"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/alleyinteractive/wp-match-blocks/zipball/1fa48dc7059653df7ac46ec1fad05e36da236578",
+ "reference": "1fa48dc7059653df7ac46ec1fad05e36da236578",
+ "shasum": ""
+ },
+ "require": {
+ "alleyinteractive/composer-wordpress-autoloader": "^1.0.0",
+ "alleyinteractive/laminas-validator-extensions": "^2.0.0",
+ "php": "^8.0"
+ },
+ "require-dev": {
+ "alleyinteractive/alley-coding-standards": "^1.0.0",
+ "friendsofphp/php-cs-fixer": "^3.8",
+ "mantle-framework/testkit": "^0.9"
+ },
+ "time": "2023-11-02T03:13:33+00:00",
+ "type": "library",
+ "extra": {
+ "wordpress-autoloader": {
+ "autoload": {
+ "Alley\\": "src/alley/"
+ },
+ "autoload-dev": {
+ "Alley\\": "tests/alley/"
+ }
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "src/alley/wp/match-blocks.php",
+ "src/alley/wp/internals/internals.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Alley",
+ "email": "info@alley.com"
+ }
+ ],
+ "description": "Match WordPress blocks in the given content.",
+ "support": {
+ "issues": "https://github.com/alleyinteractive/wp-match-blocks/issues",
+ "source": "https://github.com/alleyinteractive/wp-match-blocks/tree/v3.1.0"
+ },
+ "install-path": "../alleyinteractive/wp-match-blocks"
+ },
+ {
+ "name": "alleyinteractive/wp-type-extensions",
+ "version": "v2.2.0",
+ "version_normalized": "2.2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/alleyinteractive/wp-type-extensions.git",
+ "reference": "5a225b53fc8336193a6b2ed71e610d8c5b57d200"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/alleyinteractive/wp-type-extensions/zipball/5a225b53fc8336193a6b2ed71e610d8c5b57d200",
+ "reference": "5a225b53fc8336193a6b2ed71e610d8c5b57d200",
+ "shasum": ""
+ },
+ "require": {
+ "alleyinteractive/laminas-validator-extensions": "^2.0",
+ "alleyinteractive/wp-match-blocks": "^3.0",
+ "php": "^8.1",
+ "spatie/once": "^3.1"
+ },
+ "require-dev": {
+ "alleyinteractive/alley-coding-standards": "^1.0.0",
+ "friendsofphp/php-cs-fixer": "^3.8",
+ "mantle-framework/testkit": "^0.9",
+ "szepeviktor/phpstan-wordpress": "^1.1"
+ },
+ "time": "2024-05-29T02:13:50+00:00",
+ "type": "library",
+ "extra": {
+ "wordpress-autoloader": {
+ "autoload": {
+ "Alley\\": "src/alley/"
+ },
+ "autoload-dev": {
+ "Alley\\": "tests/alley/"
+ }
+ }
+ },
+ "installation-source": "dist",
+ "autoload-dev": {
+ "psr-4": {
+ "Alley\\": "tests/alley/"
+ }
+ },
+ "scripts": {
+ "fixer": [
+ "php-cs-fixer -v fix --allow-risky=yes"
+ ],
+ "phpcbf": [
+ "phpcbf"
+ ],
+ "phpcs": [
+ "phpcs"
+ ],
+ "phpstan": [
+ "phpstan --memory-limit=512M"
+ ],
+ "phpunit": [
+ "phpunit"
+ ]
+ },
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Alley",
+ "email": "info@alley.com"
+ }
+ ],
+ "description": "PHP interfaces and implementations for WordPress.",
+ "support": {
+ "source": "https://github.com/alleyinteractive/wp-type-extensions/tree/v2.2.0",
+ "issues": "https://github.com/alleyinteractive/wp-type-extensions/issues"
+ },
+ "install-path": "../alleyinteractive/wp-type-extensions"
+ },
+ {
+ "name": "laminas/laminas-servicemanager",
+ "version": "3.22.1",
+ "version_normalized": "3.22.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laminas/laminas-servicemanager.git",
+ "reference": "de98d297d4743956a0558a6d71616979ff779328"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/de98d297d4743956a0558a6d71616979ff779328",
+ "reference": "de98d297d4743956a0558a6d71616979ff779328",
+ "shasum": ""
+ },
+ "require": {
+ "laminas/laminas-stdlib": "^3.17",
+ "php": "~8.1.0 || ~8.2.0 || ~8.3.0",
+ "psr/container": "^1.0"
+ },
+ "conflict": {
+ "ext-psr": "*",
+ "laminas/laminas-code": "<4.10.0",
+ "zendframework/zend-code": "<3.3.1",
+ "zendframework/zend-servicemanager": "*"
+ },
+ "provide": {
+ "psr/container-implementation": "^1.0"
+ },
+ "replace": {
+ "container-interop/container-interop": "^1.2.0"
+ },
+ "require-dev": {
+ "composer/package-versions-deprecated": "^1.11.99.5",
+ "friendsofphp/proxy-manager-lts": "^1.0.14",
+ "laminas/laminas-code": "^4.10.0",
+ "laminas/laminas-coding-standard": "~2.5.0",
+ "laminas/laminas-container-config-test": "^0.8",
+ "mikey179/vfsstream": "^1.6.11",
+ "phpbench/phpbench": "^1.2.9",
+ "phpunit/phpunit": "^10.4",
+ "psalm/plugin-phpunit": "^0.18.4",
+ "vimeo/psalm": "^5.8.0"
+ },
+ "suggest": {
+ "friendsofphp/proxy-manager-lts": "ProxyManager ^2.1.1 to handle lazy initialization of services"
+ },
+ "time": "2023-10-24T11:19:47+00:00",
+ "bin": [
+ "bin/generate-deps-for-config-factory",
+ "bin/generate-factory-for-class"
+ ],
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "src/autoload.php"
+ ],
+ "psr-4": {
+ "Laminas\\ServiceManager\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "description": "Factory-Driven Dependency Injection Container",
+ "homepage": "https://laminas.dev",
+ "keywords": [
+ "PSR-11",
+ "dependency-injection",
+ "di",
+ "dic",
+ "laminas",
+ "service-manager",
+ "servicemanager"
+ ],
+ "support": {
+ "chat": "https://laminas.dev/chat",
+ "docs": "https://docs.laminas.dev/laminas-servicemanager/",
+ "forum": "https://discourse.laminas.dev",
+ "issues": "https://github.com/laminas/laminas-servicemanager/issues",
+ "rss": "https://github.com/laminas/laminas-servicemanager/releases.atom",
+ "source": "https://github.com/laminas/laminas-servicemanager"
+ },
+ "funding": [
+ {
+ "url": "https://funding.communitybridge.org/projects/laminas-project",
+ "type": "community_bridge"
+ }
+ ],
+ "install-path": "../laminas/laminas-servicemanager"
+ },
+ {
+ "name": "laminas/laminas-stdlib",
+ "version": "3.19.0",
+ "version_normalized": "3.19.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laminas/laminas-stdlib.git",
+ "reference": "6a192dd0882b514e45506f533b833b623b78fff3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/6a192dd0882b514e45506f533b833b623b78fff3",
+ "reference": "6a192dd0882b514e45506f533b833b623b78fff3",
+ "shasum": ""
+ },
+ "require": {
+ "php": "~8.1.0 || ~8.2.0 || ~8.3.0"
+ },
+ "conflict": {
+ "zendframework/zend-stdlib": "*"
+ },
+ "require-dev": {
+ "laminas/laminas-coding-standard": "^2.5",
+ "phpbench/phpbench": "^1.2.15",
+ "phpunit/phpunit": "^10.5.8",
+ "psalm/plugin-phpunit": "^0.18.4",
+ "vimeo/psalm": "^5.20.0"
+ },
+ "time": "2024-01-19T12:39:49+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Laminas\\Stdlib\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "description": "SPL extensions, array utilities, error handlers, and more",
+ "homepage": "https://laminas.dev",
+ "keywords": [
+ "laminas",
+ "stdlib"
+ ],
+ "support": {
+ "chat": "https://laminas.dev/chat",
+ "docs": "https://docs.laminas.dev/laminas-stdlib/",
+ "forum": "https://discourse.laminas.dev",
+ "issues": "https://github.com/laminas/laminas-stdlib/issues",
+ "rss": "https://github.com/laminas/laminas-stdlib/releases.atom",
+ "source": "https://github.com/laminas/laminas-stdlib"
+ },
+ "funding": [
+ {
+ "url": "https://funding.communitybridge.org/projects/laminas-project",
+ "type": "community_bridge"
+ }
+ ],
+ "install-path": "../laminas/laminas-stdlib"
+ },
+ {
+ "name": "laminas/laminas-validator",
+ "version": "2.64.1",
+ "version_normalized": "2.64.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laminas/laminas-validator.git",
+ "reference": "9db115056b666b7540c951b6d4477b8e0b52b9ad"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/9db115056b666b7540c951b6d4477b8e0b52b9ad",
+ "reference": "9db115056b666b7540c951b6d4477b8e0b52b9ad",
+ "shasum": ""
+ },
+ "require": {
+ "laminas/laminas-servicemanager": "^3.21.0",
+ "laminas/laminas-stdlib": "^3.19",
+ "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
+ "psr/http-message": "^1.0.1 || ^2.0.0"
+ },
+ "conflict": {
+ "zendframework/zend-validator": "*"
+ },
+ "require-dev": {
+ "laminas/laminas-coding-standard": "^2.5",
+ "laminas/laminas-db": "^2.20",
+ "laminas/laminas-filter": "^2.35.2",
+ "laminas/laminas-i18n": "^2.26.0",
+ "laminas/laminas-session": "^2.20",
+ "laminas/laminas-uri": "^2.11.0",
+ "phpunit/phpunit": "^10.5.20",
+ "psalm/plugin-phpunit": "^0.19.0",
+ "psr/http-client": "^1.0.3",
+ "psr/http-factory": "^1.1.0",
+ "vimeo/psalm": "^5.24.0"
+ },
+ "suggest": {
+ "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator",
+ "laminas/laminas-filter": "Laminas\\Filter component, required by the Digits validator",
+ "laminas/laminas-i18n": "Laminas\\I18n component to allow translation of validation error messages",
+ "laminas/laminas-i18n-resources": "Translations of validator messages",
+ "laminas/laminas-servicemanager": "Laminas\\ServiceManager component to allow using the ValidatorPluginManager and validator chains",
+ "laminas/laminas-session": "Laminas\\Session component, ^2.8; required by the Csrf validator",
+ "laminas/laminas-uri": "Laminas\\Uri component, required by the Uri and Sitemap\\Loc validators",
+ "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators"
+ },
+ "time": "2024-08-01T09:32:54+00:00",
+ "type": "library",
+ "extra": {
+ "laminas": {
+ "component": "Laminas\\Validator",
+ "config-provider": "Laminas\\Validator\\ConfigProvider"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Laminas\\Validator\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "description": "Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria",
+ "homepage": "https://laminas.dev",
+ "keywords": [
+ "laminas",
+ "validator"
+ ],
+ "support": {
+ "chat": "https://laminas.dev/chat",
+ "docs": "https://docs.laminas.dev/laminas-validator/",
+ "forum": "https://discourse.laminas.dev",
+ "issues": "https://github.com/laminas/laminas-validator/issues",
+ "rss": "https://github.com/laminas/laminas-validator/releases.atom",
+ "source": "https://github.com/laminas/laminas-validator"
+ },
+ "funding": [
+ {
+ "url": "https://funding.communitybridge.org/projects/laminas-project",
+ "type": "community_bridge"
+ }
+ ],
+ "install-path": "../laminas/laminas-validator"
+ },
+ {
+ "name": "psr/container",
+ "version": "1.1.2",
+ "version_normalized": "1.1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "513e0666f7216c7459170d56df27dfcefe1689ea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea",
+ "reference": "513e0666f7216c7459170d56df27dfcefe1689ea",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "time": "2021-11-05T16:50:12+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/1.1.2"
+ },
+ "install-path": "../psr/container"
+ },
+ {
+ "name": "psr/http-message",
+ "version": "2.0",
+ "version_normalized": "2.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
+ "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "time": "2023-04-04T09:54:51+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-message/tree/2.0"
+ },
+ "install-path": "../psr/http-message"
+ },
+ {
+ "name": "spatie/once",
+ "version": "3.1.1",
+ "version_normalized": "3.1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/once.git",
+ "reference": "25252b821765d72566be17c52ea05b35329f0f8f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/once/zipball/25252b821765d72566be17c52ea05b35329f0f8f",
+ "reference": "25252b821765d72566be17c52ea05b35329f0f8f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.0"
+ },
+ "require-dev": {
+ "pestphp/pest": "^1.21",
+ "symfony/var-dumper": "^5.1"
+ },
+ "time": "2024-05-27T09:17:58+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "Spatie\\Once\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "A magic memoization function",
+ "homepage": "https://github.com/spatie/once",
+ "keywords": [
+ "cache",
+ "callable",
+ "memoization",
+ "once",
+ "spatie"
+ ],
+ "support": {
+ "source": "https://github.com/spatie/once/tree/3.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://spatie.be/open-source/support-us",
+ "type": "custom"
+ }
+ ],
+ "install-path": "../spatie/once"
+ }
+ ],
+ "dev": false,
+ "dev-package-names": []
+}
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
new file mode 100644
index 00000000..37606e90
--- /dev/null
+++ b/vendor/composer/installed.php
@@ -0,0 +1,143 @@
+ array(
+ 'name' => 'alleyinteractive/wp-curate',
+ 'pretty_version' => '1.0.0+no-version-set',
+ 'version' => '1.0.0.0',
+ 'reference' => null,
+ 'type' => 'wordpress-plugin',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'dev' => false,
+ ),
+ 'versions' => array(
+ 'alleyinteractive/composer-wordpress-autoloader' => array(
+ 'pretty_version' => 'v1.1.0',
+ 'version' => '1.1.0.0',
+ 'reference' => '30acf9e3498a84af478e316ae3869fa362835695',
+ 'type' => 'composer-plugin',
+ 'install_path' => __DIR__ . '/../alleyinteractive/composer-wordpress-autoloader',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'alleyinteractive/laminas-validator-extensions' => array(
+ 'pretty_version' => 'v2.1.1',
+ 'version' => '2.1.1.0',
+ 'reference' => '09fd68d1fda5179d5e972cf7053edf4eab903313',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../alleyinteractive/laminas-validator-extensions',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'alleyinteractive/traverse-reshape' => array(
+ 'pretty_version' => 'v2.0.0',
+ 'version' => '2.0.0.0',
+ 'reference' => '2b5bfd0723c0b9312ce6f7673f0e861fbe2d1567',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../alleyinteractive/traverse-reshape',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'alleyinteractive/wordpress-autoloader' => array(
+ 'pretty_version' => 'v1.1.1',
+ 'version' => '1.1.1.0',
+ 'reference' => 'c7599d95f49f1cdc38fad19944a50b19ec0dd6ca',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../alleyinteractive/wordpress-autoloader',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'alleyinteractive/wp-curate' => array(
+ 'pretty_version' => '1.0.0+no-version-set',
+ 'version' => '1.0.0.0',
+ 'reference' => null,
+ 'type' => 'wordpress-plugin',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'alleyinteractive/wp-match-blocks' => array(
+ 'pretty_version' => 'v3.1.0',
+ 'version' => '3.1.0.0',
+ 'reference' => '1fa48dc7059653df7ac46ec1fad05e36da236578',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../alleyinteractive/wp-match-blocks',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'alleyinteractive/wp-type-extensions' => array(
+ 'pretty_version' => 'v2.2.0',
+ 'version' => '2.2.0.0',
+ 'reference' => '5a225b53fc8336193a6b2ed71e610d8c5b57d200',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../alleyinteractive/wp-type-extensions',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'container-interop/container-interop' => array(
+ 'dev_requirement' => false,
+ 'replaced' => array(
+ 0 => '^1.2.0',
+ ),
+ ),
+ 'laminas/laminas-servicemanager' => array(
+ 'pretty_version' => '3.22.1',
+ 'version' => '3.22.1.0',
+ 'reference' => 'de98d297d4743956a0558a6d71616979ff779328',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../laminas/laminas-servicemanager',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'laminas/laminas-stdlib' => array(
+ 'pretty_version' => '3.19.0',
+ 'version' => '3.19.0.0',
+ 'reference' => '6a192dd0882b514e45506f533b833b623b78fff3',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../laminas/laminas-stdlib',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'laminas/laminas-validator' => array(
+ 'pretty_version' => '2.64.1',
+ 'version' => '2.64.1.0',
+ 'reference' => '9db115056b666b7540c951b6d4477b8e0b52b9ad',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../laminas/laminas-validator',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'psr/container' => array(
+ 'pretty_version' => '1.1.2',
+ 'version' => '1.1.2.0',
+ 'reference' => '513e0666f7216c7459170d56df27dfcefe1689ea',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../psr/container',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'psr/container-implementation' => array(
+ 'dev_requirement' => false,
+ 'provided' => array(
+ 0 => '^1.0',
+ ),
+ ),
+ 'psr/http-message' => array(
+ 'pretty_version' => '2.0',
+ 'version' => '2.0.0.0',
+ 'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../psr/http-message',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'spatie/once' => array(
+ 'pretty_version' => '3.1.1',
+ 'version' => '3.1.1.0',
+ 'reference' => '25252b821765d72566be17c52ea05b35329f0f8f',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../spatie/once',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ ),
+);
diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php
new file mode 100644
index 00000000..4c3a5d68
--- /dev/null
+++ b/vendor/composer/platform_check.php
@@ -0,0 +1,26 @@
+= 80100)) {
+ $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.';
+}
+
+if ($issues) {
+ if (!headers_sent()) {
+ header('HTTP/1.1 500 Internal Server Error');
+ }
+ if (!ini_get('display_errors')) {
+ if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+ fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
+ } elseif (!headers_sent()) {
+ echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
+ }
+ }
+ trigger_error(
+ 'Composer detected issues in your platform: ' . implode(' ', $issues),
+ E_USER_ERROR
+ );
+}
diff --git a/vendor/laminas/laminas-servicemanager/COPYRIGHT.md b/vendor/laminas/laminas-servicemanager/COPYRIGHT.md
new file mode 100644
index 00000000..0a8cccc0
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/COPYRIGHT.md
@@ -0,0 +1 @@
+Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. (https://getlaminas.org/)
diff --git a/vendor/laminas/laminas-servicemanager/LICENSE.md b/vendor/laminas/laminas-servicemanager/LICENSE.md
new file mode 100644
index 00000000..10b40f14
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/LICENSE.md
@@ -0,0 +1,26 @@
+Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+- Neither the name of Laminas Foundation nor the names of its contributors may
+ be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/laminas/laminas-servicemanager/README.md b/vendor/laminas/laminas-servicemanager/README.md
new file mode 100644
index 00000000..862c83f0
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/README.md
@@ -0,0 +1,40 @@
+# laminas-servicemanager
+
+[![Build Status](https://github.com/laminas/laminas-servicemanager/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/laminas/laminas-servicemanager/actions/workflows/continuous-integration.yml)
+[![Psalm coverage](https://shepherd.dev/github/laminas/laminas-servicemanager/coverage.svg?)](https://shepherd.dev/github/laminas/laminas-servicemanager)
+
+> ## 🇷🇺 Русским гражданам
+>
+> Мы, участники Laminas, родились и живем в разных странах. У многих из нас есть друзья, родственники и коллеги как в России, так и в Украине. Некоторые из нас родились в России. Некоторые из нас живут в России. У некоторых бабушки и дедушки сражались с фашистами во Второй мировой войне. Здесь никто не поддерживает фашизм.
+>
+> У одного из нас есть украинская родственница, которая спаслась из дома вместе с сыном. Поезд задержался из-за бомбежки на дороге впереди. У нас есть друзья, которые прячутся в бомбоубежищах. Мы с тревогой ждем весточки от них после воздушных налетов, которые беспорядочно наносят удары и попадают по больницам, школам, детским садам и домам. Мы не берем это из каких-либо СМИ. Мы наблюдаем это напрямую.
+>
+> Вы доверяете нам достаточно, чтоб использовать наши программы, и мы просим вас довериться нам вновь. Мы нуждаемся в помощи. Выходите и протестуйте против этой бесполезной войны. Остановите кровопролитие. Скажите "Нет войне!"
+>
+> ## 🇺🇸 To Citizens of Russia
+>
+> We at Laminas come from all over the world. Many of us have friends, family and colleagues in both Russia and Ukraine. Some of us were born in Russia. Some of us currently live in Russia. Some have grandparents who fought Nazis in World War II. Nobody here supports fascism.
+>
+> One team member has a Ukrainian relative who fled her home with her son. The train was delayed due to bombing on the road ahead. We have friends who are hiding in bomb shelters. We anxiously follow up on them after the air raids, which indiscriminately fire at hospitals, schools, kindergartens and houses. We're not taking this from any media. These are our actual experiences.
+>
+> You trust us enough to use our software. We ask that you trust us to say the truth on this. We need your help. Go out and protest this unnecessary war. Stop the bloodshed. Say "stop the war!"
+
+The Service Locator design pattern is implemented by the `Laminas\ServiceManager`
+component. The Service Locator is a service/object locator, tasked with
+retrieving other objects.
+
+- File issues at https://github.com/laminas/laminas-servicemanager/issues
+- [Online documentation](https://docs.laminas.dev/laminas-servicemanager)
+- [Documentation source files](docs/book/)
+
+## Benchmarks
+
+We provide scripts for benchmarking laminas-servicemanager using the
+[PHPBench](https://github.com/phpbench/phpbench) framework; these can be
+found in the `benchmarks/` directory.
+
+To execute the benchmarks you can run the following command:
+
+```bash
+$ vendor/bin/phpbench run --report=aggregate
+```
diff --git a/vendor/laminas/laminas-servicemanager/composer.json b/vendor/laminas/laminas-servicemanager/composer.json
new file mode 100644
index 00000000..996a777d
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/composer.json
@@ -0,0 +1,98 @@
+{
+ "name": "laminas/laminas-servicemanager",
+ "description": "Factory-Driven Dependency Injection Container",
+ "license": "BSD-3-Clause",
+ "keywords": [
+ "laminas",
+ "di",
+ "dic",
+ "dependency-injection",
+ "psr-11",
+ "servicemanager",
+ "service-manager"
+ ],
+ "homepage": "https://laminas.dev",
+ "support": {
+ "issues": "https://github.com/laminas/laminas-servicemanager/issues",
+ "forum": "https://discourse.laminas.dev",
+ "chat": "https://laminas.dev/chat",
+ "source": "https://github.com/laminas/laminas-servicemanager",
+ "docs": "https://docs.laminas.dev/laminas-servicemanager/",
+ "rss": "https://github.com/laminas/laminas-servicemanager/releases.atom"
+ },
+ "require": {
+ "php": "~8.1.0 || ~8.2.0 || ~8.3.0",
+ "laminas/laminas-stdlib": "^3.17",
+ "psr/container": "^1.0"
+ },
+ "require-dev": {
+ "composer/package-versions-deprecated": "^1.11.99.5",
+ "friendsofphp/proxy-manager-lts": "^1.0.14",
+ "laminas/laminas-code": "^4.10.0",
+ "laminas/laminas-coding-standard": "~2.5.0",
+ "laminas/laminas-container-config-test": "^0.8",
+ "mikey179/vfsstream": "^1.6.11",
+ "phpbench/phpbench": "^1.2.9",
+ "phpunit/phpunit": "^10.4",
+ "psalm/plugin-phpunit": "^0.18.4",
+ "vimeo/psalm": "^5.8.0"
+ },
+ "replace": {
+ "container-interop/container-interop": "^1.2.0"
+ },
+ "conflict": {
+ "ext-psr": "*",
+ "laminas/laminas-code": "<4.10.0",
+ "zendframework/zend-code": "<3.3.1",
+ "zendframework/zend-servicemanager": "*"
+ },
+ "provide": {
+ "psr/container-implementation": "^1.0"
+ },
+ "suggest": {
+ "friendsofphp/proxy-manager-lts": "ProxyManager ^2.1.1 to handle lazy initialization of services"
+ },
+ "autoload": {
+ "psr-4": {
+ "Laminas\\ServiceManager\\": "src/"
+ },
+ "files": [
+ "src/autoload.php"
+ ]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "LaminasTest\\ServiceManager\\": "test/",
+ "LaminasBench\\ServiceManager\\": "benchmarks/"
+ },
+ "files": [
+ "test/autoload.php"
+ ]
+ },
+ "bin": [
+ "bin/generate-deps-for-config-factory",
+ "bin/generate-factory-for-class"
+ ],
+ "config": {
+ "allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true,
+ "composer/package-versions-deprecated": true
+ },
+ "platform": {
+ "php": "8.1.99"
+ },
+ "sort-packages": true
+ },
+ "scripts": {
+ "benchmark": "phpbench run --revs=2 --iterations=2 --report=aggregate",
+ "check": [
+ "@cs-check",
+ "@test"
+ ],
+ "cs-check": "phpcs",
+ "cs-fix": "phpcbf",
+ "static-analysis": "psalm --shepherd --stats",
+ "test": "phpunit --colors=always",
+ "test-coverage": "phpunit --colors=always --coverage-clover clover.xml"
+ }
+}
diff --git a/vendor/laminas/laminas-servicemanager/src/AbstractFactory/ConfigAbstractFactory.php b/vendor/laminas/laminas-servicemanager/src/AbstractFactory/ConfigAbstractFactory.php
new file mode 100644
index 00000000..3b58ad8f
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/src/AbstractFactory/ConfigAbstractFactory.php
@@ -0,0 +1,78 @@
+has('config')) {
+ return false;
+ }
+ $config = $container->get('config');
+ if (! isset($config[self::class])) {
+ return false;
+ }
+ $dependencies = $config[self::class];
+
+ return is_array($dependencies) && array_key_exists($requestedName, $dependencies);
+ }
+
+ /** {@inheritDoc} */
+ public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null)
+ {
+ if (! $container->has('config')) {
+ throw new ServiceNotCreatedException('Cannot find a config array in the container');
+ }
+
+ $config = $container->get('config');
+
+ if (! (is_array($config) || $config instanceof ArrayObject)) {
+ throw new ServiceNotCreatedException('Config must be an array or an instance of ArrayObject');
+ }
+ if (! isset($config[self::class])) {
+ throw new ServiceNotCreatedException('Cannot find a `' . self::class . '` key in the config array');
+ }
+
+ $dependencies = $config[self::class];
+
+ if (
+ ! is_array($dependencies)
+ || ! array_key_exists($requestedName, $dependencies)
+ || ! is_array($dependencies[$requestedName])
+ ) {
+ throw new ServiceNotCreatedException('Service dependencies config must exist and be an array');
+ }
+
+ $serviceDependencies = $dependencies[$requestedName];
+
+ if ($serviceDependencies !== array_values(array_map('strval', $serviceDependencies))) {
+ $problem = json_encode(array_map('gettype', $serviceDependencies));
+ throw new ServiceNotCreatedException(
+ 'Service dependencies config must be an array of strings, ' . $problem . ' given'
+ );
+ }
+
+ $arguments = array_map([$container, 'get'], $serviceDependencies);
+
+ return new $requestedName(...$arguments);
+ }
+}
diff --git a/vendor/laminas/laminas-servicemanager/src/AbstractFactory/ReflectionBasedAbstractFactory.php b/vendor/laminas/laminas-servicemanager/src/AbstractFactory/ReflectionBasedAbstractFactory.php
new file mode 100644
index 00000000..4f4654bd
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/src/AbstractFactory/ReflectionBasedAbstractFactory.php
@@ -0,0 +1,245 @@
+
+ * 'service_manager' => [
+ * 'abstract_factories' => [
+ * ReflectionBasedAbstractFactory::class,
+ * ],
+ * ],
+ *
+ *
+ * Or as a factory, mapping a class name to it:
+ *
+ *
+ * 'service_manager' => [
+ * 'factories' => [
+ * MyClassWithDependencies::class => ReflectionBasedAbstractFactory::class,
+ * ],
+ * ],
+ *
+ *
+ * The latter approach is more explicit, and also more performant.
+ *
+ * The factory has the following constraints/features:
+ *
+ * - A parameter named `$config` typehinted as an array will receive the
+ * application "config" service (i.e., the merged configuration).
+ * - Parameters type-hinted against array, but not named `$config` will
+ * be injected with an empty array.
+ * - Scalar parameters will result in an exception being thrown, unless
+ * a default value is present; if the default is present, that will be used.
+ * - If a service cannot be found for a given typehint, the factory will
+ * raise an exception detailing this.
+ * - Some services provided by Laminas components do not have
+ * entries based on their class name (for historical reasons); the
+ * factory allows defining a map of these class/interface names to the
+ * corresponding service name to allow them to resolve.
+ *
+ * `$options` passed to the factory are ignored in all cases, as we cannot
+ * make assumptions about which argument(s) they might replace.
+ *
+ * Based on the LazyControllerAbstractFactory from laminas-mvc.
+ */
+class ReflectionBasedAbstractFactory implements AbstractFactoryInterface
+{
+ /**
+ * Maps known classes/interfaces to the service that provides them; only
+ * required for those services with no entry based on the class/interface
+ * name.
+ *
+ * Extend the class if you wish to add to the list.
+ *
+ * Example:
+ *
+ *
+ * [
+ * \Laminas\Filter\FilterPluginManager::class => 'FilterManager',
+ * \Laminas\Validator\ValidatorPluginManager::class => 'ValidatorManager',
+ * ]
+ *
+ *
+ * @var string[]
+ */
+ protected $aliases = [];
+
+ /**
+ * Allows overriding the internal list of aliases. These should be of the
+ * form `class name => well-known service name`; see the documentation for
+ * the `$aliases` property for details on what is accepted.
+ *
+ * @param string[] $aliases
+ */
+ public function __construct(array $aliases = [])
+ {
+ if (! empty($aliases)) {
+ $this->aliases = $aliases;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return DispatchableInterface
+ */
+ public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null)
+ {
+ $reflectionClass = new ReflectionClass($requestedName);
+
+ if (null === ($constructor = $reflectionClass->getConstructor())) {
+ return new $requestedName();
+ }
+
+ $reflectionParameters = $constructor->getParameters();
+
+ if (empty($reflectionParameters)) {
+ return new $requestedName();
+ }
+
+ $resolver = $container->has('config')
+ ? $this->resolveParameterWithConfigService($container, $requestedName)
+ : $this->resolveParameterWithoutConfigService($container, $requestedName);
+
+ $parameters = array_map($resolver, $reflectionParameters);
+
+ return new $requestedName(...$parameters);
+ }
+
+ /** {@inheritDoc} */
+ public function canCreate(ContainerInterface $container, $requestedName)
+ {
+ return class_exists($requestedName) && $this->canCallConstructor($requestedName);
+ }
+
+ private function canCallConstructor(string $requestedName): bool
+ {
+ $constructor = (new ReflectionClass($requestedName))->getConstructor();
+
+ return $constructor === null || $constructor->isPublic();
+ }
+
+ /**
+ * Resolve a parameter to a value.
+ *
+ * Returns a callback for resolving a parameter to a value, but without
+ * allowing mapping array `$config` arguments to the `config` service.
+ *
+ * @param string $requestedName
+ * @return callable
+ */
+ private function resolveParameterWithoutConfigService(ContainerInterface $container, $requestedName)
+ {
+ /**
+ * @param ReflectionParameter $parameter
+ * @return mixed
+ * @throws ServiceNotFoundException If type-hinted parameter cannot be
+ * resolved to a service in the container.
+ * @psalm-suppress MissingClosureReturnType
+ */
+ return fn(ReflectionParameter $parameter) => $this->resolveParameter($parameter, $container, $requestedName);
+ }
+
+ /**
+ * Returns a callback for resolving a parameter to a value, including mapping 'config' arguments.
+ *
+ * Unlike resolveParameter(), this version will detect `$config` array
+ * arguments and have them return the 'config' service.
+ *
+ * @param string $requestedName
+ * @return callable
+ */
+ private function resolveParameterWithConfigService(ContainerInterface $container, $requestedName)
+ {
+ /**
+ * @param ReflectionParameter $parameter
+ * @return mixed
+ * @throws ServiceNotFoundException If type-hinted parameter cannot be
+ * resolved to a service in the container.
+ */
+ return function (ReflectionParameter $parameter) use ($container, $requestedName) {
+ if ($parameter->getName() === 'config') {
+ $type = $parameter->getType();
+ if ($type instanceof ReflectionNamedType && $type->getName() === 'array') {
+ return $container->get('config');
+ }
+ }
+ return $this->resolveParameter($parameter, $container, $requestedName);
+ };
+ }
+
+ /**
+ * Logic common to all parameter resolution.
+ *
+ * @param string $requestedName
+ * @return mixed
+ * @throws ServiceNotFoundException If type-hinted parameter cannot be
+ * resolved to a service in the container.
+ */
+ private function resolveParameter(ReflectionParameter $parameter, ContainerInterface $container, $requestedName)
+ {
+ $type = $parameter->getType();
+ $type = $type instanceof ReflectionNamedType ? $type->getName() : null;
+
+ if ($type === 'array') {
+ return [];
+ }
+
+ if ($type === null || (is_string($type) && ! class_exists($type) && ! interface_exists($type))) {
+ if (! $parameter->isDefaultValueAvailable()) {
+ throw new ServiceNotFoundException(sprintf(
+ 'Unable to create service "%s"; unable to resolve parameter "%s" '
+ . 'to a class, interface, or array type',
+ $requestedName,
+ $parameter->getName()
+ ));
+ }
+
+ return $parameter->getDefaultValue();
+ }
+
+ $type = $this->aliases[$type] ?? $type;
+
+ if ($container->has($type)) {
+ return $container->get($type);
+ }
+
+ if (! $parameter->isOptional()) {
+ throw new ServiceNotFoundException(sprintf(
+ 'Unable to create service "%s"; unable to resolve parameter "%s" using type hint "%s"',
+ $requestedName,
+ $parameter->getName(),
+ $type
+ ));
+ }
+
+ // Type not available in container, but the value is optional and has a
+ // default defined.
+ return $parameter->getDefaultValue();
+ }
+}
diff --git a/vendor/laminas/laminas-servicemanager/src/AbstractFactoryInterface.php b/vendor/laminas/laminas-servicemanager/src/AbstractFactoryInterface.php
new file mode 100644
index 00000000..2a8ff71b
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/src/AbstractFactoryInterface.php
@@ -0,0 +1,53 @@
+
+ * @psalm-import-type ServiceManagerConfiguration from ServiceManager
+ * @psalm-suppress PropertyNotSetInConstructor
+ */
+abstract class AbstractPluginManager extends ServiceManager implements PluginManagerInterface
+{
+ /**
+ * Whether or not to auto-add a FQCN as an invokable if it exists.
+ *
+ * @var bool
+ */
+ protected $autoAddInvokableClass = true;
+
+ /**
+ * An object type that the created instance must be instanced of
+ *
+ * @var null|string
+ * @psalm-var null|class-string
+ */
+ protected $instanceOf;
+
+ /**
+ * Sets the provided $parentLocator as the creation context for all
+ * factories; for $config, {@see \Laminas\ServiceManager\ServiceManager::configure()}
+ * for details on its accepted structure.
+ *
+ * @param null|ConfigInterface|ContainerInterface $configInstanceOrParentLocator
+ * @param array $config
+ * @psalm-param ServiceManagerConfiguration $config
+ */
+ public function __construct($configInstanceOrParentLocator = null, array $config = [])
+ {
+ /** @psalm-suppress DocblockTypeContradiction */
+ if (
+ null !== $configInstanceOrParentLocator
+ && ! $configInstanceOrParentLocator instanceof ConfigInterface
+ && ! $configInstanceOrParentLocator instanceof ContainerInterface
+ ) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ '%s expects a ConfigInterface or ContainerInterface instance as the first argument; received %s',
+ self::class,
+ is_object($configInstanceOrParentLocator)
+ ? $configInstanceOrParentLocator::class
+ : gettype($configInstanceOrParentLocator)
+ ));
+ }
+
+ if ($configInstanceOrParentLocator instanceof ConfigInterface) {
+ trigger_error(sprintf(
+ 'Usage of %s as a constructor argument for %s is now deprecated',
+ ConfigInterface::class,
+ static::class
+ ), E_USER_DEPRECATED);
+ $config = $configInstanceOrParentLocator->toArray();
+ }
+
+ parent::__construct($config);
+
+ if (! $configInstanceOrParentLocator instanceof ContainerInterface) {
+ trigger_error(sprintf(
+ '%s now expects a %s instance representing the parent container; please update your code',
+ __METHOD__,
+ ContainerInterface::class
+ ), E_USER_DEPRECATED);
+ }
+
+ $this->creationContext = $configInstanceOrParentLocator instanceof ContainerInterface
+ ? $configInstanceOrParentLocator
+ : $this;
+ }
+
+ /**
+ * Override configure() to validate service instances.
+ *
+ * @param array $config
+ * @psalm-param ServiceManagerConfiguration $config
+ * @return self
+ * @throws InvalidServiceException If an instance passed in the `services` configuration is invalid for the
+ * plugin manager.
+ * @throws ContainerModificationsNotAllowedException If the allow override flag has been toggled off, and a
+ * service instanceexists for a given service.
+ */
+ public function configure(array $config)
+ {
+ if (isset($config['services'])) {
+ foreach ($config['services'] as $service) {
+ $this->validate($service);
+ }
+ }
+
+ parent::configure($config);
+
+ return $this;
+ }
+
+ /**
+ * Override setService for additional plugin validation.
+ *
+ * {@inheritDoc}
+ *
+ * @param string|class-string $name
+ * @param InstanceType $service
+ */
+ public function setService($name, $service)
+ {
+ $this->validate($service);
+ parent::setService($name, $service);
+ }
+
+ /**
+ * @param class-string|string $name Service name of plugin to retrieve.
+ * @param null|array $options Options to use when creating the instance.
+ * @return mixed
+ * @psalm-return ($name is class-string ? InstanceType : mixed)
+ * @throws Exception\ServiceNotFoundException If the manager does not have
+ * a service definition for the instance, and the service is not
+ * auto-invokable.
+ * @throws InvalidServiceException If the plugin created is invalid for the
+ * plugin context.
+ */
+ public function get($name, ?array $options = null)
+ {
+ if (! $this->has($name)) {
+ if (! $this->autoAddInvokableClass || ! class_exists($name)) {
+ throw new Exception\ServiceNotFoundException(sprintf(
+ 'A plugin by the name "%s" was not found in the plugin manager %s',
+ $name,
+ static::class
+ ));
+ }
+
+ $this->setFactory($name, Factory\InvokableFactory::class);
+ }
+
+ $instance = ! $options ? parent::get($name) : $this->build($name, $options);
+ $this->validate($instance);
+ return $instance;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @psalm-assert InstanceType $instance
+ */
+ public function validate(mixed $instance)
+ {
+ if (method_exists($this, 'validatePlugin')) {
+ trigger_error(sprintf(
+ '%s::validatePlugin() has been deprecated as of 3.0; please define validate() instead',
+ static::class
+ ), E_USER_DEPRECATED);
+ $this->validatePlugin($instance);
+ return;
+ }
+
+ if (empty($this->instanceOf) || $instance instanceof $this->instanceOf) {
+ return;
+ }
+
+ throw new InvalidServiceException(sprintf(
+ 'Plugin manager "%s" expected an instance of type "%s", but "%s" was received',
+ self::class,
+ $this->instanceOf,
+ is_object($instance) ? $instance::class : gettype($instance)
+ ));
+ }
+
+ /**
+ * Implemented for backwards compatibility only.
+ *
+ * Returns the creation context.
+ *
+ * @deprecated since 3.0.0. The creation context should be passed during
+ * instantiation instead.
+ *
+ * @return void
+ */
+ public function setServiceLocator(ContainerInterface $container)
+ {
+ trigger_error(sprintf(
+ 'Usage of %s is deprecated since v3.0.0; please pass the container to the constructor instead',
+ __METHOD__
+ ), E_USER_DEPRECATED);
+ $this->creationContext = $container;
+ }
+}
diff --git a/vendor/laminas/laminas-servicemanager/src/Config.php b/vendor/laminas/laminas-servicemanager/src/Config.php
new file mode 100644
index 00000000..b7b6042c
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/src/Config.php
@@ -0,0 +1,102 @@
+ */
+ private array $allowedKeys = [
+ 'abstract_factories' => true,
+ 'aliases' => true,
+ 'delegators' => true,
+ 'factories' => true,
+ 'initializers' => true,
+ 'invokables' => true,
+ 'lazy_services' => true,
+ 'services' => true,
+ 'shared' => true,
+ ];
+
+ /**
+ * @var array
+ * @psalm-var ServiceManagerConfigurationType
+ */
+ protected $config = [
+ 'abstract_factories' => [],
+ 'aliases' => [],
+ 'delegators' => [],
+ 'factories' => [],
+ 'initializers' => [],
+ 'invokables' => [],
+ 'lazy_services' => [],
+ 'services' => [],
+ 'shared' => [],
+ ];
+
+ /**
+ * @psalm-param ServiceManagerConfigurationType $config
+ */
+ public function __construct(array $config = [])
+ {
+ // Only merge keys we're interested in
+ foreach (array_keys($config) as $key) {
+ if (! isset($this->allowedKeys[$key])) {
+ unset($config[$key]);
+ }
+ }
+
+ /** @psalm-suppress ArgumentTypeCoercion */
+ $this->config = $this->merge($this->config, $config);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function configureServiceManager(ServiceManager $serviceManager)
+ {
+ return $serviceManager->configure($this->config);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function toArray()
+ {
+ return $this->config;
+ }
+
+ /**
+ * @psalm-param ServiceManagerConfigurationType $a
+ * @psalm-param ServiceManagerConfigurationType $b
+ * @psalm-return ServiceManagerConfigurationType
+ */
+ private function merge(array $a, array $b)
+ {
+ return ArrayUtils::merge($a, $b);
+ }
+}
diff --git a/vendor/laminas/laminas-servicemanager/src/ConfigInterface.php b/vendor/laminas/laminas-servicemanager/src/ConfigInterface.php
new file mode 100644
index 00000000..15fb7e3e
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/src/ConfigInterface.php
@@ -0,0 +1,93 @@
+|Factory\AbstractFactoryInterface)
+ * >
+ * @psalm-type DelegatorsConfigurationType = array<
+ * string,
+ * array<
+ * array-key,
+ * (class-string|Factory\DelegatorFactoryInterface)
+ * |callable(ContainerInterface,string,callable():object,array|null):object
+ * >
+ * >
+ * @psalm-type FactoriesConfigurationType = array<
+ * string,
+ * (class-string|Factory\FactoryInterface)
+ * |callable(ContainerInterface,?string,?array|null):object
+ * >
+ * @psalm-type InitializersConfigurationType = array<
+ * array-key,
+ * (class-string|Initializer\InitializerInterface)
+ * |callable(ContainerInterface,object):void
+ * >
+ * @psalm-type LazyServicesConfigurationType = array{
+ * class_map?:array,
+ * proxies_namespace?:non-empty-string,
+ * proxies_target_dir?:non-empty-string,
+ * write_proxy_files?:bool
+ * }
+ * @psalm-type ServiceManagerConfigurationType = array{
+ * abstract_factories?: AbstractFactoriesConfigurationType,
+ * aliases?: array,
+ * delegators?: DelegatorsConfigurationType,
+ * factories?: FactoriesConfigurationType,
+ * initializers?: InitializersConfigurationType,
+ * invokables?: array,
+ * lazy_services?: LazyServicesConfigurationType,
+ * services?: array,
+ * shared?:array,
+ * ...
+ * }
+ */
+interface ConfigInterface
+{
+ /**
+ * Configure a service manager.
+ *
+ * Implementations should pull configuration from somewhere (typically
+ * local properties) and pass it to a ServiceManager's withConfig() method,
+ * returning a new instance.
+ *
+ * @return ServiceManager
+ */
+ public function configureServiceManager(ServiceManager $serviceManager);
+
+ /**
+ * Return configuration for a service manager instance as an array.
+ *
+ * Implementations MUST return an array compatible with ServiceManager::configure,
+ * containing one or more of the following keys:
+ *
+ * - abstract_factories
+ * - aliases
+ * - delegators
+ * - factories
+ * - initializers
+ * - invokables
+ * - lazy_services
+ * - services
+ * - shared
+ *
+ * In other words, this should return configuration that can be used to instantiate
+ * a service manager or plugin manager, or pass to its `withConfig()` method.
+ *
+ * @return array
+ * @psalm-return ServiceManagerConfigurationType
+ */
+ public function toArray();
+}
diff --git a/vendor/laminas/laminas-servicemanager/src/DelegatorFactoryInterface.php b/vendor/laminas/laminas-servicemanager/src/DelegatorFactoryInterface.php
new file mode 100644
index 00000000..1b30ecf6
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/src/DelegatorFactoryInterface.php
@@ -0,0 +1,41 @@
+ ' . $cursor;
+ }
+ $cycle .= ' -> ' . $alias . "\n";
+
+ return new self(sprintf(
+ "A cycle was detected within the aliases definitions:\n%s",
+ $cycle
+ ));
+ }
+
+ /**
+ * @param string[] $aliases map of referenced services, indexed by alias name (string)
+ * @return self
+ */
+ public static function fromAliasesMap(array $aliases)
+ {
+ $detectedCycles = array_filter(array_map(
+ static fn($alias): ?array => self::getCycleFor($aliases, $alias),
+ array_keys($aliases)
+ ));
+
+ if (! $detectedCycles) {
+ return new self(sprintf(
+ "A cycle was detected within the following aliases map:\n\n%s",
+ self::printReferencesMap($aliases)
+ ));
+ }
+
+ return new self(sprintf(
+ "Cycles were detected within the provided aliases:\n\n%s\n\n"
+ . "The cycle was detected in the following alias map:\n\n%s",
+ self::printCycles(self::deDuplicateDetectedCycles($detectedCycles)),
+ self::printReferencesMap($aliases)
+ ));
+ }
+
+ /**
+ * Retrieves the cycle detected for the given $alias, or `null` if no cycle was detected
+ *
+ * @param string[] $aliases
+ * @param string $alias
+ * @return array|null
+ */
+ private static function getCycleFor(array $aliases, $alias)
+ {
+ $cycleCandidate = [];
+ $targetName = $alias;
+
+ while (isset($aliases[$targetName])) {
+ if (isset($cycleCandidate[$targetName])) {
+ return $cycleCandidate;
+ }
+
+ $cycleCandidate[$targetName] = true;
+ $targetName = $aliases[$targetName];
+ }
+
+ return null;
+ }
+
+ /**
+ * @param string[] $aliases
+ * @return string
+ */
+ private static function printReferencesMap(array $aliases)
+ {
+ $map = [];
+
+ foreach ($aliases as $alias => $reference) {
+ $map[] = '"' . $alias . '" => "' . $reference . '"';
+ }
+
+ return "[\n" . implode("\n", $map) . "\n]";
+ }
+
+ /**
+ * @param string[][] $detectedCycles
+ * @return string
+ */
+ private static function printCycles(array $detectedCycles)
+ {
+ return "[\n" . implode("\n", array_map([self::class, 'printCycle'], $detectedCycles)) . "\n]";
+ }
+
+ /**
+ * @param string[] $detectedCycle
+ * @return string
+ * @phpcsSuppress SlevomatCodingStandard.Classes.UnusedPrivateElements.UnusedMethod
+ */
+ private static function printCycle(array $detectedCycle)
+ {
+ $fullCycle = array_keys($detectedCycle);
+ $fullCycle[] = reset($fullCycle);
+
+ return implode(
+ ' => ',
+ array_map(
+ static fn($cycle): string => '"' . $cycle . '"',
+ $fullCycle
+ )
+ );
+ }
+
+ /**
+ * @param bool[][] $detectedCycles
+ * @return bool[][] de-duplicated
+ */
+ private static function deDuplicateDetectedCycles(array $detectedCycles)
+ {
+ $detectedCyclesByHash = [];
+
+ foreach ($detectedCycles as $detectedCycle) {
+ $cycleAliases = array_keys($detectedCycle);
+
+ sort($cycleAliases);
+
+ $hash = serialize($cycleAliases);
+
+ $detectedCyclesByHash[$hash] ??= $detectedCycle;
+ }
+
+ return array_values($detectedCyclesByHash);
+ }
+}
diff --git a/vendor/laminas/laminas-servicemanager/src/Exception/ExceptionInterface.php b/vendor/laminas/laminas-servicemanager/src/Exception/ExceptionInterface.php
new file mode 100644
index 00000000..cccc42df
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/src/Exception/ExceptionInterface.php
@@ -0,0 +1,14 @@
+ $options
+ * @return object
+ * @throws ServiceNotFoundException If unable to resolve the service.
+ * @throws ServiceNotCreatedException If an exception is raised when creating a service.
+ * @throws ContainerExceptionInterface If any other error occurs.
+ */
+ public function __invoke(ContainerInterface $container, $name, callable $callback, ?array $options = null);
+}
diff --git a/vendor/laminas/laminas-servicemanager/src/Factory/FactoryInterface.php b/vendor/laminas/laminas-servicemanager/src/Factory/FactoryInterface.php
new file mode 100644
index 00000000..68e61671
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/src/Factory/FactoryInterface.php
@@ -0,0 +1,33 @@
+ $options
+ * @return object
+ * @throws ServiceNotFoundException If unable to resolve the service.
+ * @throws ServiceNotCreatedException If an exception is raised when creating a service.
+ * @throws ContainerExceptionInterface If any other error occurs.
+ */
+ public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null);
+}
diff --git a/vendor/laminas/laminas-servicemanager/src/Factory/InvokableFactory.php b/vendor/laminas/laminas-servicemanager/src/Factory/InvokableFactory.php
new file mode 100644
index 00000000..6f80c894
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/src/Factory/InvokableFactory.php
@@ -0,0 +1,27 @@
+ $servicesMap A map of service names to
+ * class names of their respective classes
+ */
+ public function __construct(private LazyLoadingValueHolderFactory $proxyFactory, private array $servicesMap)
+ {
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @param string $name
+ * @return VirtualProxyInterface
+ */
+ public function __invoke(ContainerInterface $container, $name, callable $callback, ?array $options = null)
+ {
+ if (isset($this->servicesMap[$name])) {
+ $initializer = static function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($callback): bool {
+ $proxy->setProxyInitializer(null);
+ $wrappedInstance = $callback();
+
+ return true;
+ };
+
+ return $this->proxyFactory->createProxy($this->servicesMap[$name], $initializer);
+ }
+
+ throw new Exception\ServiceNotFoundException(
+ sprintf('The requested service "%s" was not found in the provided services map', $name)
+ );
+ }
+}
diff --git a/vendor/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php b/vendor/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php
new file mode 100644
index 00000000..3d4bd447
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php
@@ -0,0 +1,30 @@
+ $name
+ * @param null|array $options
+ * @return mixed
+ * @psalm-return ($name is class-string ? T : mixed)
+ * @throws Exception\ServiceNotFoundException If no factory/abstract
+ * factory could be found to create the instance.
+ * @throws Exception\ServiceNotCreatedException If factory/delegator fails
+ * to create the instance.
+ * @throws ContainerExceptionInterface If any other error occurs.
+ */
+ public function build($name, ?array $options = null);
+}
diff --git a/vendor/laminas/laminas-servicemanager/src/ServiceManager.php b/vendor/laminas/laminas-servicemanager/src/ServiceManager.php
new file mode 100644
index 00000000..d63e2631
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/src/ServiceManager.php
@@ -0,0 +1,1010 @@
+|Factory\AbstractFactoryInterface)
+ * >
+ * @psalm-type DelegatorsConfiguration = array<
+ * string,
+ * array<
+ * array-key,
+ * (class-string|Factory\DelegatorFactoryInterface)
+ * |callable(ContainerInterface,string,callable():object,array|null):object
+ * >
+ * >
+ * @psalm-type FactoriesConfiguration = array<
+ * string,
+ * (class-string|Factory\FactoryInterface)
+ * |callable(ContainerInterface,?string,?array|null):object
+ * >
+ * @psalm-type InitializersConfiguration = array<
+ * array-key,
+ * (class-string|Initializer\InitializerInterface)
+ * |callable(ContainerInterface,object):void
+ * >
+ * @psalm-type LazyServicesConfiguration = array{
+ * class_map?:array,
+ * proxies_namespace?:non-empty-string,
+ * proxies_target_dir?:non-empty-string,
+ * write_proxy_files?:bool
+ * }
+ * @psalm-type ServiceManagerConfiguration = array{
+ * abstract_factories?: AbstractFactoriesConfiguration,
+ * aliases?: array,
+ * delegators?: DelegatorsConfiguration,
+ * factories?: FactoriesConfiguration,
+ * initializers?: InitializersConfiguration,
+ * invokables?: array,
+ * lazy_services?: LazyServicesConfiguration,
+ * services?: array>,
+ * shared?:array,
+ * shared_by_default?:bool,
+ * ...
+ * }
+ */
+class ServiceManager implements ServiceLocatorInterface
+{
+ /** @var Factory\AbstractFactoryInterface[] */
+ protected $abstractFactories = [];
+
+ /**
+ * A list of aliases
+ *
+ * Should map one alias to a service name, or another alias (aliases are recursively resolved)
+ *
+ * @var string[]
+ */
+ protected $aliases = [];
+
+ /**
+ * Whether or not changes may be made to this instance.
+ *
+ * @var bool
+ */
+ protected $allowOverride = false;
+
+ /** @var ContainerInterface */
+ protected $creationContext;
+
+ /**
+ * @var string[][]|Factory\DelegatorFactoryInterface[][]
+ * @psalm-var DelegatorsConfiguration
+ */
+ protected $delegators = [];
+
+ /**
+ * A list of factories (either as string name or callable)
+ *
+ * @var string[]|callable[]
+ * @psalm-var FactoriesConfiguration
+ */
+ protected $factories = [];
+
+ /**
+ * @var Initializer\InitializerInterface[]|callable[]
+ * @psalm-var InitializersConfiguration
+ */
+ protected $initializers = [];
+
+ /**
+ * @var array
+ * @psalm-var LazyServicesConfiguration
+ */
+ protected $lazyServices = [];
+
+ private ?LazyServiceFactory $lazyServicesDelegator = null;
+
+ /**
+ * A list of already loaded services (this act as a local cache)
+ *
+ * @var array
+ */
+ protected $services = [];
+
+ /**
+ * Enable/disable shared instances by service name.
+ *
+ * Example configuration:
+ *
+ * 'shared' => [
+ * MyService::class => true, // will be shared, even if "sharedByDefault" is false
+ * MyOtherService::class => false // won't be shared, even if "sharedByDefault" is true
+ * ]
+ *
+ * @var array
+ */
+ protected $shared = [];
+
+ /**
+ * Should the services be shared by default?
+ *
+ * @var bool
+ */
+ protected $sharedByDefault = true;
+
+ /**
+ * Service manager was already configured?
+ *
+ * @var bool
+ */
+ protected $configured = false;
+
+ /**
+ * Cached abstract factories from string.
+ */
+ private array $cachedAbstractFactories = [];
+
+ /**
+ * See {@see \Laminas\ServiceManager\ServiceManager::configure()} for details
+ * on what $config accepts.
+ *
+ * @psalm-param ServiceManagerConfiguration $config
+ */
+ public function __construct(array $config = [])
+ {
+ $this->creationContext = $this;
+ $this->configure($config);
+ }
+
+ /**
+ * Implemented for backwards compatibility with previous plugin managers only.
+ *
+ * Returns the creation context.
+ *
+ * @deprecated since 3.0.0. Factories using 3.0 should use the container
+ * instance passed to the factory instead.
+ *
+ * @return ContainerInterface
+ */
+ public function getServiceLocator()
+ {
+ trigger_error(sprintf(
+ 'Usage of %s is deprecated since v3.0.0; please use the container passed to the factory instead',
+ __METHOD__
+ ), E_USER_DEPRECATED);
+ return $this->creationContext;
+ }
+
+ /** {@inheritDoc} */
+ public function get($name)
+ {
+ // We start by checking if we have cached the requested service;
+ // this is the fastest method.
+ if (isset($this->services[$name])) {
+ return $this->services[$name];
+ }
+
+ // Determine if the service should be shared.
+ $sharedService = $this->shared[$name] ?? $this->sharedByDefault;
+
+ // We achieve better performance if we can let all alias
+ // considerations out.
+ if (! $this->aliases) {
+ $object = $this->doCreate($name);
+
+ // Cache the object for later, if it is supposed to be shared.
+ if ($sharedService) {
+ $this->services[$name] = $object;
+ }
+ return $object;
+ }
+
+ // We now deal with requests which may be aliases.
+ $resolvedName = $this->aliases[$name] ?? $name;
+
+ // Update shared service information as we checked if the alias was shared before.
+ if ($resolvedName !== $name) {
+ $sharedService = $this->shared[$resolvedName] ?? $sharedService;
+ }
+
+ // The following is only true if the requested service is a shared alias.
+ $sharedAlias = $sharedService && isset($this->services[$resolvedName]);
+
+ // If the alias is configured as a shared service, we are done.
+ if ($sharedAlias) {
+ $this->services[$name] = $this->services[$resolvedName];
+ return $this->services[$resolvedName];
+ }
+
+ // At this point, we have to create the object.
+ // We use the resolved name for that.
+ $object = $this->doCreate($resolvedName);
+
+ // Cache the object for later, if it is supposed to be shared.
+ if ($sharedService) {
+ $this->services[$resolvedName] = $object;
+ }
+
+ // Also cache under the alias name; this allows sharing based on the
+ // service name used.
+ if ($sharedAlias) {
+ $this->services[$name] = $object;
+ }
+
+ return $object;
+ }
+
+ /** {@inheritDoc} */
+ public function build($name, ?array $options = null)
+ {
+ // We never cache when using "build".
+ $name = $this->aliases[$name] ?? $name;
+ return $this->doCreate($name, $options);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @param string|class-string $name
+ * @return bool
+ */
+ public function has($name)
+ {
+ // Check static services and factories first to speedup the most common requests.
+ return $this->staticServiceOrFactoryCanCreate($name) || $this->abstractFactoryCanCreate($name);
+ }
+
+ /**
+ * Indicate whether or not the instance is immutable.
+ *
+ * @param bool $flag
+ */
+ public function setAllowOverride($flag)
+ {
+ $this->allowOverride = (bool) $flag;
+ }
+
+ /**
+ * Retrieve the flag indicating immutability status.
+ *
+ * @return bool
+ */
+ public function getAllowOverride()
+ {
+ return $this->allowOverride;
+ }
+
+ /**
+ * @psalm-param ServiceManagerConfiguration $config
+ * @return self
+ * @throws ContainerModificationsNotAllowedException If the allow
+ * override flag has been toggled off, and a service instance
+ * exists for a given service.
+ */
+ public function configure(array $config)
+ {
+ // This is a bulk update/initial configuration,
+ // so we check all definitions up front.
+ $this->validateServiceNames($config);
+
+ if (isset($config['services'])) {
+ $this->services = $config['services'] + $this->services;
+ }
+
+ if (isset($config['invokables']) && ! empty($config['invokables'])) {
+ $newAliases = $this->createAliasesAndFactoriesForInvokables($config['invokables']);
+ // override existing aliases with those created by invokables to ensure
+ // that they are still present after merging aliases later on
+ $config['aliases'] = $newAliases + ($config['aliases'] ?? []);
+ }
+
+ if (isset($config['factories'])) {
+ $this->factories = $config['factories'] + $this->factories;
+ }
+
+ if (isset($config['delegators'])) {
+ $this->mergeDelegators($config['delegators']);
+ }
+
+ if (isset($config['shared'])) {
+ $this->shared = $config['shared'] + $this->shared;
+ }
+
+ if (! empty($config['aliases'])) {
+ $this->aliases = $config['aliases'] + $this->aliases;
+ $this->mapAliasesToTargets();
+ } elseif (! $this->configured && ! empty($this->aliases)) {
+ $this->mapAliasesToTargets();
+ }
+
+ if (isset($config['shared_by_default'])) {
+ $this->sharedByDefault = $config['shared_by_default'];
+ }
+
+ // If lazy service configuration was provided, reset the lazy services
+ // delegator factory.
+ if (isset($config['lazy_services']) && ! empty($config['lazy_services'])) {
+ /** @psalm-suppress MixedPropertyTypeCoercion */
+ $this->lazyServices = ArrayUtils::merge($this->lazyServices, $config['lazy_services']);
+ $this->lazyServicesDelegator = null;
+ }
+
+ // For abstract factories and initializers, we always directly
+ // instantiate them to avoid checks during service construction.
+ if (isset($config['abstract_factories'])) {
+ $abstractFactories = $config['abstract_factories'];
+ // $key not needed, but foreach is faster than foreach + array_values.
+ foreach ($abstractFactories as $key => $abstractFactory) {
+ $this->resolveAbstractFactoryInstance($abstractFactory);
+ }
+ }
+
+ if (isset($config['initializers'])) {
+ $this->resolveInitializers($config['initializers']);
+ }
+
+ $this->configured = true;
+
+ return $this;
+ }
+
+ /**
+ * Add an alias.
+ *
+ * @param string $alias
+ * @param string $target
+ * @throws ContainerModificationsNotAllowedException If $alias already
+ * exists as a service and overrides are disallowed.
+ */
+ public function setAlias($alias, $target)
+ {
+ if (isset($this->services[$alias]) && ! $this->allowOverride) {
+ throw ContainerModificationsNotAllowedException::fromExistingService($alias);
+ }
+
+ $this->mapAliasToTarget($alias, $target);
+ }
+
+ /**
+ * Add an invokable class mapping.
+ *
+ * @param string $name Service name
+ * @param null|string $class Class to which to map; if omitted, $name is
+ * assumed.
+ * @throws ContainerModificationsNotAllowedException If $name already
+ * exists as a service and overrides are disallowed.
+ */
+ public function setInvokableClass($name, $class = null)
+ {
+ if (isset($this->services[$name]) && ! $this->allowOverride) {
+ throw ContainerModificationsNotAllowedException::fromExistingService($name);
+ }
+
+ $this->createAliasesAndFactoriesForInvokables([$name => $class ?? $name]);
+ }
+
+ /**
+ * Specify a factory for a given service name.
+ *
+ * @param string $name Service name
+ * @param string|callable|Factory\FactoryInterface $factory Factory to which to map.
+ * phpcs:disable Generic.Files.LineLength.TooLong
+ * @psalm-param class-string|callable(ContainerInterface,string,array|null):object|Factory\FactoryInterface $factory
+ * phpcs:enable Generic.Files.LineLength.TooLong
+ * @return void
+ * @throws ContainerModificationsNotAllowedException If $name already
+ * exists as a service and overrides are disallowed.
+ */
+ public function setFactory($name, $factory)
+ {
+ if (isset($this->services[$name]) && ! $this->allowOverride) {
+ throw ContainerModificationsNotAllowedException::fromExistingService($name);
+ }
+
+ $this->factories[$name] = $factory;
+ }
+
+ /**
+ * Create a lazy service mapping to a class.
+ *
+ * @param string $name Service name to map
+ * @param null|string $class Class to which to map; if not provided, $name
+ * will be used for the mapping.
+ */
+ public function mapLazyService($name, $class = null)
+ {
+ $this->configure(['lazy_services' => ['class_map' => [$name => $class ?: $name]]]);
+ }
+
+ /**
+ * Add an abstract factory for resolving services.
+ *
+ * @param string|Factory\AbstractFactoryInterface $factory Abstract factory
+ * instance or class name.
+ * @psalm-param class-string|Factory\AbstractFactoryInterface $factory
+ */
+ public function addAbstractFactory($factory)
+ {
+ $this->resolveAbstractFactoryInstance($factory);
+ }
+
+ /**
+ * Add a delegator for a given service.
+ *
+ * @param string $name Service name
+ * @param string|callable|Factory\DelegatorFactoryInterface $factory Delegator
+ * factory to assign.
+ * @psalm-param class-string
+ * |callable(ContainerInterface,string,callable,array|null) $factory
+ */
+ public function addDelegator($name, $factory)
+ {
+ $this->configure(['delegators' => [$name => [$factory]]]);
+ }
+
+ /**
+ * Add an initializer.
+ *
+ * @param string|callable|Initializer\InitializerInterface $initializer
+ * @psalm-param class-string
+ * |callable(ContainerInterface,mixed):void
+ * |Initializer\InitializerInterface $initializer
+ */
+ public function addInitializer($initializer)
+ {
+ $this->configure(['initializers' => [$initializer]]);
+ }
+
+ /**
+ * Map a service.
+ *
+ * @param string $name Service name
+ * @param array|object $service
+ * @throws ContainerModificationsNotAllowedException If $name already
+ * exists as a service and overrides are disallowed.
+ */
+ public function setService($name, $service)
+ {
+ if (isset($this->services[$name]) && ! $this->allowOverride) {
+ throw ContainerModificationsNotAllowedException::fromExistingService($name);
+ }
+ $this->services[$name] = $service;
+ }
+
+ /**
+ * Add a service sharing rule.
+ *
+ * @param string $name Service name
+ * @param bool $flag Whether or not the service should be shared.
+ * @throws ContainerModificationsNotAllowedException If $name already
+ * exists as a service and overrides are disallowed.
+ */
+ public function setShared($name, $flag)
+ {
+ if (isset($this->services[$name]) && ! $this->allowOverride) {
+ throw ContainerModificationsNotAllowedException::fromExistingService($name);
+ }
+
+ $this->shared[$name] = (bool) $flag;
+ }
+
+ /**
+ * Instantiate initializers for to avoid checks during service construction.
+ *
+ * @psalm-param InitializersConfiguration $initializers
+ */
+ private function resolveInitializers(array $initializers): void
+ {
+ foreach ($initializers as $initializer) {
+ if (is_string($initializer) && class_exists($initializer)) {
+ $initializer = new $initializer();
+ }
+
+ if (is_callable($initializer)) {
+ $this->initializers[] = $initializer;
+ continue;
+ }
+
+ throw InvalidArgumentException::fromInvalidInitializer($initializer);
+ }
+ }
+
+ /**
+ * Get a factory for the given service name
+ *
+ * @psalm-return (callable(ContainerInterface,string,array|null):object)|Factory\FactoryInterface
+ * @throws ServiceNotFoundException
+ */
+ private function getFactory(string $name): callable
+ {
+ $factory = $this->factories[$name] ?? null;
+
+ $lazyLoaded = false;
+ if (is_string($factory) && class_exists($factory)) {
+ $factory = new $factory();
+ $lazyLoaded = true;
+ }
+
+ if (is_callable($factory)) {
+ if ($lazyLoaded) {
+ $this->factories[$name] = $factory;
+ }
+
+ return $factory;
+ }
+
+ // Check abstract factories
+ foreach ($this->abstractFactories as $abstractFactory) {
+ if ($abstractFactory->canCreate($this->creationContext, $name)) {
+ return $abstractFactory;
+ }
+ }
+
+ throw new ServiceNotFoundException(sprintf(
+ 'Unable to resolve service "%s" to a factory; are you certain you provided it during configuration?',
+ $name
+ ));
+ }
+
+ /**
+ * @return object
+ */
+ private function createDelegatorFromName(string $name, ?array $options = null)
+ {
+ $creationCallback = function () use ($name, $options) {
+ // Code is inlined for performance reason, instead of abstracting the creation
+ $factory = $this->getFactory($name);
+ return $factory($this->creationContext, $name, $options);
+ };
+
+ $initialCreationContext = $this->creationContext;
+
+ foreach ($this->delegators[$name] as $index => $delegatorFactory) {
+ $delegatorFactory = $this->delegators[$name][$index];
+
+ if ($delegatorFactory === LazyServiceFactory::class) {
+ $delegatorFactory = $this->createLazyServiceDelegatorFactory();
+ } elseif (is_string($delegatorFactory) && class_exists($delegatorFactory)) {
+ $delegatorFactory = new $delegatorFactory();
+ }
+
+ $this->assertCallableDelegatorFactory($delegatorFactory);
+
+ $this->delegators[$name][$index] = $delegatorFactory;
+
+ $creationCallback =
+ /** @return object */
+ static fn() => $delegatorFactory($initialCreationContext, $name, $creationCallback, $options);
+ }
+
+ return $creationCallback();
+ }
+
+ /**
+ * Create a new instance with an already resolved name
+ *
+ * This is a highly performance sensitive method, do not modify if you have not benchmarked it carefully
+ *
+ * @return object
+ * @throws ServiceNotFoundException If unable to resolve the service.
+ * @throws ServiceNotCreatedException If an exception is raised when creating a service.
+ * @throws ContainerExceptionInterface If any other error occurs.
+ */
+ private function doCreate(string $resolvedName, ?array $options = null)
+ {
+ try {
+ if (! isset($this->delegators[$resolvedName])) {
+ // Let's create the service by fetching the factory
+ $factory = $this->getFactory($resolvedName);
+ $object = $factory($this->creationContext, $resolvedName, $options);
+ } else {
+ $object = $this->createDelegatorFromName($resolvedName, $options);
+ }
+ } catch (ContainerExceptionInterface $exception) {
+ throw $exception;
+ } catch (Exception $exception) {
+ throw new ServiceNotCreatedException(sprintf(
+ 'Service with name "%s" could not be created. Reason: %s',
+ $resolvedName,
+ $exception->getMessage()
+ ), (int) $exception->getCode(), $exception);
+ }
+
+ foreach ($this->initializers as $initializer) {
+ $initializer($this->creationContext, $object);
+ }
+
+ return $object;
+ }
+
+ /**
+ * Create the lazy services delegator factory.
+ *
+ * Creates the lazy services delegator factory based on the lazy_services
+ * configuration present.
+ *
+ * @throws ServiceNotCreatedException When the lazy service class_map configuration is missing.
+ */
+ private function createLazyServiceDelegatorFactory(): LazyServiceFactory
+ {
+ if ($this->lazyServicesDelegator) {
+ return $this->lazyServicesDelegator;
+ }
+
+ if (! isset($this->lazyServices['class_map'])) {
+ throw new ServiceNotCreatedException('Missing "class_map" config key in "lazy_services"');
+ }
+
+ $factoryConfig = new ProxyConfiguration();
+
+ if (isset($this->lazyServices['proxies_namespace'])) {
+ $factoryConfig->setProxiesNamespace($this->lazyServices['proxies_namespace']);
+ }
+
+ if (isset($this->lazyServices['proxies_target_dir'])) {
+ $factoryConfig->setProxiesTargetDir($this->lazyServices['proxies_target_dir']);
+ }
+
+ if (! isset($this->lazyServices['write_proxy_files']) || ! $this->lazyServices['write_proxy_files']) {
+ $factoryConfig->setGeneratorStrategy(new EvaluatingGeneratorStrategy());
+ } else {
+ $factoryConfig->setGeneratorStrategy(new FileWriterGeneratorStrategy(
+ new FileLocator($factoryConfig->getProxiesTargetDir())
+ ));
+ }
+
+ spl_autoload_register($factoryConfig->getProxyAutoloader());
+
+ $this->lazyServicesDelegator = new LazyServiceFactory(
+ new LazyLoadingValueHolderFactory($factoryConfig),
+ $this->lazyServices['class_map']
+ );
+
+ return $this->lazyServicesDelegator;
+ }
+
+ /**
+ * Merge delegators avoiding multiple same delegators for the same service.
+ * It works with strings and class instances.
+ * It's not possible to de-duple anonymous functions
+ *
+ * @psalm-param DelegatorsConfiguration $config
+ * @psalm-return DelegatorsConfiguration
+ */
+ private function mergeDelegators(array $config): array
+ {
+ foreach ($config as $key => $delegators) {
+ if (! array_key_exists($key, $this->delegators)) {
+ $this->delegators[$key] = $delegators;
+ continue;
+ }
+
+ foreach ($delegators as $delegator) {
+ if (! in_array($delegator, $this->delegators[$key], true)) {
+ $this->delegators[$key][] = $delegator;
+ }
+ }
+ }
+
+ return $this->delegators;
+ }
+
+ /**
+ * Create aliases and factories for invokable classes.
+ *
+ * If an invokable service name does not match the class it maps to, this
+ * creates an alias to the class (which will later be mapped as an
+ * invokable factory). The newly created aliases will be returned as an array.
+ *
+ * @param array $invokables
+ * @return array
+ */
+ private function createAliasesAndFactoriesForInvokables(array $invokables): array
+ {
+ $newAliases = [];
+
+ foreach ($invokables as $name => $class) {
+ $this->factories[$class] = Factory\InvokableFactory::class;
+ if ($name !== $class) {
+ $this->aliases[$name] = $class;
+ $newAliases[$name] = $class;
+ }
+ }
+
+ return $newAliases;
+ }
+
+ /**
+ * Determine if a service for any name provided by a service
+ * manager configuration(services, aliases, factories, ...)
+ * already exists, and if it exists, determine if is it allowed
+ * to get overriden.
+ *
+ * Validation in the context of this class means, that for
+ * a given service name we do not have a service instance
+ * in the cache OR override is explicitly allowed.
+ *
+ * @psalm-param ServiceManagerConfiguration $config
+ * @throws ContainerModificationsNotAllowedException If any
+ * service key is invalid.
+ */
+ private function validateServiceNames(array $config): void
+ {
+ if ($this->allowOverride || ! $this->configured) {
+ return;
+ }
+
+ if (isset($config['services'])) {
+ foreach (array_keys($config['services']) as $service) {
+ if (isset($this->services[$service])) {
+ throw ContainerModificationsNotAllowedException::fromExistingService($service);
+ }
+ }
+ }
+
+ if (isset($config['aliases'])) {
+ foreach (array_keys($config['aliases']) as $service) {
+ if (isset($this->services[$service])) {
+ throw ContainerModificationsNotAllowedException::fromExistingService($service);
+ }
+ }
+ }
+
+ if (isset($config['invokables'])) {
+ foreach (array_keys($config['invokables']) as $service) {
+ if (isset($this->services[$service])) {
+ throw ContainerModificationsNotAllowedException::fromExistingService($service);
+ }
+ }
+ }
+
+ if (isset($config['factories'])) {
+ foreach (array_keys($config['factories']) as $service) {
+ if (isset($this->services[$service])) {
+ throw ContainerModificationsNotAllowedException::fromExistingService($service);
+ }
+ }
+ }
+
+ if (isset($config['delegators'])) {
+ foreach (array_keys($config['delegators']) as $service) {
+ if (isset($this->services[$service])) {
+ throw ContainerModificationsNotAllowedException::fromExistingService($service);
+ }
+ }
+ }
+
+ if (isset($config['shared'])) {
+ foreach (array_keys($config['shared']) as $service) {
+ if (isset($this->services[$service])) {
+ throw ContainerModificationsNotAllowedException::fromExistingService($service);
+ }
+ }
+ }
+
+ if (isset($config['lazy_services']['class_map'])) {
+ foreach (array_keys($config['lazy_services']['class_map']) as $service) {
+ if (isset($this->services[$service])) {
+ throw ContainerModificationsNotAllowedException::fromExistingService($service);
+ }
+ }
+ }
+ }
+
+ /**
+ * Assuming that the alias name is valid (see above) resolve/add it.
+ *
+ * This is done differently from bulk mapping aliases for performance reasons, as the
+ * algorithms for mapping a single item efficiently are different from those of mapping
+ * many.
+ */
+ private function mapAliasToTarget(string $alias, string $target): void
+ {
+ // $target is either an alias or something else
+ // if it is an alias, resolve it
+ $this->aliases[$alias] = $this->aliases[$target] ?? $target;
+
+ // a self-referencing alias indicates a cycle
+ if ($alias === $this->aliases[$alias]) {
+ throw CyclicAliasException::fromCyclicAlias($alias, $this->aliases);
+ }
+
+ // finally we have to check if existing incomplete alias definitions
+ // exist which can get resolved by the new alias
+ if (in_array($alias, $this->aliases)) {
+ $r = array_intersect($this->aliases, [$alias]);
+ // found some, resolve them
+ foreach ($r as $name => $service) {
+ $this->aliases[$name] = $target;
+ }
+ }
+ }
+
+ /**
+ * Assuming that all provided alias keys are valid resolve them.
+ *
+ * This function maps $this->aliases in place.
+ *
+ * This algorithm is an adaptated version of Tarjans Strongly
+ * Connected Components. Instead of returning the strongly
+ * connected components (i.e. cycles in our case), we throw.
+ * If nodes are not strongly connected (i.e. resolvable in
+ * our case), they get resolved.
+ *
+ * This algorithm is fast for mass updates through configure().
+ * It is not appropriate if just a single alias is added.
+ *
+ * @see mapAliasToTarget above
+ */
+ private function mapAliasesToTargets(): void
+ {
+ $tagged = [];
+ foreach ($this->aliases as $alias => $target) {
+ if (isset($tagged[$alias])) {
+ continue;
+ }
+
+ $tCursor = $this->aliases[$alias];
+ $aCursor = $alias;
+ if ($aCursor === $tCursor) {
+ throw CyclicAliasException::fromCyclicAlias($alias, $this->aliases);
+ }
+ if (! isset($this->aliases[$tCursor])) {
+ continue;
+ }
+
+ $stack = [];
+
+ while (isset($this->aliases[$tCursor])) {
+ $stack[] = $aCursor;
+ if ($aCursor === $this->aliases[$tCursor]) {
+ throw CyclicAliasException::fromCyclicAlias($alias, $this->aliases);
+ }
+ $aCursor = $tCursor;
+ $tCursor = $this->aliases[$tCursor];
+ }
+
+ $tagged[$aCursor] = true;
+
+ foreach ($stack as $alias) {
+ if ($alias === $tCursor) {
+ throw CyclicAliasException::fromCyclicAlias($alias, $this->aliases);
+ }
+ $this->aliases[$alias] = $tCursor;
+ $tagged[$alias] = true;
+ }
+ }
+ }
+
+ /**
+ * Instantiate abstract factories in order to avoid checks during service construction.
+ *
+ * @param string|Factory\AbstractFactoryInterface $abstractFactory
+ * @psalm-param class-string|Factory\AbstractFactoryInterface $abstractFactory
+ */
+ private function resolveAbstractFactoryInstance($abstractFactory): void
+ {
+ if (is_string($abstractFactory) && class_exists($abstractFactory)) {
+ // Cached string factory name
+ if (! isset($this->cachedAbstractFactories[$abstractFactory])) {
+ $this->cachedAbstractFactories[$abstractFactory] = new $abstractFactory();
+ }
+
+ $abstractFactory = $this->cachedAbstractFactories[$abstractFactory];
+ }
+
+ if (! $abstractFactory instanceof Factory\AbstractFactoryInterface) {
+ throw InvalidArgumentException::fromInvalidAbstractFactory($abstractFactory);
+ }
+
+ $abstractFactoryObjHash = spl_object_hash($abstractFactory);
+ $this->abstractFactories[$abstractFactoryObjHash] = $abstractFactory;
+ }
+
+ /**
+ * Check if a static service or factory exists for the given name.
+ */
+ private function staticServiceOrFactoryCanCreate(string $name): bool
+ {
+ if (isset($this->services[$name]) || isset($this->factories[$name])) {
+ return true;
+ }
+
+ $resolvedName = $this->aliases[$name] ?? $name;
+ if ($resolvedName !== $name) {
+ return $this->staticServiceOrFactoryCanCreate($resolvedName);
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if an abstract factory exists that can create a service for the given name.
+ */
+ private function abstractFactoryCanCreate(string $name): bool
+ {
+ foreach ($this->abstractFactories as $abstractFactory) {
+ if ($abstractFactory->canCreate($this->creationContext, $name)) {
+ return true;
+ }
+ }
+
+ $resolvedName = $this->aliases[$name] ?? $name;
+ if ($resolvedName !== $name) {
+ return $this->abstractFactoryCanCreate($resolvedName);
+ }
+
+ return false;
+ }
+
+ /**
+ * @psalm-param mixed $delegatorFactory
+ * @psalm-assert callable(ContainerInterface,string,callable():object,array|null):object $delegatorFactory
+ */
+ private function assertCallableDelegatorFactory($delegatorFactory): void
+ {
+ if (
+ $delegatorFactory instanceof Factory\DelegatorFactoryInterface
+ || is_callable($delegatorFactory)
+ ) {
+ return;
+ }
+ if (is_string($delegatorFactory)) {
+ throw new ServiceNotCreatedException(sprintf(
+ 'An invalid delegator factory was registered; resolved to class or function "%s"'
+ . ' which does not exist; please provide a valid function name or class name resolving'
+ . ' to an implementation of %s',
+ $delegatorFactory,
+ DelegatorFactoryInterface::class
+ ));
+ }
+ throw new ServiceNotCreatedException(sprintf(
+ 'A non-callable delegator, "%s", was provided; expected a callable or instance of "%s"',
+ is_object($delegatorFactory) ? $delegatorFactory::class : gettype($delegatorFactory),
+ DelegatorFactoryInterface::class
+ ));
+ }
+}
diff --git a/vendor/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php b/vendor/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php
new file mode 100644
index 00000000..7534de4a
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/src/Test/CommonPluginManagerTrait.php
@@ -0,0 +1,120 @@
+getPluginManager();
+ $reflection = new ReflectionProperty($manager, 'instanceOf');
+ $this->assertEquals($this->getInstanceOf(), $reflection->getValue($manager), 'instanceOf does not match');
+ }
+
+ public function testShareByDefaultAndSharedByDefault()
+ {
+ $manager = $this->getPluginManager();
+ $reflection = new ReflectionClass($manager);
+ $shareByDefault = $sharedByDefault = true;
+
+ foreach ($reflection->getProperties() as $prop) {
+ if ($prop->getName() === 'shareByDefault') {
+ $shareByDefault = $prop->getValue($manager);
+ }
+ if ($prop->getName() === 'sharedByDefault') {
+ $sharedByDefault = $prop->getValue($manager);
+ }
+ }
+
+ $this->assertSame(
+ $shareByDefault,
+ $sharedByDefault,
+ 'Values of shareByDefault and sharedByDefault do not match'
+ );
+ }
+
+ public function testRegisteringInvalidElementRaisesException()
+ {
+ $this->expectException($this->getServiceNotFoundException());
+ $this->getPluginManager()->setService('test', $this);
+ }
+
+ public function testLoadingInvalidElementRaisesException()
+ {
+ $manager = $this->getPluginManager();
+ $manager->setInvokableClass('test', stdClass::class);
+ $this->expectException($this->getServiceNotFoundException());
+ $manager->get('test');
+ }
+
+ /**
+ * @dataProvider aliasProvider
+ * @param string $alias
+ * @param string $expected
+ */
+ public function testPluginAliasesResolve($alias, $expected)
+ {
+ $this->assertInstanceOf($expected, $this->getPluginManager()->get($alias), "Alias '$alias' does not resolve'");
+ }
+
+ /**
+ * @return array
+ */
+ public static function aliasProvider(): array
+ {
+ $manager = self::getPluginManager();
+ $reflection = new ReflectionProperty($manager, 'aliases');
+ $data = [];
+ foreach ($reflection->getValue($manager) as $alias => $expected) {
+ $data[] = [$alias, $expected];
+ }
+ return $data;
+ }
+
+ protected function getServiceNotFoundException(): string
+ {
+ $manager = $this->getPluginManager();
+ if (method_exists($manager, 'configure')) {
+ return InvalidServiceException::class;
+ }
+ return $this->getV2InvalidPluginException();
+ }
+
+ /**
+ * Returns the plugin manager to test
+ *
+ * @return AbstractPluginManager
+ */
+ abstract protected static function getPluginManager();
+
+ /**
+ * Returns the FQCN of the exception thrown under v2 by `validatePlugin()`
+ *
+ * @return mixed
+ */
+ abstract protected function getV2InvalidPluginException();
+
+ /**
+ * Returns the value the instanceOf property has been set to
+ *
+ * @return string
+ */
+ abstract protected function getInstanceOf();
+}
diff --git a/vendor/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php b/vendor/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php
new file mode 100644
index 00000000..6dd6b7f6
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php
@@ -0,0 +1,255 @@
+validateClassName($className);
+
+ $reflectionClass = new ReflectionClass($className);
+
+ // class is an interface; do nothing
+ if ($reflectionClass->isInterface()) {
+ return $config;
+ }
+
+ // class has no constructor, treat it as an invokable
+ if (! $reflectionClass->getConstructor()) {
+ return $this->createInvokable($config, $className);
+ }
+
+ $constructorArguments = $reflectionClass->getConstructor()->getParameters();
+ $constructorArguments = array_filter(
+ $constructorArguments,
+ static fn(ReflectionParameter $argument): bool => ! $argument->isOptional()
+ );
+
+ // has no required parameters, treat it as an invokable
+ if (empty($constructorArguments)) {
+ return $this->createInvokable($config, $className);
+ }
+
+ $classConfig = [];
+
+ foreach ($constructorArguments as $constructorArgument) {
+ $type = $constructorArgument->getType();
+ $argumentType = $type instanceof ReflectionNamedType && ! $type->isBuiltin() ? $type->getName() : null;
+
+ if ($argumentType === null) {
+ if ($ignoreUnresolved) {
+ // don't throw an exception, just return the previous config
+ return $config;
+ }
+ // don't throw an exception if the class is an already defined service
+ if ($this->container && $this->container->has($className)) {
+ return $config;
+ }
+ throw new InvalidArgumentException(sprintf(
+ 'Cannot create config for constructor argument "%s", '
+ . 'it has no type hint, or non-class/interface type hint',
+ $constructorArgument->getName()
+ ));
+ }
+ $config = $this->createDependencyConfig($config, $argumentType, $ignoreUnresolved);
+ $classConfig[] = $argumentType;
+ }
+
+ $config[ConfigAbstractFactory::class][$className] = $classConfig;
+
+ return $config;
+ }
+
+ /**
+ * @param string $className
+ * @throws InvalidArgumentException If class name is not a string or does
+ * not exist.
+ */
+ private function validateClassName($className)
+ {
+ if (! is_string($className)) {
+ throw new InvalidArgumentException('Class name must be a string, ' . gettype($className) . ' given');
+ }
+
+ if (! class_exists($className) && ! interface_exists($className)) {
+ throw new InvalidArgumentException('Cannot find class or interface with name ' . $className);
+ }
+ }
+
+ /**
+ * @param string $className
+ * @return array
+ */
+ private function createInvokable(array $config, $className)
+ {
+ $config[ConfigAbstractFactory::class][$className] = [];
+ return $config;
+ }
+
+ /**
+ * @return array
+ * @throws InvalidArgumentException If ConfigAbstractFactory configuration
+ * value is not an array.
+ */
+ public function createFactoryMappingsFromConfig(array $config)
+ {
+ if (! array_key_exists(ConfigAbstractFactory::class, $config)) {
+ return $config;
+ }
+
+ if (! is_array($config[ConfigAbstractFactory::class])) {
+ throw new InvalidArgumentException(
+ 'Config key for ' . ConfigAbstractFactory::class . ' should be an array, ' . gettype(
+ $config[ConfigAbstractFactory::class]
+ ) . ' given'
+ );
+ }
+
+ foreach ($config[ConfigAbstractFactory::class] as $className => $dependency) {
+ $config = $this->createFactoryMappings($config, $className);
+ }
+ return $config;
+ }
+
+ /**
+ * @param string $className
+ * @return array
+ */
+ public function createFactoryMappings(array $config, $className)
+ {
+ $this->validateClassName($className);
+
+ if (
+ array_key_exists('service_manager', $config)
+ && array_key_exists('factories', $config['service_manager'])
+ && array_key_exists($className, $config['service_manager']['factories'])
+ ) {
+ return $config;
+ }
+
+ $config['service_manager']['factories'][$className] = ConfigAbstractFactory::class;
+ return $config;
+ }
+
+ /**
+ * @return string
+ */
+ public function dumpConfigFile(array $config)
+ {
+ $prepared = $this->prepareConfig($config);
+ return sprintf(
+ self::CONFIG_TEMPLATE,
+ static::class,
+ date('Y-m-d H:i:s'),
+ $prepared
+ );
+ }
+
+ /**
+ * @param array|Traversable $config
+ * @param int $indentLevel
+ * @return string
+ */
+ private function prepareConfig($config, $indentLevel = 1)
+ {
+ $indent = str_repeat(' ', $indentLevel * 4);
+ $entries = [];
+ foreach ($config as $key => $value) {
+ $key = $this->createConfigKey($key);
+ $entries[] = sprintf(
+ '%s%s%s,',
+ $indent,
+ $key ? sprintf('%s => ', $key) : '',
+ $this->createConfigValue($value, $indentLevel)
+ );
+ }
+
+ $outerIndent = str_repeat(' ', ($indentLevel - 1) * 4);
+
+ return sprintf(
+ "[\n%s\n%s]",
+ implode("\n", $entries),
+ $outerIndent
+ );
+ }
+
+ /**
+ * @param string|int|null $key
+ * @return null|string
+ */
+ private function createConfigKey($key)
+ {
+ if (is_string($key) && class_exists($key)) {
+ return sprintf('\\%s::class', $key);
+ }
+
+ if (is_int($key)) {
+ return null;
+ }
+
+ return sprintf("'%s'", $key);
+ }
+
+ /**
+ * @param int $indentLevel
+ * @return string
+ */
+ private function createConfigValue(mixed $value, $indentLevel)
+ {
+ if (is_array($value) || $value instanceof Traversable) {
+ return $this->prepareConfig($value, $indentLevel + 1);
+ }
+
+ if (is_string($value) && class_exists($value)) {
+ return sprintf('\\%s::class', $value);
+ }
+
+ return var_export($value, true);
+ }
+}
diff --git a/vendor/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php b/vendor/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php
new file mode 100644
index 00000000..f9747868
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php
@@ -0,0 +1,244 @@
+,
+ * class: string,
+ * ignoreUnresolved: bool
+ * }
+ */
+class ConfigDumperCommand
+{
+ public const COMMAND_DUMP = 'dump';
+ public const COMMAND_ERROR = 'error';
+ public const COMMAND_HELP = 'help';
+
+ public const DEFAULT_SCRIPT_NAME = self::class;
+
+ public const HELP_TEMPLATE = <<Usage:
+
+ %s [-h|--help|help] [-i|--ignore-unresolved]
+
+Arguments:
+
+ -h|--help|help This usage message
+ -i|--ignore-unresolved Ignore classes with unresolved direct dependencies.
+ Path to a config file for which to generate
+ configuration. If the file does not exist, it will
+ be created. If it does exist, it must return an
+ array, and the file will be updated with new
+ configuration.
+ Name of the class to reflect and for which to
+ generate dependency configuration.
+
+Reads the provided configuration file (creating it if it does not exist),
+and injects it with ConfigAbstractFactory dependency configuration for
+the provided class name, writing the changes back to the file.
+EOH;
+
+ private ConsoleHelper $helper;
+
+ /**
+ * @param string $scriptName
+ */
+ public function __construct(private $scriptName = self::DEFAULT_SCRIPT_NAME, ?ConsoleHelper $helper = null)
+ {
+ $this->helper = $helper ?: new ConsoleHelper();
+ }
+
+ /**
+ * @param array $args Argument list, minus script name
+ * @return int Exit status
+ */
+ public function __invoke(array $args)
+ {
+ $arguments = $this->parseArgs($args);
+
+ switch ($arguments->command) {
+ case self::COMMAND_HELP:
+ $this->help();
+ return 0;
+ case self::COMMAND_ERROR:
+ $this->helper->writeErrorMessage($arguments->message);
+ $this->help(STDERR);
+ return 1;
+ case self::COMMAND_DUMP:
+ // fall-through
+ default:
+ break;
+ }
+
+ $dumper = new ConfigDumper();
+ try {
+ $config = $dumper->createDependencyConfig(
+ $arguments->config,
+ $arguments->class,
+ $arguments->ignoreUnresolved
+ );
+ } catch (Exception\InvalidArgumentException $e) {
+ $this->helper->writeErrorMessage(sprintf(
+ 'Unable to create config for "%s": %s',
+ $arguments->class,
+ $e->getMessage()
+ ));
+ $this->help(STDERR);
+ return 1;
+ }
+
+ file_put_contents($arguments->configFile, $dumper->dumpConfigFile($config));
+
+ $this->helper->writeLine(sprintf(
+ '[DONE] Changes written to %s',
+ $arguments->configFile
+ ));
+ return 0;
+ }
+
+ /**
+ * @return object
+ */
+ private function parseArgs(array $args)
+ {
+ if (! $args) {
+ return $this->createHelpArgument();
+ }
+
+ $arg1 = array_shift($args);
+
+ if (in_array($arg1, ['-h', '--help', 'help'], true)) {
+ return $this->createHelpArgument();
+ }
+
+ $ignoreUnresolved = false;
+ if (in_array($arg1, ['-i', '--ignore-unresolved'], true)) {
+ $ignoreUnresolved = true;
+ $arg1 = array_shift($args);
+ }
+
+ if (! $args) {
+ return $this->createErrorArgument('Missing class name');
+ }
+
+ $configFile = $arg1;
+ switch (file_exists($configFile)) {
+ case true:
+ $config = require $configFile;
+
+ if (! is_array($config)) {
+ return $this->createErrorArgument(sprintf(
+ 'Configuration at path "%s" does not return an array.',
+ $configFile
+ ));
+ }
+
+ break;
+ case false:
+ // fall-through
+ default:
+ if (! is_writable(dirname($configFile))) {
+ return $this->createErrorArgument(sprintf(
+ 'Cannot create configuration at path "%s"; not writable.',
+ $configFile
+ ));
+ }
+
+ $config = [];
+ break;
+ }
+
+ $class = array_shift($args);
+
+ if (! class_exists($class)) {
+ return $this->createErrorArgument(sprintf(
+ 'Class "%s" does not exist or could not be autoloaded.',
+ $class
+ ));
+ }
+
+ return $this->createArguments(self::COMMAND_DUMP, $configFile, $config, $class, $ignoreUnresolved);
+ }
+
+ /**
+ * @param resource $resource Defaults to STDOUT
+ * @return void
+ */
+ private function help($resource = STDOUT)
+ {
+ $this->helper->writeLine(sprintf(
+ self::HELP_TEMPLATE,
+ $this->scriptName
+ ), true, $resource);
+ }
+
+ /**
+ * @param string $command
+ * @param string $configFile File from which config originates, and to
+ * which it will be written.
+ * @param array $config Parsed configuration.
+ * @param string $class Name of class to reflect.
+ * @param bool $ignoreUnresolved If to ignore classes with unresolved direct dependencies.
+ * @return ArgumentObject
+ */
+ private function createArguments($command, $configFile, $config, $class, $ignoreUnresolved)
+ {
+ return (object) [
+ 'command' => $command,
+ 'configFile' => $configFile,
+ 'config' => $config,
+ 'class' => $class,
+ 'ignoreUnresolved' => $ignoreUnresolved,
+ ];
+ }
+
+ /**
+ * @param string $message
+ * @return ErrorObject
+ */
+ private function createErrorArgument($message)
+ {
+ return (object) [
+ 'command' => self::COMMAND_ERROR,
+ 'message' => $message,
+ ];
+ }
+
+ /**
+ * @return HelpObject
+ */
+ private function createHelpArgument()
+ {
+ return (object) [
+ 'command' => self::COMMAND_HELP,
+ ];
+ }
+}
diff --git a/vendor/laminas/laminas-servicemanager/src/Tool/FactoryCreator.php b/vendor/laminas/laminas-servicemanager/src/Tool/FactoryCreator.php
new file mode 100644
index 00000000..6abcbf69
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/src/Tool/FactoryCreator.php
@@ -0,0 +1,165 @@
+getClassName($className);
+
+ return sprintf(
+ self::FACTORY_TEMPLATE,
+ preg_replace('/\\\\' . $class . '$/', '', $className),
+ $this->createImportStatements($className),
+ $class,
+ $class,
+ $class,
+ $this->createArgumentString($className)
+ );
+ }
+
+ private function getClassName(string $className): string
+ {
+ return substr($className, strrpos($className, '\\') + 1);
+ }
+
+ /**
+ * @param string $className
+ * @return array
+ */
+ private function getConstructorParameters($className)
+ {
+ $reflectionClass = new ReflectionClass($className);
+
+ if (! $reflectionClass->getConstructor()) {
+ return [];
+ }
+
+ $constructorParameters = $reflectionClass->getConstructor()->getParameters();
+
+ if (empty($constructorParameters)) {
+ return [];
+ }
+
+ $constructorParameters = array_filter(
+ $constructorParameters,
+ static function (ReflectionParameter $argument): bool {
+ if ($argument->isOptional()) {
+ return false;
+ }
+
+ $type = $argument->getType();
+ $class = $type instanceof ReflectionNamedType && ! $type->isBuiltin() ? $type->getName() : null;
+
+ if (null === $class) {
+ throw new InvalidArgumentException(sprintf(
+ 'Cannot identify type for constructor argument "%s"; '
+ . 'no type hint, or non-class/interface type hint',
+ $argument->getName()
+ ));
+ }
+
+ return true;
+ }
+ );
+
+ if (empty($constructorParameters)) {
+ return [];
+ }
+
+ return array_map(static function (ReflectionParameter $parameter): ?string {
+ $type = $parameter->getType();
+ return $type instanceof ReflectionNamedType && ! $type->isBuiltin() ? $type->getName() : null;
+ }, $constructorParameters);
+ }
+
+ /**
+ * @param string $className
+ * @return string
+ */
+ private function createArgumentString($className)
+ {
+ $arguments = array_map(static fn(string $dependency): string
+ => sprintf('$container->get(\\%s::class)', $dependency), $this->getConstructorParameters($className));
+
+ switch (count($arguments)) {
+ case 0:
+ return '';
+ case 1:
+ return array_shift($arguments);
+ default:
+ $argumentPad = str_repeat(' ', 12);
+ $closePad = str_repeat(' ', 8);
+ return sprintf(
+ "\n%s%s\n%s",
+ $argumentPad,
+ implode(",\n" . $argumentPad, $arguments),
+ $closePad
+ );
+ }
+ }
+
+ private function createImportStatements(string $className): string
+ {
+ $imports = array_merge(self::IMPORT_ALWAYS, [$className]);
+ sort($imports);
+ return implode("\n", array_map(static fn(string $import): string => sprintf('use %s;', $import), $imports));
+ }
+}
diff --git a/vendor/laminas/laminas-servicemanager/src/Tool/FactoryCreatorCommand.php b/vendor/laminas/laminas-servicemanager/src/Tool/FactoryCreatorCommand.php
new file mode 100644
index 00000000..a89df165
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/src/Tool/FactoryCreatorCommand.php
@@ -0,0 +1,150 @@
+Usage:
+
+ %s [-h|--help|help]
+
+Arguments:
+
+ -h|--help|help This usage message
+ Name of the class to reflect and for which to generate
+ a factory.
+
+Generates to STDOUT a factory for creating the specified class; this may then
+be added to your application, and configured as a factory for the class.
+EOH;
+
+ private ConsoleHelper $helper;
+
+ /**
+ * @param string $scriptName
+ */
+ public function __construct(private $scriptName = self::DEFAULT_SCRIPT_NAME, ?ConsoleHelper $helper = null)
+ {
+ $this->helper = $helper ?: new ConsoleHelper();
+ }
+
+ /**
+ * @param array $args Argument list, minus script name
+ * @return int Exit status
+ */
+ public function __invoke(array $args)
+ {
+ $arguments = $this->parseArgs($args);
+
+ switch ($arguments->command) {
+ case self::COMMAND_HELP:
+ $this->help();
+ return 0;
+ case self::COMMAND_ERROR:
+ assert(is_string($arguments->message));
+ $this->helper->writeErrorMessage($arguments->message);
+ $this->help(STDERR);
+ return 1;
+ case self::COMMAND_DUMP:
+ // fall-through
+ default:
+ break;
+ }
+
+ $generator = new FactoryCreator();
+ assert(is_string($arguments->class));
+ try {
+ $factory = $generator->createFactory($arguments->class);
+ } catch (Exception\InvalidArgumentException $e) {
+ $this->helper->writeErrorMessage(sprintf(
+ 'Unable to create factory for "%s": %s',
+ $arguments->class,
+ $e->getMessage()
+ ));
+ $this->help(STDERR);
+ return 1;
+ }
+
+ $this->helper->write($factory, false);
+ return 0;
+ }
+
+ /**
+ * @return ArgumentObject
+ */
+ private function parseArgs(array $args)
+ {
+ if (! $args) {
+ return $this->createArguments(self::COMMAND_HELP);
+ }
+
+ $arg1 = array_shift($args);
+
+ if (in_array($arg1, ['-h', '--help', 'help'], true)) {
+ return $this->createArguments(self::COMMAND_HELP);
+ }
+
+ $class = $arg1;
+
+ if (! class_exists($class)) {
+ return $this->createArguments(self::COMMAND_ERROR, null, sprintf(
+ 'Class "%s" does not exist or could not be autoloaded.',
+ $class
+ ));
+ }
+
+ return $this->createArguments(self::COMMAND_DUMP, $class);
+ }
+
+ /**
+ * @param resource $resource Defaults to STDOUT
+ * @return void
+ */
+ private function help($resource = STDOUT)
+ {
+ $this->helper->writeLine(sprintf(
+ self::HELP_TEMPLATE,
+ $this->scriptName
+ ), true, $resource);
+ }
+
+ /**
+ * @param string $command
+ * @param string|null $class Name of class to reflect.
+ * @param string|null $error Error message, if any.
+ * @return ArgumentObject
+ */
+ private function createArguments($command, $class = null, $error = null)
+ {
+ return (object) [
+ 'command' => $command,
+ 'class' => $class,
+ 'message' => $error,
+ ];
+ }
+}
diff --git a/vendor/laminas/laminas-servicemanager/src/autoload.php b/vendor/laminas/laminas-servicemanager/src/autoload.php
new file mode 100644
index 00000000..76bd64e0
--- /dev/null
+++ b/vendor/laminas/laminas-servicemanager/src/autoload.php
@@ -0,0 +1,21 @@
+ ## 🇷🇺 Русским гражданам
+>
+> Мы, участники Laminas, родились и живем в разных странах. У многих из нас есть друзья, родственники и коллеги как в России, так и в Украине. Некоторые из нас родились в России. Некоторые из нас живут в России. У некоторых бабушки и дедушки сражались с фашистами во Второй мировой войне. Здесь никто не поддерживает фашизм.
+>
+> У одного из нас есть украинская родственница, которая спаслась из дома вместе с сыном. Поезд задержался из-за бомбежки на дороге впереди. У нас есть друзья, которые прячутся в бомбоубежищах. Мы с тревогой ждем весточки от них после воздушных налетов, которые беспорядочно наносят удары и попадают по больницам, школам, детским садам и домам. Мы не берем это из каких-либо СМИ. Мы наблюдаем это напрямую.
+>
+> Вы доверяете нам достаточно, чтоб использовать наши программы, и мы просим вас довериться нам вновь. Мы нуждаемся в помощи. Выходите и протестуйте против этой бесполезной войны. Остановите кровопролитие. Скажите "Нет войне!"
+>
+> ## 🇺🇸 To Citizens of Russia
+>
+> We at Laminas come from all over the world. Many of us have friends, family and colleagues in both Russia and Ukraine. Some of us were born in Russia. Some of us currently live in Russia. Some have grandparents who fought Nazis in World War II. Nobody here supports fascism.
+>
+> One team member has a Ukrainian relative who fled her home with her son. The train was delayed due to bombing on the road ahead. We have friends who are hiding in bomb shelters. We anxiously follow up on them after the air raids, which indiscriminately fire at hospitals, schools, kindergartens and houses. We're not taking this from any media. These are our actual experiences.
+>
+> You trust us enough to use our software. We ask that you trust us to say the truth on this. We need your help. Go out and protest this unnecessary war. Stop the bloodshed. Say "stop the war!"
+
+`Laminas\Stdlib` is a set of components that implements general purpose utility
+class for different scopes like:
+
+- array utilities functions;
+- general messaging systems;
+- string wrappers;
+- etc.
+
+---
+
+- File issues at https://github.com/laminas/laminas-stdlib/issues
+- Documentation is at https://docs.laminas.dev/laminas-stdlib/
+
+## Benchmarks
+
+We provide scripts for benchmarking laminas-stdlib using the
+[PHPBench](https://github.com/phpbench/phpbench) framework; these can be
+found in the `benchmark/` directory.
+
+To execute the benchmarks you can run the following command:
+
+```bash
+$ vendor/bin/phpbench run --report=aggregate
+```
diff --git a/vendor/laminas/laminas-stdlib/composer.json b/vendor/laminas/laminas-stdlib/composer.json
new file mode 100644
index 00000000..bb713ea8
--- /dev/null
+++ b/vendor/laminas/laminas-stdlib/composer.json
@@ -0,0 +1,64 @@
+{
+ "name": "laminas/laminas-stdlib",
+ "description": "SPL extensions, array utilities, error handlers, and more",
+ "license": "BSD-3-Clause",
+ "keywords": [
+ "laminas",
+ "stdlib"
+ ],
+ "homepage": "https://laminas.dev",
+ "support": {
+ "docs": "https://docs.laminas.dev/laminas-stdlib/",
+ "issues": "https://github.com/laminas/laminas-stdlib/issues",
+ "source": "https://github.com/laminas/laminas-stdlib",
+ "rss": "https://github.com/laminas/laminas-stdlib/releases.atom",
+ "chat": "https://laminas.dev/chat",
+ "forum": "https://discourse.laminas.dev"
+ },
+ "config": {
+ "sort-packages": true,
+ "platform": {
+ "php": "8.1.99"
+ },
+ "allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true
+ }
+ },
+ "extra": {
+ },
+ "require": {
+ "php": "~8.1.0 || ~8.2.0 || ~8.3.0"
+ },
+ "require-dev": {
+ "laminas/laminas-coding-standard": "^2.5",
+ "phpbench/phpbench": "^1.2.15",
+ "phpunit/phpunit": "^10.5.8",
+ "psalm/plugin-phpunit": "^0.18.4",
+ "vimeo/psalm": "^5.20.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Laminas\\Stdlib\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "LaminasTest\\Stdlib\\": "test/",
+ "LaminasBench\\Stdlib\\": "benchmark/"
+ }
+ },
+ "scripts": {
+ "check": [
+ "@cs-check",
+ "@test"
+ ],
+ "cs-check": "phpcs",
+ "cs-fix": "phpcbf",
+ "static-analysis": "psalm --shepherd --stats",
+ "test": "phpunit --colors=always",
+ "test-coverage": "phpunit --colors=always --coverage-clover clover.xml"
+ },
+ "conflict": {
+ "zendframework/zend-stdlib": "*"
+ }
+}
diff --git a/vendor/laminas/laminas-stdlib/src/AbstractOptions.php b/vendor/laminas/laminas-stdlib/src/AbstractOptions.php
new file mode 100644
index 00000000..5db2ea95
--- /dev/null
+++ b/vendor/laminas/laminas-stdlib/src/AbstractOptions.php
@@ -0,0 +1,200 @@
+
+ */
+abstract class AbstractOptions implements ParameterObjectInterface
+{
+ // phpcs:disable PSR2.Classes.PropertyDeclaration.Underscore,WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCapsProperty
+
+ /**
+ * We use the __ prefix to avoid collisions with properties in
+ * user-implementations.
+ *
+ * @var bool
+ */
+ protected $__strictMode__ = true;
+
+ // phpcs:enable
+
+ /**
+ * Constructor
+ *
+ * @param iterable|AbstractOptions|null $options
+ */
+ public function __construct($options = null)
+ {
+ if (null !== $options) {
+ $this->setFromArray($options);
+ }
+ }
+
+ /**
+ * Set one or more configuration properties
+ *
+ * @param iterable|AbstractOptions $options
+ * @throws Exception\InvalidArgumentException
+ * @return AbstractOptions Provides fluent interface
+ */
+ public function setFromArray($options)
+ {
+ if ($options instanceof self) {
+ $options = $options->toArray();
+ }
+
+ if (! is_array($options) && ! $options instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(
+ sprintf(
+ 'Parameter provided to %s must be an %s, %s or %s',
+ __METHOD__,
+ 'array',
+ 'Traversable',
+ self::class
+ )
+ );
+ }
+
+ foreach ($options as $key => $value) {
+ $this->__set($key, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Cast to array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $array = [];
+
+ $transform = static function (array $letters): string {
+ /** @var list $letters */
+ $letter = array_shift($letters);
+ return '_' . strtolower($letter);
+ };
+
+ /** @psalm-var TValue $value */
+ foreach (get_object_vars($this) as $key => $value) {
+ if ($key === '__strictMode__') {
+ continue;
+ }
+ $normalizedKey = preg_replace_callback('/([A-Z])/', $transform, $key);
+ $array[$normalizedKey] = $value;
+ }
+
+ return $array;
+ }
+
+ /**
+ * Set a configuration property
+ *
+ * @see ParameterObject::__set()
+ *
+ * @param string $key
+ * @param TValue|null $value
+ * @throws Exception\BadMethodCallException
+ * @return void
+ */
+ public function __set($key, $value)
+ {
+ $setter = 'set' . str_replace('_', '', $key);
+
+ if (is_callable([$this, $setter])) {
+ $this->{$setter}($value);
+
+ return;
+ }
+
+ if ($this->__strictMode__) {
+ throw new Exception\BadMethodCallException(sprintf(
+ 'The option "%s" does not have a callable "%s" ("%s") setter method which must be defined',
+ $key,
+ 'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key))),
+ $setter
+ ));
+ }
+ }
+
+ /**
+ * Get a configuration property
+ *
+ * @see ParameterObject::__get()
+ *
+ * @param string $key
+ * @throws Exception\BadMethodCallException
+ * @return TValue
+ */
+ public function __get($key)
+ {
+ $getter = 'get' . str_replace('_', '', $key);
+
+ if (is_callable([$this, $getter])) {
+ return $this->{$getter}();
+ }
+
+ throw new Exception\BadMethodCallException(sprintf(
+ 'The option "%s" does not have a callable "%s" getter method which must be defined',
+ $key,
+ 'get' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key)))
+ ));
+ }
+
+ /**
+ * Test if a configuration property is null
+ *
+ * @see ParameterObject::__isset()
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function __isset($key)
+ {
+ $getter = 'get' . str_replace('_', '', $key);
+
+ return method_exists($this, $getter) && null !== $this->__get($key);
+ }
+
+ /**
+ * Set a configuration property to NULL
+ *
+ * @see ParameterObject::__unset()
+ *
+ * @param string $key
+ * @throws Exception\InvalidArgumentException
+ * @return void
+ */
+ public function __unset($key)
+ {
+ try {
+ $this->__set($key, null);
+ } catch (Exception\BadMethodCallException $e) {
+ throw new Exception\InvalidArgumentException(
+ 'The class property $' . $key . ' cannot be unset as'
+ . ' NULL is an invalid value for it',
+ 0,
+ $e
+ );
+ }
+ }
+}
diff --git a/vendor/laminas/laminas-stdlib/src/ArrayObject.php b/vendor/laminas/laminas-stdlib/src/ArrayObject.php
new file mode 100644
index 00000000..e0cdc710
--- /dev/null
+++ b/vendor/laminas/laminas-stdlib/src/ArrayObject.php
@@ -0,0 +1,509 @@
+
+ * @template-implements ArrayAccess
+ */
+#[AllowDynamicProperties]
+class ArrayObject implements IteratorAggregate, ArrayAccess, Serializable, Countable
+{
+ /**
+ * Properties of the object have their normal functionality
+ * when accessed as list (var_dump, foreach, etc.).
+ */
+ public const STD_PROP_LIST = 1;
+
+ /**
+ * Entries can be accessed as properties (read and write).
+ */
+ public const ARRAY_AS_PROPS = 2;
+
+ /** @var array */
+ protected $storage;
+
+ /** @var self::STD_PROP_LIST|self::ARRAY_AS_PROPS */
+ protected $flag;
+
+ /** @var class-string */
+ protected $iteratorClass;
+
+ /** @var list */
+ protected $protectedProperties;
+
+ /**
+ * @param array|object $input Object values must act like ArrayAccess
+ * @param self::STD_PROP_LIST|self::ARRAY_AS_PROPS $flags
+ * @param class-string $iteratorClass
+ */
+ public function __construct($input = [], $flags = self::STD_PROP_LIST, $iteratorClass = ArrayIterator::class)
+ {
+ $this->setFlags($flags);
+ $this->storage = $input;
+ $this->setIteratorClass($iteratorClass);
+ $this->protectedProperties = array_keys(get_object_vars($this));
+ }
+
+ /**
+ * Returns whether the requested key exists
+ *
+ * @param TKey $key
+ * @return bool
+ */
+ public function __isset(mixed $key)
+ {
+ if ($this->flag === self::ARRAY_AS_PROPS) {
+ return $this->offsetExists($key);
+ }
+
+ if (in_array($key, $this->protectedProperties)) {
+ throw new Exception\InvalidArgumentException("$key is a protected property, use a different key");
+ }
+
+ return isset($this->$key);
+ }
+
+ /**
+ * Sets the value at the specified key to value
+ *
+ * @param TKey $key
+ * @param TValue $value
+ * @return void
+ */
+ public function __set(mixed $key, mixed $value)
+ {
+ if ($this->flag === self::ARRAY_AS_PROPS) {
+ $this->offsetSet($key, $value);
+ return;
+ }
+
+ if (in_array($key, $this->protectedProperties)) {
+ throw new Exception\InvalidArgumentException("$key is a protected property, use a different key");
+ }
+
+ $this->$key = $value;
+ }
+
+ /**
+ * Unsets the value at the specified key
+ *
+ * @param TKey $key
+ * @return void
+ */
+ public function __unset(mixed $key)
+ {
+ if ($this->flag === self::ARRAY_AS_PROPS) {
+ $this->offsetUnset($key);
+ return;
+ }
+
+ if (in_array($key, $this->protectedProperties)) {
+ throw new Exception\InvalidArgumentException("$key is a protected property, use a different key");
+ }
+
+ unset($this->$key);
+ }
+
+ /**
+ * Returns the value at the specified key by reference
+ *
+ * @param TKey $key
+ * @return TValue|null
+ */
+ public function &__get(mixed $key)
+ {
+ if ($this->flag === self::ARRAY_AS_PROPS) {
+ $ret = &$this->offsetGet($key);
+
+ return $ret;
+ }
+
+ if (in_array($key, $this->protectedProperties, true)) {
+ throw new Exception\InvalidArgumentException("$key is a protected property, use a different key");
+ }
+
+ return $this->$key;
+ }
+
+ /**
+ * Appends the value
+ *
+ * @param TValue $value
+ * @return void
+ */
+ public function append(mixed $value)
+ {
+ $this->storage[] = $value;
+ }
+
+ /**
+ * Sort the entries by value
+ *
+ * @return void
+ */
+ public function asort()
+ {
+ asort($this->storage);
+ }
+
+ /**
+ * Get the number of public properties in the ArrayObject
+ *
+ * @return positive-int|0
+ */
+ #[ReturnTypeWillChange]
+ public function count()
+ {
+ return count($this->storage);
+ }
+
+ /**
+ * Exchange the array for another one.
+ *
+ * @param array|ArrayObject|ArrayIterator|object $data
+ * @return array
+ */
+ public function exchangeArray($data)
+ {
+ if (! is_array($data) && ! is_object($data)) {
+ throw new Exception\InvalidArgumentException(
+ 'Passed variable is not an array or object, using empty array instead'
+ );
+ }
+
+ if (is_object($data) && ($data instanceof self || $data instanceof \ArrayObject)) {
+ $data = $data->getArrayCopy();
+ }
+ if (! is_array($data)) {
+ $data = (array) $data;
+ }
+
+ $storage = $this->storage;
+
+ $this->storage = $data;
+
+ return $storage;
+ }
+
+ /**
+ * Creates a copy of the ArrayObject.
+ *
+ * @return array
+ */
+ public function getArrayCopy()
+ {
+ return $this->storage;
+ }
+
+ /**
+ * Gets the behavior flags.
+ *
+ * @return self::STD_PROP_LIST|self::ARRAY_AS_PROPS
+ */
+ public function getFlags()
+ {
+ return $this->flag;
+ }
+
+ /**
+ * Create a new iterator from an ArrayObject instance
+ *
+ * @return Iterator
+ */
+ #[ReturnTypeWillChange]
+ public function getIterator()
+ {
+ $class = $this->iteratorClass;
+
+ return new $class($this->storage);
+ }
+
+ /**
+ * Gets the iterator classname for the ArrayObject.
+ *
+ * @return class-string
+ */
+ public function getIteratorClass()
+ {
+ return $this->iteratorClass;
+ }
+
+ /**
+ * Sort the entries by key
+ *
+ * @return void
+ */
+ public function ksort()
+ {
+ ksort($this->storage);
+ }
+
+ /**
+ * Sort an array using a case insensitive "natural order" algorithm
+ *
+ * @return void
+ */
+ public function natcasesort()
+ {
+ natcasesort($this->storage);
+ }
+
+ /**
+ * Sort entries using a "natural order" algorithm
+ *
+ * @return void
+ */
+ public function natsort()
+ {
+ natsort($this->storage);
+ }
+
+ /**
+ * Returns whether the requested key exists
+ *
+ * @param TKey $key
+ * @return bool
+ */
+ #[ReturnTypeWillChange]
+ public function offsetExists(mixed $key)
+ {
+ return isset($this->storage[$key]);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @param TKey $key
+ * @return TValue|null
+ */
+ #[ReturnTypeWillChange]
+ public function &offsetGet(mixed $key)
+ {
+ $ret = null;
+ if (! $this->offsetExists($key)) {
+ return $ret;
+ }
+ $ret = &$this->storage[$key];
+
+ return $ret;
+ }
+
+ /**
+ * Sets the value at the specified key to value
+ *
+ * @param TKey $offset
+ * @param TValue $value
+ * @return void
+ */
+ #[ReturnTypeWillChange]
+ public function offsetSet(mixed $offset, mixed $value)
+ {
+ $this->storage[$offset] = $value;
+ }
+
+ /**
+ * Unsets the value at the specified key
+ *
+ * @param TKey $offset
+ * @return void
+ */
+ #[ReturnTypeWillChange]
+ public function offsetUnset(mixed $offset)
+ {
+ if ($this->offsetExists($offset)) {
+ unset($this->storage[$offset]);
+ }
+ }
+
+ /**
+ * Serialize an ArrayObject
+ *
+ * @return string
+ */
+ public function serialize()
+ {
+ return serialize($this->__serialize());
+ }
+
+ /**
+ * Magic method used for serializing of an instance.
+ *
+ * @return array
+ */
+ public function __serialize()
+ {
+ return get_object_vars($this);
+ }
+
+ /**
+ * Sets the behavior flags
+ *
+ * @param self::STD_PROP_LIST|self::ARRAY_AS_PROPS $flags
+ * @return void
+ */
+ public function setFlags($flags)
+ {
+ $this->flag = $flags;
+ }
+
+ /**
+ * Sets the iterator classname for the ArrayObject
+ *
+ * @param class-string $class
+ * @return void
+ */
+ public function setIteratorClass($class)
+ {
+ if (class_exists($class)) {
+ $this->iteratorClass = $class;
+
+ return;
+ }
+
+ if (str_starts_with($class, '\\')) {
+ $class = '\\' . $class;
+ if (class_exists($class)) {
+ $this->iteratorClass = $class;
+
+ return;
+ }
+ }
+
+ throw new Exception\InvalidArgumentException('The iterator class does not exist');
+ }
+
+ /**
+ * Sort the entries with a user-defined comparison function and maintain key association
+ *
+ * @param callable(TValue, TValue): int $function
+ * @return void
+ */
+ public function uasort($function)
+ {
+ if (is_callable($function)) {
+ uasort($this->storage, $function);
+ }
+ }
+
+ /**
+ * Sort the entries by keys using a user-defined comparison function
+ *
+ * @param callable(TKey, TKey): int $function
+ * @return void
+ */
+ public function uksort($function)
+ {
+ if (is_callable($function)) {
+ uksort($this->storage, $function);
+ }
+ }
+
+ /**
+ * Unserialize an ArrayObject
+ *
+ * @param string $data
+ * @return void
+ */
+ public function unserialize($data)
+ {
+ $toUnserialize = unserialize($data);
+ if (! is_array($toUnserialize)) {
+ throw new UnexpectedValueException(sprintf(
+ 'Cannot deserialize %s instance; corrupt serialization data',
+ self::class
+ ));
+ }
+
+ $this->__unserialize($toUnserialize);
+ }
+
+ /**
+ * Magic method used to rebuild an instance.
+ *
+ * @param array $data Data array.
+ * @return void
+ */
+ public function __unserialize($data)
+ {
+ $this->protectedProperties = array_keys(get_object_vars($this));
+
+ // Unserialize protected internal properties first
+ if (array_key_exists('flag', $data)) {
+ $this->setFlags((int) $data['flag']);
+ unset($data['flag']);
+ }
+
+ if (array_key_exists('storage', $data)) {
+ if (! is_array($data['storage']) && ! is_object($data['storage'])) {
+ throw new UnexpectedValueException(sprintf(
+ 'Cannot deserialize %s instance: corrupt storage data; expected array or object, received %s',
+ self::class,
+ gettype($data['storage'])
+ ));
+ }
+ $this->exchangeArray($data['storage']);
+ unset($data['storage']);
+ }
+
+ if (array_key_exists('iteratorClass', $data)) {
+ if (! is_string($data['iteratorClass'])) {
+ throw new UnexpectedValueException(sprintf(
+ 'Cannot deserialize %s instance: invalid iteratorClass; expected string, received %s',
+ self::class,
+ get_debug_type($data['iteratorClass'])
+ ));
+ }
+ $this->setIteratorClass($data['iteratorClass']);
+ unset($data['iteratorClass']);
+ }
+
+ unset($data['protectedProperties']);
+
+ // Unserialize array keys after resolving protected properties to ensure configuration is used.
+ foreach ($data as $k => $v) {
+ $this->__set($k, $v);
+ }
+ }
+}
diff --git a/vendor/laminas/laminas-stdlib/src/ArraySerializableInterface.php b/vendor/laminas/laminas-stdlib/src/ArraySerializableInterface.php
new file mode 100644
index 00000000..adb5231f
--- /dev/null
+++ b/vendor/laminas/laminas-stdlib/src/ArraySerializableInterface.php
@@ -0,0 +1,22 @@
+
+ */
+class ArrayStack extends PhpArrayObject
+{
+ /**
+ * Retrieve iterator
+ *
+ * Retrieve an array copy of the object, reverse its order, and return an
+ * ArrayIterator with that reversed array.
+ *
+ * @return ArrayIterator
+ */
+ #[ReturnTypeWillChange]
+ public function getIterator()
+ {
+ $array = $this->getArrayCopy();
+ return new ArrayIterator(array_reverse($array));
+ }
+}
diff --git a/vendor/laminas/laminas-stdlib/src/ArrayUtils.php b/vendor/laminas/laminas-stdlib/src/ArrayUtils.php
new file mode 100644
index 00000000..7bfa20dc
--- /dev/null
+++ b/vendor/laminas/laminas-stdlib/src/ArrayUtils.php
@@ -0,0 +1,337 @@
+
+ * $list = array('a', 'b', 'c', 'd');
+ * $list = array(
+ * 0 => 'foo',
+ * 1 => 'bar',
+ * 2 => array('foo' => 'baz'),
+ * );
+ *
+ *
+ * @param bool $allowEmpty Is an empty list a valid list?
+ * @return bool
+ */
+ public static function isList(mixed $value, $allowEmpty = false)
+ {
+ if (! is_array($value)) {
+ return false;
+ }
+
+ if (! $value) {
+ return $allowEmpty;
+ }
+
+ return array_values($value) === $value;
+ }
+
+ /**
+ * Test whether an array is a hash table.
+ *
+ * An array is a hash table if:
+ *
+ * 1. Contains one or more non-integer keys, or
+ * 2. Integer keys are non-continuous or misaligned (not starting with 0)
+ *
+ * For example:
+ *
+ * $hash = array(
+ * 'foo' => 15,
+ * 'bar' => false,
+ * );
+ * $hash = array(
+ * 1995 => 'Birth of PHP',
+ * 2009 => 'PHP 5.3.0',
+ * 2012 => 'PHP 5.4.0',
+ * );
+ * $hash = array(
+ * 'formElement,
+ * 'options' => array( 'debug' => true ),
+ * );
+ *
+ *
+ * @param bool $allowEmpty Is an empty array() a valid hash table?
+ * @return bool
+ */
+ public static function isHashTable(mixed $value, $allowEmpty = false)
+ {
+ if (! is_array($value)) {
+ return false;
+ }
+
+ if (! $value) {
+ return $allowEmpty;
+ }
+
+ return array_values($value) !== $value;
+ }
+
+ /**
+ * Checks if a value exists in an array.
+ *
+ * Due to "foo" == 0 === TRUE with in_array when strict = false, an option
+ * has been added to prevent this. When $strict = 0/false, the most secure
+ * non-strict check is implemented. if $strict = -1, the default in_array
+ * non-strict behaviour is used.
+ *
+ * @deprecated This method will be removed in version 4.0 of this component
+ *
+ * @param array $haystack
+ * @param int|bool $strict
+ * @return bool
+ */
+ public static function inArray(mixed $needle, array $haystack, $strict = false)
+ {
+ if ((bool) $strict === false) {
+ if (is_int($needle) || is_float($needle)) {
+ $needle = (string) $needle;
+ }
+ if (is_string($needle)) {
+ foreach ($haystack as &$h) {
+ if (is_int($h) || is_float($h)) {
+ $h = (string) $h;
+ }
+ }
+ }
+ }
+
+ return in_array($needle, $haystack, (bool) $strict);
+ }
+
+ /**
+ * Converts an iterator to an array. The $recursive flag, on by default,
+ * hints whether or not you want to do so recursively.
+ *
+ * @template TKey
+ * @template TValue
+ * @param iterable $iterator The array or Traversable object to convert
+ * @param bool $recursive Recursively check all nested structures
+ * @throws Exception\InvalidArgumentException If $iterator is not an array or a Traversable object.
+ * @return array
+ */
+ public static function iteratorToArray($iterator, $recursive = true)
+ {
+ /** @psalm-suppress DocblockTypeContradiction */
+ if (! is_array($iterator) && ! $iterator instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(__METHOD__ . ' expects an array or Traversable object');
+ }
+
+ if (! $recursive) {
+ if (is_array($iterator)) {
+ return $iterator;
+ }
+
+ return iterator_to_array($iterator);
+ }
+
+ if (
+ is_object($iterator)
+ && ! $iterator instanceof Iterator
+ && method_exists($iterator, 'toArray')
+ ) {
+ /** @psalm-var array $array */
+ $array = $iterator->toArray();
+
+ return $array;
+ }
+
+ $array = [];
+ foreach ($iterator as $key => $value) {
+ if (is_scalar($value)) {
+ $array[$key] = $value;
+ continue;
+ }
+
+ if ($value instanceof Traversable) {
+ $array[$key] = static::iteratorToArray($value, $recursive);
+ continue;
+ }
+
+ if (is_array($value)) {
+ $array[$key] = static::iteratorToArray($value, $recursive);
+ continue;
+ }
+
+ $array[$key] = $value;
+ }
+
+ /** @psalm-var array $array */
+
+ return $array;
+ }
+
+ /**
+ * Merge two arrays together.
+ *
+ * If an integer key exists in both arrays and preserveNumericKeys is false, the value
+ * from the second array will be appended to the first array. If both values are arrays, they
+ * are merged together, else the value of the second array overwrites the one of the first array.
+ *
+ * @param array $a
+ * @param array $b
+ * @param bool $preserveNumericKeys
+ * @return array
+ */
+ public static function merge(array $a, array $b, $preserveNumericKeys = false)
+ {
+ foreach ($b as $key => $value) {
+ if ($value instanceof MergeReplaceKeyInterface) {
+ $a[$key] = $value->getData();
+ } elseif (isset($a[$key]) || array_key_exists($key, $a)) {
+ if ($value instanceof MergeRemoveKey) {
+ unset($a[$key]);
+ } elseif (! $preserveNumericKeys && is_int($key)) {
+ $a[] = $value;
+ } elseif (is_array($value) && is_array($a[$key])) {
+ $a[$key] = static::merge($a[$key], $value, $preserveNumericKeys);
+ } else {
+ $a[$key] = $value;
+ }
+ } else {
+ if (! $value instanceof MergeRemoveKey) {
+ $a[$key] = $value;
+ }
+ }
+ }
+
+ return $a;
+ }
+
+ /**
+ * @deprecated Since 3.2.0; use the native array_filter methods
+ *
+ * @param callable $callback
+ * @param null|int $flag
+ * @return array
+ * @throws Exception\InvalidArgumentException
+ */
+ public static function filter(array $data, $callback, $flag = null)
+ {
+ if (! is_callable($callback)) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Second parameter of %s must be callable',
+ __METHOD__
+ ));
+ }
+
+ return array_filter($data, $callback, $flag ?? 0);
+ }
+}
diff --git a/vendor/laminas/laminas-stdlib/src/ArrayUtils/MergeRemoveKey.php b/vendor/laminas/laminas-stdlib/src/ArrayUtils/MergeRemoveKey.php
new file mode 100644
index 00000000..bde0b25a
--- /dev/null
+++ b/vendor/laminas/laminas-stdlib/src/ArrayUtils/MergeRemoveKey.php
@@ -0,0 +1,9 @@
+data;
+ }
+}
diff --git a/vendor/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php b/vendor/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php
new file mode 100644
index 00000000..47243fc2
--- /dev/null
+++ b/vendor/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php
@@ -0,0 +1,16 @@
+message`,
+ * `message`)
+ * - Write output to a specified stream, optionally with colorization.
+ * - Write a line of output to a specified stream, optionally with
+ * colorization, using the system EOL sequence..
+ * - Write an error message to STDERR.
+ *
+ * Colorization will only occur when expected sequences are discovered, and
+ * then, only if the console terminal allows it.
+ *
+ * Essentially, provides the bare minimum to allow you to provide messages to
+ * the current console.
+ */
+class ConsoleHelper
+{
+ public const COLOR_GREEN = "\033[32m";
+ public const COLOR_RED = "\033[31m";
+ public const COLOR_RESET = "\033[0m";
+
+ public const HIGHLIGHT_INFO = 'info';
+ public const HIGHLIGHT_ERROR = 'error';
+
+ /** @psalm-var array */
+ private array $highlightMap = [
+ self::HIGHLIGHT_INFO => self::COLOR_GREEN,
+ self::HIGHLIGHT_ERROR => self::COLOR_RED,
+ ];
+
+ /** @var string Exists only for testing. */
+ private string $eol = PHP_EOL;
+
+ /** @var resource Exists only for testing. */
+ private $stderr = STDERR;
+
+ private bool $supportsColor;
+
+ /**
+ * @param resource $resource
+ */
+ public function __construct($resource = STDOUT)
+ {
+ $this->supportsColor = $this->detectColorCapabilities($resource);
+ }
+
+ /**
+ * Colorize a string for use with the terminal.
+ *
+ * Takes strings formatted as `string` and formats them per the
+ * $highlightMap; if color support is disabled, simply removes the formatting
+ * tags.
+ *
+ * @param string $string
+ * @return string
+ */
+ public function colorize($string)
+ {
+ $reset = $this->supportsColor ? self::COLOR_RESET : '';
+ foreach ($this->highlightMap as $key => $color) {
+ $pattern = sprintf('#<%s>(.*?)%s>#s', $key, $key);
+ $color = $this->supportsColor ? $color : '';
+ $string = preg_replace($pattern, $color . '$1' . $reset, $string);
+ }
+ return $string;
+ }
+
+ /**
+ * @param string $string
+ * @param bool $colorize Whether or not to colorize the string
+ * @param resource $resource Defaults to STDOUT
+ * @return void
+ */
+ public function write($string, $colorize = true, $resource = STDOUT)
+ {
+ if ($colorize) {
+ $string = $this->colorize($string);
+ }
+
+ $string = $this->formatNewlines($string);
+
+ fwrite($resource, $string);
+ }
+
+ /**
+ * @param string $string
+ * @param bool $colorize Whether or not to colorize the line
+ * @param resource $resource Defaults to STDOUT
+ * @return void
+ */
+ public function writeLine($string, $colorize = true, $resource = STDOUT)
+ {
+ $this->write($string . $this->eol, $colorize, $resource);
+ }
+
+ /**
+ * Emit an error message.
+ *
+ * Wraps the message in ``, and passes it to `writeLine()`,
+ * using STDERR as the resource; emits an additional empty line when done,
+ * also to STDERR.
+ *
+ * @param string $message
+ * @return void
+ */
+ public function writeErrorMessage($message)
+ {
+ $this->writeLine(sprintf('%s', $message), true, $this->stderr);
+ $this->writeLine('', false, $this->stderr);
+ }
+
+ /**
+ * @param resource $resource
+ * @return bool
+ */
+ private function detectColorCapabilities($resource = STDOUT)
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ // Windows
+ return false !== getenv('ANSICON')
+ || 'ON' === getenv('ConEmuANSI')
+ || 'xterm' === getenv('TERM');
+ }
+
+ return function_exists('posix_isatty') && posix_isatty($resource);
+ }
+
+ /**
+ * Ensure newlines are appropriate for the current terminal.
+ *
+ * @param string $string
+ * @return string
+ */
+ private function formatNewlines($string)
+ {
+ $string = str_replace($this->eol, "\0PHP_EOL\0", $string);
+ $string = preg_replace("/(\r\n|\n|\r)/", $this->eol, $string);
+ return str_replace("\0PHP_EOL\0", $this->eol, $string);
+ }
+}
diff --git a/vendor/laminas/laminas-stdlib/src/DispatchableInterface.php b/vendor/laminas/laminas-stdlib/src/DispatchableInterface.php
new file mode 100644
index 00000000..a9b325a6
--- /dev/null
+++ b/vendor/laminas/laminas-stdlib/src/DispatchableInterface.php
@@ -0,0 +1,15 @@
+
+ */
+ protected static $stack = [];
+
+ /**
+ * Check if this error handler is active
+ *
+ * @return bool
+ */
+ public static function started()
+ {
+ return (bool) static::getNestedLevel();
+ }
+
+ /**
+ * Get the current nested level
+ *
+ * @return int
+ */
+ public static function getNestedLevel()
+ {
+ return count(static::$stack);
+ }
+
+ /**
+ * Starting the error handler
+ *
+ * @param int $errorLevel
+ * @return void
+ */
+ public static function start($errorLevel = E_WARNING)
+ {
+ if (! static::$stack) {
+ set_error_handler([static::class, 'addError'], $errorLevel);
+ }
+
+ static::$stack[] = null;
+ }
+
+ /**
+ * Stopping the error handler
+ *
+ * @param bool $throw Throw the ErrorException if any
+ * @return null|ErrorException
+ * @throws ErrorException If an error has been caught and $throw is true.
+ */
+ public static function stop($throw = false)
+ {
+ $errorException = null;
+
+ if (static::$stack) {
+ $errorException = array_pop(static::$stack);
+
+ if (! static::$stack) {
+ restore_error_handler();
+ }
+
+ if ($errorException && $throw) {
+ throw $errorException;
+ }
+ }
+
+ return $errorException;
+ }
+
+ /**
+ * Stop all active handler
+ *
+ * @return void
+ */
+ public static function clean()
+ {
+ if (static::$stack) {
+ restore_error_handler();
+ }
+
+ static::$stack = [];
+ }
+
+ /**
+ * Add an error to the stack
+ *
+ * @param int $errno
+ * @param string $errstr
+ * @param string $errfile
+ * @param int $errline
+ * @return void
+ */
+ public static function addError($errno, $errstr = '', $errfile = '', $errline = 0)
+ {
+ $stack = &static::$stack[count(static::$stack) - 1];
+ $stack = new ErrorException($errstr, 0, $errno, $errfile, $errline, $stack);
+ }
+}
diff --git a/vendor/laminas/laminas-stdlib/src/Exception/BadMethodCallException.php b/vendor/laminas/laminas-stdlib/src/Exception/BadMethodCallException.php
new file mode 100644
index 00000000..a62b9171
--- /dev/null
+++ b/vendor/laminas/laminas-stdlib/src/Exception/BadMethodCallException.php
@@ -0,0 +1,12 @@
+
+ */
+class FastPriorityQueue implements Iterator, Countable, Serializable
+{
+ public const EXTR_DATA = PhpSplPriorityQueue::EXTR_DATA;
+ public const EXTR_PRIORITY = PhpSplPriorityQueue::EXTR_PRIORITY;
+ public const EXTR_BOTH = PhpSplPriorityQueue::EXTR_BOTH;
+
+ /** @var self::EXTR_* */
+ protected $extractFlag = self::EXTR_DATA;
+
+ /**
+ * Elements of the queue, divided by priorities
+ *
+ * @var array>
+ */
+ protected $values = [];
+
+ /**
+ * Array of priorities
+ *
+ * @var array
+ */
+ protected $priorities = [];
+
+ /**
+ * Array of priorities used for the iteration
+ *
+ * @var array
+ */
+ protected $subPriorities = [];
+
+ /**
+ * Max priority
+ *
+ * @var int|null
+ */
+ protected $maxPriority;
+
+ /**
+ * Total number of elements in the queue
+ *
+ * @var int
+ */
+ protected $count = 0;
+
+ /**
+ * Index of the current element in the queue
+ *
+ * @var int
+ */
+ protected $index = 0;
+
+ /**
+ * Sub index of the current element in the same priority level
+ *
+ * @var int
+ */
+ protected $subIndex = 0;
+
+ public function __serialize(): array
+ {
+ $clone = clone $this;
+ $clone->setExtractFlags(self::EXTR_BOTH);
+
+ $data = [];
+ foreach ($clone as $item) {
+ $data[] = $item;
+ }
+
+ return $data;
+ }
+
+ public function __unserialize(array $data): void
+ {
+ foreach ($data as $item) {
+ $this->insert($item['data'], $item['priority']);
+ }
+ }
+
+ /**
+ * Insert an element in the queue with a specified priority
+ *
+ * @param TValue $value
+ * @param int $priority
+ * @return void
+ */
+ public function insert(mixed $value, $priority)
+ {
+ if (! is_int($priority)) {
+ throw new Exception\InvalidArgumentException('The priority must be an integer');
+ }
+ $this->values[$priority][] = $value;
+ if (! isset($this->priorities[$priority])) {
+ $this->priorities[$priority] = $priority;
+ $this->maxPriority = $this->maxPriority === null ? $priority : max($priority, $this->maxPriority);
+ }
+ ++$this->count;
+ }
+
+ /**
+ * Extract an element in the queue according to the priority and the
+ * order of insertion
+ *
+ * @return TValue|int|array{data: TValue, priority: int}|false
+ */
+ public function extract()
+ {
+ if (! $this->valid()) {
+ return false;
+ }
+ $value = $this->current();
+ $this->nextAndRemove();
+ return $value;
+ }
+
+ /**
+ * Remove an item from the queue
+ *
+ * This is different than {@link extract()}; its purpose is to dequeue an
+ * item.
+ *
+ * Note: this removes the first item matching the provided item found. If
+ * the same item has been added multiple times, it will not remove other
+ * instances.
+ *
+ * @return bool False if the item was not found, true otherwise.
+ */
+ public function remove(mixed $datum)
+ {
+ $currentIndex = $this->index;
+ $currentSubIndex = $this->subIndex;
+ $currentPriority = $this->maxPriority;
+
+ $this->rewind();
+ while ($this->valid()) {
+ if (current($this->values[$this->maxPriority]) === $datum) {
+ $index = key($this->values[$this->maxPriority]);
+ unset($this->values[$this->maxPriority][$index]);
+
+ // The `next()` method advances the internal array pointer, so we need to use the `reset()` function,
+ // otherwise we would lose all elements before the place the pointer points.
+ reset($this->values[$this->maxPriority]);
+
+ $this->index = $currentIndex;
+ $this->subIndex = $currentSubIndex;
+
+ // If the array is empty we need to destroy the unnecessary priority,
+ // otherwise we would end up with an incorrect value of `$this->count`
+ // {@see \Laminas\Stdlib\FastPriorityQueue::nextAndRemove()}.
+ if (empty($this->values[$this->maxPriority])) {
+ unset($this->values[$this->maxPriority]);
+ unset($this->priorities[$this->maxPriority]);
+ if ($this->maxPriority === $currentPriority) {
+ $this->subIndex = 0;
+ }
+ }
+
+ $this->maxPriority = empty($this->priorities) ? null : max($this->priorities);
+ --$this->count;
+ return true;
+ }
+ $this->next();
+ }
+ return false;
+ }
+
+ /**
+ * Get the total number of elements in the queue
+ *
+ * @return int
+ */
+ #[ReturnTypeWillChange]
+ public function count()
+ {
+ return $this->count;
+ }
+
+ /**
+ * Get the current element in the queue
+ *
+ * @return TValue|int|array{data: TValue|false, priority: int|null}|false
+ */
+ #[ReturnTypeWillChange]
+ public function current()
+ {
+ switch ($this->extractFlag) {
+ case self::EXTR_DATA:
+ return current($this->values[$this->maxPriority]);
+ case self::EXTR_PRIORITY:
+ return $this->maxPriority;
+ case self::EXTR_BOTH:
+ return [
+ 'data' => current($this->values[$this->maxPriority]),
+ 'priority' => $this->maxPriority,
+ ];
+ }
+ }
+
+ /**
+ * Get the index of the current element in the queue
+ *
+ * @return int
+ */
+ #[ReturnTypeWillChange]
+ public function key()
+ {
+ return $this->index;
+ }
+
+ /**
+ * Set the iterator pointer to the next element in the queue
+ * removing the previous element
+ *
+ * @return void
+ */
+ protected function nextAndRemove()
+ {
+ $key = key($this->values[$this->maxPriority]);
+
+ if (false === next($this->values[$this->maxPriority])) {
+ unset($this->priorities[$this->maxPriority]);
+ unset($this->values[$this->maxPriority]);
+ $this->maxPriority = empty($this->priorities) ? null : max($this->priorities);
+ $this->subIndex = -1;
+ } else {
+ unset($this->values[$this->maxPriority][$key]);
+ }
+ ++$this->index;
+ ++$this->subIndex;
+ --$this->count;
+ }
+
+ /**
+ * Set the iterator pointer to the next element in the queue
+ * without removing the previous element
+ */
+ #[ReturnTypeWillChange]
+ public function next()
+ {
+ if (false === next($this->values[$this->maxPriority])) {
+ unset($this->subPriorities[$this->maxPriority]);
+ reset($this->values[$this->maxPriority]);
+ $this->maxPriority = empty($this->subPriorities) ? null : max($this->subPriorities);
+ $this->subIndex = -1;
+ }
+ ++$this->index;
+ ++$this->subIndex;
+ }
+
+ /**
+ * Check if the current iterator is valid
+ *
+ * @return bool
+ */
+ #[ReturnTypeWillChange]
+ public function valid()
+ {
+ return isset($this->values[$this->maxPriority]);
+ }
+
+ /**
+ * Rewind the current iterator
+ */
+ #[ReturnTypeWillChange]
+ public function rewind()
+ {
+ $this->subPriorities = $this->priorities;
+ $this->maxPriority = empty($this->priorities) ? 0 : max($this->priorities);
+ $this->index = 0;
+ $this->subIndex = 0;
+ }
+
+ /**
+ * Serialize to an array
+ *
+ * Array will be priority => data pairs
+ *
+ * @return list
+ */
+ public function toArray()
+ {
+ $array = [];
+ foreach (clone $this as $item) {
+ $array[] = $item;
+ }
+ return $array;
+ }
+
+ /**
+ * Serialize
+ *
+ * @return string
+ */
+ public function serialize()
+ {
+ return serialize($this->__serialize());
+ }
+
+ /**
+ * Deserialize
+ *
+ * @param string $data
+ * @return void
+ */
+ public function unserialize($data)
+ {
+ $toUnserialize = unserialize($data);
+ if (! is_array($toUnserialize)) {
+ throw new UnexpectedValueException(sprintf(
+ 'Cannot deserialize %s instance; corrupt serialization data',
+ self::class
+ ));
+ }
+
+ $this->__unserialize($toUnserialize);
+ }
+
+ /**
+ * Set the extract flag
+ *
+ * @param self::EXTR_* $flag
+ * @return void
+ */
+ public function setExtractFlags($flag)
+ {
+ $this->extractFlag = match ($flag) {
+ self::EXTR_DATA, self::EXTR_PRIORITY, self::EXTR_BOTH => $flag,
+ default => throw new Exception\InvalidArgumentException("The extract flag specified is not valid"),
+ };
+ }
+
+ /**
+ * Check if the queue is empty
+ *
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ return empty($this->values);
+ }
+
+ /**
+ * Does the queue contain the given datum?
+ *
+ * @return bool
+ */
+ public function contains(mixed $datum)
+ {
+ foreach ($this->values as $values) {
+ if (in_array($datum, $values)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Does the queue have an item with the given priority?
+ *
+ * @param int $priority
+ * @return bool
+ */
+ public function hasPriority($priority)
+ {
+ return isset($this->values[$priority]);
+ }
+}
diff --git a/vendor/laminas/laminas-stdlib/src/Glob.php b/vendor/laminas/laminas-stdlib/src/Glob.php
new file mode 100644
index 00000000..5b5be710
--- /dev/null
+++ b/vendor/laminas/laminas-stdlib/src/Glob.php
@@ -0,0 +1,226 @@
+ GLOB_MARK,
+ self::GLOB_NOSORT => GLOB_NOSORT,
+ self::GLOB_NOCHECK => GLOB_NOCHECK,
+ self::GLOB_NOESCAPE => GLOB_NOESCAPE,
+ self::GLOB_BRACE => defined('GLOB_BRACE') ? GLOB_BRACE : 0,
+ self::GLOB_ONLYDIR => GLOB_ONLYDIR,
+ self::GLOB_ERR => GLOB_ERR,
+ ];
+
+ $globFlags = 0;
+
+ foreach ($flagMap as $internalFlag => $globFlag) {
+ if ($flags & $internalFlag) {
+ $globFlags |= $globFlag;
+ }
+ }
+ } else {
+ $globFlags = 0;
+ }
+
+ ErrorHandler::start();
+ $res = glob($pattern, $globFlags);
+ $err = ErrorHandler::stop();
+ if ($res === false) {
+ throw new Exception\RuntimeException("glob('{$pattern}', {$globFlags}) failed", 0, $err);
+ }
+ return $res;
+ }
+
+ /**
+ * Expand braces manually, then use the system glob.
+ *
+ * @param string $pattern
+ * @param int $flags
+ * @return array
+ * @throws Exception\RuntimeException
+ */
+ protected static function fallbackGlob($pattern, $flags)
+ {
+ if (! self::flagsIsEqualTo($flags, self::GLOB_BRACE)) {
+ return static::systemGlob($pattern, $flags);
+ }
+
+ $flags &= ~self::GLOB_BRACE;
+ $length = strlen($pattern);
+ $paths = [];
+
+ if ($flags & self::GLOB_NOESCAPE) {
+ $begin = strpos($pattern, '{');
+ } else {
+ $begin = 0;
+
+ while (true) {
+ if ($begin === $length) {
+ $begin = false;
+ break;
+ } elseif ($pattern[$begin] === '\\' && ($begin + 1) < $length) {
+ $begin++;
+ } elseif ($pattern[$begin] === '{') {
+ break;
+ }
+
+ $begin++;
+ }
+ }
+
+ if ($begin === false) {
+ return static::systemGlob($pattern, $flags);
+ }
+
+ $next = static::nextBraceSub($pattern, $begin + 1, $flags);
+
+ if ($next === null) {
+ return static::systemGlob($pattern, $flags);
+ }
+
+ $rest = $next;
+
+ while ($pattern[$rest] !== '}') {
+ $rest = static::nextBraceSub($pattern, $rest + 1, $flags);
+
+ if ($rest === null) {
+ return static::systemGlob($pattern, $flags);
+ }
+ }
+
+ $p = $begin + 1;
+
+ while (true) {
+ $subPattern = substr($pattern, 0, $begin)
+ . substr($pattern, $p, $next - $p)
+ . substr($pattern, $rest + 1);
+
+ $result = static::fallbackGlob($subPattern, $flags | self::GLOB_BRACE);
+
+ if ($result) {
+ $paths = array_merge($paths, $result);
+ }
+
+ if ($pattern[$next] === '}') {
+ break;
+ }
+
+ $p = $next + 1;
+ $next = static::nextBraceSub($pattern, $p, $flags);
+ }
+
+ return array_unique($paths);
+ }
+
+ /**
+ * Find the end of the sub-pattern in a brace expression.
+ *
+ * @param string $pattern
+ * @param int $begin
+ * @param int $flags
+ * @return int|null
+ */
+ protected static function nextBraceSub($pattern, $begin, $flags)
+ {
+ $length = strlen($pattern);
+ $depth = 0;
+ $current = $begin;
+
+ while ($current < $length) {
+ $flagsEqualsNoEscape = self::flagsIsEqualTo($flags, self::GLOB_NOESCAPE);
+
+ if ($flagsEqualsNoEscape && $pattern[$current] === '\\') {
+ if (++$current === $length) {
+ break;
+ }
+
+ $current++;
+ } else {
+ if (
+ ($pattern[$current] === '}' && $depth-- === 0)
+ || ($pattern[$current] === ',' && $depth === 0)
+ ) {
+ break;
+ } elseif ($pattern[$current++] === '{') {
+ $depth++;
+ }
+ }
+ }
+
+ return $current < $length ? $current : null;
+ }
+
+ /** @internal */
+ public static function flagsIsEqualTo(int $flags, int $otherFlags): bool
+ {
+ return (bool) ($flags & $otherFlags);
+ }
+}
diff --git a/vendor/laminas/laminas-stdlib/src/Guard/AllGuardsTrait.php b/vendor/laminas/laminas-stdlib/src/Guard/AllGuardsTrait.php
new file mode 100644
index 00000000..b5abe5a6
--- /dev/null
+++ b/vendor/laminas/laminas-stdlib/src/Guard/AllGuardsTrait.php
@@ -0,0 +1,15 @@
+metadata[$spec] = $value;
+ return $this;
+ }
+ if (! is_array($spec) && ! $spec instanceof Traversable) {
+ throw new Exception\InvalidArgumentException(sprintf(
+ 'Expected a string, array, or Traversable argument in first position; received "%s"',
+ get_debug_type($spec)
+ ));
+ }
+ foreach ($spec as $key => $value) {
+ $this->metadata[$key] = $value;
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve all metadata or a single metadatum as specified by key
+ *
+ * @param null|string|int $key
+ * @param null|mixed $default
+ * @throws Exception\InvalidArgumentException
+ * @return mixed
+ */
+ public function getMetadata($key = null, $default = null)
+ {
+ if (null === $key) {
+ return $this->metadata;
+ }
+
+ if (! is_scalar($key)) {
+ throw new Exception\InvalidArgumentException('Non-scalar argument provided for key');
+ }
+
+ if (array_key_exists($key, $this->metadata)) {
+ return $this->metadata[$key];
+ }
+
+ return $default;
+ }
+
+ /**
+ * Set message content
+ *
+ * @param mixed $value
+ * @return Message
+ */
+ public function setContent($value)
+ {
+ $this->content = $value;
+ return $this;
+ }
+
+ /**
+ * Get message content
+ *
+ * @return mixed
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * @return string
+ */
+ public function toString()
+ {
+ $request = '';
+ foreach ($this->getMetadata() as $key => $value) {
+ $request .= sprintf(
+ "%s: %s\r\n",
+ (string) $key,
+ (string) $value
+ );
+ }
+ $request .= "\r\n" . $this->getContent();
+ return $request;
+ }
+}
diff --git a/vendor/laminas/laminas-stdlib/src/MessageInterface.php b/vendor/laminas/laminas-stdlib/src/MessageInterface.php
new file mode 100644
index 00000000..71a48208
--- /dev/null
+++ b/vendor/laminas/laminas-stdlib/src/MessageInterface.php
@@ -0,0 +1,41 @@
+
+ * @template-implements ParametersInterface
+ */
+class Parameters extends PhpArrayObject implements ParametersInterface
+{
+ /**
+ * Constructor
+ *
+ * Enforces that we have an array, and enforces parameter access to array
+ * elements.
+ *
+ * @param array|null $values
+ */
+ public function __construct(?array $values = null)
+ {
+ if (null === $values) {
+ $values = [];
+ }
+ parent::__construct($values, ArrayObject::ARRAY_AS_PROPS);
+ }
+
+ /**
+ * Populate from native PHP array
+ *
+ * @param array $values
+ * @return void
+ */
+ public function fromArray(array $values)
+ {
+ $this->exchangeArray($values);
+ }
+
+ /**
+ * Populate from query string
+ *
+ * @param string $string
+ * @return void
+ */
+ public function fromString($string)
+ {
+ $array = [];
+ parse_str($string, $array);
+ $this->fromArray($array);
+ }
+
+ /**
+ * Serialize to native PHP array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return $this->getArrayCopy();
+ }
+
+ /**
+ * Serialize to query string
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return http_build_query($this->toArray());
+ }
+
+ /**
+ * Retrieve by key
+ *
+ * Returns null if the key does not exist.
+ *
+ * @param TKey $name
+ * @return TValue|null
+ */
+ #[ReturnTypeWillChange]
+ public function offsetGet($name)
+ {
+ if ($this->offsetExists($name)) {
+ return parent::offsetGet($name);
+ }
+
+ return null;
+ }
+
+ /**
+ * @template TDefault
+ * @param TKey $name
+ * @param TDefault $default optional default value
+ * @return TValue|TDefault|null
+ */
+ public function get($name, $default = null)
+ {
+ if ($this->offsetExists($name)) {
+ return parent::offsetGet($name);
+ }
+ return $default;
+ }
+
+ /**
+ * @param TKey $name
+ * @param TValue $value
+ * @return $this
+ */
+ public function set($name, $value)
+ {
+ $this[$name] = $value;
+ return $this;
+ }
+}
diff --git a/vendor/laminas/laminas-stdlib/src/ParametersInterface.php b/vendor/laminas/laminas-stdlib/src/ParametersInterface.php
new file mode 100644
index 00000000..9b9a0c4c
--- /dev/null
+++ b/vendor/laminas/laminas-stdlib/src/ParametersInterface.php
@@ -0,0 +1,82 @@
+
+ * @template-extends Traversable
+ */
+interface ParametersInterface extends ArrayAccess, Countable, Serializable, Traversable
+{
+ /**
+ * Constructor
+ *
+ * @param array|null $values
+ */
+ public function __construct(?array $values = null);
+
+ /**
+ * From array
+ *
+ * Allow deserialization from standard array
+ *
+ * @param array $values
+ * @return mixed
+ */
+ public function fromArray(array $values);
+
+ /**
+ * From string
+ *
+ * Allow deserialization from raw body; e.g., for PUT requests
+ *
+ * @param string $string
+ * @return mixed
+ */
+ public function fromString($string);
+
+ /**
+ * To array
+ *
+ * Allow serialization back to standard array
+ *
+ * @return array
+ */
+ public function toArray();
+
+ /**
+ * To string
+ *
+ * Allow serialization to query format; e.g., for PUT or POST requests
+ *
+ * @return string
+ */
+ public function toString();
+
+ /**
+ * @param TKey $name
+ * @param TValue|null $default
+ * @return mixed
+ */
+ public function get($name, $default = null);
+
+ /**
+ * @param TKey $name
+ * @param TValue $value
+ * @return ParametersInterface
+ */
+ public function set($name, $value);
+}
diff --git a/vendor/laminas/laminas-stdlib/src/PriorityList.php b/vendor/laminas/laminas-stdlib/src/PriorityList.php
new file mode 100644
index 00000000..8e2c6e53
--- /dev/null
+++ b/vendor/laminas/laminas-stdlib/src/PriorityList.php
@@ -0,0 +1,287 @@
+
+ */
+class PriorityList implements Iterator, Countable
+{
+ public const EXTR_DATA = 0x00000001;
+ public const EXTR_PRIORITY = 0x00000002;
+ public const EXTR_BOTH = 0x00000003;
+
+ /**
+ * Internal list of all items.
+ *
+ * @var array
+ */
+ protected $items = [];
+
+ /**
+ * Serial assigned to items to preserve LIFO.
+ *
+ * @var positive-int|0
+ */
+ protected $serial = 0;
+
+ // phpcs:disable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCapsProperty
+
+ /**
+ * Serial order mode
+ *
+ * @var integer
+ */
+ protected $isLIFO = 1;
+
+ // phpcs:enable
+
+ /**
+ * Internal counter to avoid usage of count().
+ *
+ * @var int
+ */
+ protected $count = 0;
+
+ /**
+ * Whether the list was already sorted.
+ *
+ * @var bool
+ */
+ protected $sorted = false;
+
+ /**
+ * Insert a new item.
+ *
+ * @param TKey $name
+ * @param TValue $value
+ * @param int $priority
+ * @return void
+ */
+ public function insert($name, mixed $value, $priority = 0)
+ {
+ if (! isset($this->items[$name])) {
+ $this->count++;
+ }
+
+ $this->sorted = false;
+
+ $this->items[$name] = [
+ 'data' => $value,
+ 'priority' => (int) $priority,
+ 'serial' => $this->serial++,
+ ];
+ }
+
+ /**
+ * @param TKey $name
+ * @param int $priority
+ * @return $this
+ * @throws Exception
+ */
+ public function setPriority($name, $priority)
+ {
+ if (! isset($this->items[$name])) {
+ throw new Exception("item $name not found");
+ }
+
+ $this->items[$name]['priority'] = (int) $priority;
+ $this->sorted = false;
+
+ return $this;
+ }
+
+ /**
+ * Remove a item.
+ *
+ * @param TKey $name
+ * @return void
+ */
+ public function remove($name)
+ {
+ if (isset($this->items[$name])) {
+ $this->count--;
+ }
+
+ unset($this->items[$name]);
+ }
+
+ /**
+ * Remove all items.
+ *
+ * @return void
+ */
+ public function clear()
+ {
+ $this->items = [];
+ $this->serial = 0;
+ $this->count = 0;
+ $this->sorted = false;
+ }
+
+ /**
+ * Get a item.
+ *
+ * @param TKey $name
+ * @return TValue|null
+ */
+ public function get($name)
+ {
+ if (! isset($this->items[$name])) {
+ return;
+ }
+
+ return $this->items[$name]['data'];
+ }
+
+ /**
+ * Sort all items.
+ *
+ * @return void
+ */
+ protected function sort()
+ {
+ if (! $this->sorted) {
+ uasort($this->items, [$this, 'compare']);
+ $this->sorted = true;
+ }
+ }
+
+ /**
+ * Compare the priority of two items.
+ *
+ * @param array $item1,
+ * @return int
+ */
+ protected function compare(array $item1, array $item2)
+ {
+ return $item1['priority'] === $item2['priority']
+ ? ($item1['serial'] > $item2['serial'] ? -1 : 1) * $this->isLIFO
+ : ($item1['priority'] > $item2['priority'] ? -1 : 1);
+ }
+
+ /**
+ * Get/Set serial order mode
+ *
+ * @param bool|null $flag
+ * @return bool
+ */
+ public function isLIFO($flag = null)
+ {
+ if ($flag !== null) {
+ $isLifo = $flag === true ? 1 : -1;
+
+ if ($isLifo !== $this->isLIFO) {
+ $this->isLIFO = $isLifo;
+ $this->sorted = false;
+ }
+ }
+
+ return 1 === $this->isLIFO;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ #[ReturnTypeWillChange]
+ public function rewind()
+ {
+ $this->sort();
+ reset($this->items);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ #[ReturnTypeWillChange]
+ public function current()
+ {
+ $this->sorted || $this->sort();
+ $node = current($this->items);
+
+ return $node ? $node['data'] : false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ #[ReturnTypeWillChange]
+ public function key()
+ {
+ $this->sorted || $this->sort();
+ return key($this->items);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ #[ReturnTypeWillChange]
+ public function next()
+ {
+ $node = next($this->items);
+
+ return $node ? $node['data'] : false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ #[ReturnTypeWillChange]
+ public function valid()
+ {
+ return current($this->items) !== false;
+ }
+
+ /**
+ * @return self
+ */
+ public function getIterator()
+ {
+ return clone $this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ #[ReturnTypeWillChange]
+ public function count()
+ {
+ return $this->count;
+ }
+
+ /**
+ * Return list as array
+ *
+ * @param int $flag
+ * @return array
+ */
+ public function toArray($flag = self::EXTR_DATA)
+ {
+ $this->sort();
+
+ if ($flag === self::EXTR_BOTH) {
+ return $this->items;
+ }
+
+ return array_map(
+ static fn($item) => $flag === self::EXTR_PRIORITY ? $item['priority'] : $item['data'],
+ $this->items
+ );
+ }
+}
diff --git a/vendor/laminas/laminas-stdlib/src/PriorityQueue.php b/vendor/laminas/laminas-stdlib/src/PriorityQueue.php
new file mode 100644
index 00000000..b4258023
--- /dev/null
+++ b/vendor/laminas/laminas-stdlib/src/PriorityQueue.php
@@ -0,0 +1,379 @@
+
+ */
+class PriorityQueue implements Countable, IteratorAggregate, Serializable
+{
+ public const EXTR_DATA = 0x00000001;
+ public const EXTR_PRIORITY = 0x00000002;
+ public const EXTR_BOTH = 0x00000003;
+
+ /**
+ * Inner queue class to use for iteration
+ *
+ * @var class-string<\SplPriorityQueue>
+ */
+ protected $queueClass = SplPriorityQueue::class;
+
+ /**
+ * Actual items aggregated in the priority queue. Each item is an array
+ * with keys "data" and "priority".
+ *
+ * @var list
+ */
+ protected $items = [];
+
+ /**
+ * Inner queue object
+ *
+ * @var \SplPriorityQueue|null
+ */
+ protected $queue;
+
+ /**
+ * Insert an item into the queue
+ *
+ * Priority defaults to 1 (low priority) if none provided.
+ *
+ * @param TValue $data
+ * @param TPriority $priority
+ * @return $this
+ */
+ public function insert($data, $priority = 1)
+ {
+ /** @psalm-var TPriority $priority */
+ $priority = (int) $priority;
+ $this->items[] = [
+ 'data' => $data,
+ 'priority' => $priority,
+ ];
+ $this->getQueue()->insert($data, $priority);
+ return $this;
+ }
+
+ /**
+ * Remove an item from the queue
+ *
+ * This is different than {@link extract()}; its purpose is to dequeue an
+ * item.
+ *
+ * This operation is potentially expensive, as it requires
+ * re-initialization and re-population of the inner queue.
+ *
+ * Note: this removes the first item matching the provided item found. If
+ * the same item has been added multiple times, it will not remove other
+ * instances.
+ *
+ * @return bool False if the item was not found, true otherwise.
+ */
+ public function remove(mixed $datum)
+ {
+ $found = false;
+ $key = null;
+ foreach ($this->items as $key => $item) {
+ if ($item['data'] === $datum) {
+ $found = true;
+ break;
+ }
+ }
+ if ($found && $key !== null) {
+ unset($this->items[$key]);
+ $this->queue = null;
+
+ if (! $this->isEmpty()) {
+ $queue = $this->getQueue();
+ foreach ($this->items as $item) {
+ $queue->insert($item['data'], $item['priority']);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Is the queue empty?
+ *
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ return 0 === $this->count();
+ }
+
+ /**
+ * How many items are in the queue?
+ *
+ * @return int
+ */
+ #[ReturnTypeWillChange]
+ public function count()
+ {
+ return count($this->items);
+ }
+
+ /**
+ * Peek at the top node in the queue, based on priority.
+ *
+ * @return TValue
+ */
+ public function top()
+ {
+ $queue = clone $this->getQueue();
+
+ return $queue->top();
+ }
+
+ /**
+ * Extract a node from the inner queue and sift up
+ *
+ * @return TValue
+ */
+ public function extract()
+ {
+ $value = $this->getQueue()->extract();
+
+ $keyToRemove = null;
+ $highestPriority = null;
+ foreach ($this->items as $key => $item) {
+ if ($item['data'] !== $value) {
+ continue;
+ }
+
+ if (null === $highestPriority) {
+ $highestPriority = $item['priority'];
+ $keyToRemove = $key;
+ continue;
+ }
+
+ if ($highestPriority >= $item['priority']) {
+ continue;
+ }
+
+ $highestPriority = $item['priority'];
+ $keyToRemove = $key;
+ }
+
+ if ($keyToRemove !== null) {
+ unset($this->items[$keyToRemove]);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Retrieve the inner iterator
+ *
+ * SplPriorityQueue acts as a heap, which typically implies that as items
+ * are iterated, they are also removed. This does not work for situations
+ * where the queue may be iterated multiple times. As such, this class
+ * aggregates the values, and also injects an SplPriorityQueue. This method
+ * retrieves the inner queue object, and clones it for purposes of
+ * iteration.
+ *
+ * @return \SplPriorityQueue
+ */
+ #[ReturnTypeWillChange]
+ public function getIterator()
+ {
+ $queue = $this->getQueue();
+ return clone $queue;
+ }
+
+ /**
+ * Serialize the data structure
+ *
+ * @return string
+ */
+ public function serialize()
+ {
+ return serialize($this->__serialize());
+ }
+
+ /**
+ * Magic method used for serializing of an instance.
+ *
+ * @return list
+ */
+ public function __serialize()
+ {
+ return $this->items;
+ }
+
+ /**
+ * Unserialize a string into a PriorityQueue object
+ *
+ * Serialization format is compatible with {@link SplPriorityQueue}
+ *
+ * @param string $data
+ * @return void
+ */
+ public function unserialize($data)
+ {
+ $toUnserialize = unserialize($data);
+ if (! is_array($toUnserialize)) {
+ throw new UnexpectedValueException(sprintf(
+ 'Cannot deserialize %s instance; corrupt serialization data',
+ self::class
+ ));
+ }
+
+ /** @psalm-var list $toUnserialize */
+
+ $this->__unserialize($toUnserialize);
+ }
+
+ /**
+ * Magic method used to rebuild an instance.
+ *
+ * @param list $data Data array.
+ * @return void
+ */
+ public function __unserialize($data)
+ {
+ foreach ($data as $item) {
+ $this->insert($item['data'], $item['priority']);
+ }
+ }
+
+ /**
+ * Serialize to an array
+ * By default, returns only the item data, and in the order registered (not
+ * sorted). You may provide one of the EXTR_* flags as an argument, allowing
+ * the ability to return priorities or both data and priority.
+ *
+ * @param int $flag
+ * @return array
+ * @psalm-return ($flag is self::EXTR_BOTH
+ * ? list
+ * : $flag is self::EXTR_PRIORITY
+ * ? list