Add PSR HTTP Message Interfaces and Dependencies

- Implemented StreamInterface, UploadedFileInterface, and UriInterface as per PSR standards.
- Added getallheaders function to retrieve HTTP headers in a compatible manner.
- Included LICENSE files for ralouphie/getallheaders and symfony/deprecation-contracts.
- Introduced function for triggering deprecation notices in Symfony.
This commit is contained in:
2025-12-28 12:44:00 +01:00
parent cf600ae727
commit cd264483f8
410 changed files with 60841 additions and 16 deletions

View File

@@ -0,0 +1,25 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitef786e44e6598dfaf0500750ff2a09f1::getLoader();

View File

@@ -0,0 +1,579 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* 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 <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @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<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array<string, list<string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var list<string>
*/
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<string, array<string, list<string>>>
*/
private $prefixesPsr0 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var array<string, bool>
*/
private $missingClasses = array();
/** @var string|null */
private $apcuPrefix;
/**
* @var array<string, self>
*/
private static $registeredLoaders = array();
/**
* @param string|null $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return list<string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return list<string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return array<string, string> Array of classname => path
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array<string, string> $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>|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>|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>|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>|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<string, self>
*/
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);
}
}

View File

@@ -0,0 +1,359 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* 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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
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<string>
*/
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<string>
*/
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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
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<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else {
self::$installed = array();
}
}
if (self::$installed !== array()) {
$installed[] = self::$installed;
}
return $installed;
}
}

View File

@@ -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.

View File

@@ -0,0 +1,270 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'ATFPP\\AI_Translate\\Deepl\\Deepl_AI_API_Client' => $baseDir . '/includes/ai-translate/Deepl/Deepl_AI_API_Client.php',
'ATFPP\\AI_Translate\\Deepl\\Deepl_AI_Service' => $baseDir . '/includes/ai-translate/Deepl/Deepl_AI_Service.php',
'ATFPP\\AI_Translate\\Deepl\\Deepl_AI_Text_Generation_Model' => $baseDir . '/includes/ai-translate/Deepl/Deepl_AI_Text_Generation_Model.php',
'ATFPP\\AI_Translate\\Google\\Google_AI_API_Client' => $baseDir . '/includes/ai-translate/Google/Google_AI_API_Client.php',
'ATFPP\\AI_Translate\\Google\\Google_AI_Service' => $baseDir . '/includes/ai-translate/Google/Google_AI_Service.php',
'ATFPP\\AI_Translate\\Google\\Google_AI_Text_Generation_Model' => $baseDir . '/includes/ai-translate/Google/Google_AI_Text_Generation_Model.php',
'ATFPP\\AI_Translate\\Google\\Types\\Safety_Setting' => $baseDir . '/includes/ai-translate/Google/Types/Safety_Setting.php',
'ATFPP\\AI_Translate\\OpenAI\\OpenAI_AI_Service' => $baseDir . '/includes/ai-translate/OpenAI/OpenAI_AI_Service.php',
'ATFPP\\AI_Translate\\OpenAI\\OpenAI_AI_Text_Generation_Model' => $baseDir . '/includes/ai-translate/OpenAI/OpenAI_AI_Text_Generation_Model.php',
'ATFPP\\AI_Translate\\Plugin_Main' => $baseDir . '/includes/ai-translate/Plugin_Main.php',
'ATFPP\\AI_Translate\\Plugin_Service_Container_Builder' => $baseDir . '/includes/ai-translate/Plugin_Service_Container_Builder.php',
'ATFPP\\AI_Translate\\Services\\API\\Candidates_Stream_Processor' => $baseDir . '/includes/ai-translate/Services/API/Candidates_Stream_Processor.php',
'ATFPP\\AI_Translate\\Services\\API\\Enums\\AI_Capability' => $baseDir . '/includes/ai-translate/Services/API/Enums/AI_Capability.php',
'ATFPP\\AI_Translate\\Services\\API\\Enums\\Abstract_Enum' => $baseDir . '/includes/ai-translate/Services/API/Enums/Abstract_Enum.php',
'ATFPP\\AI_Translate\\Services\\API\\Enums\\Content_Role' => $baseDir . '/includes/ai-translate/Services/API/Enums/Content_Role.php',
'ATFPP\\AI_Translate\\Services\\API\\Enums\\Contracts\\Enum' => $baseDir . '/includes/ai-translate/Services/API/Enums/Contracts/Enum.php',
'ATFPP\\AI_Translate\\Services\\API\\Enums\\Modality' => $baseDir . '/includes/ai-translate/Services/API/Enums/Modality.php',
'ATFPP\\AI_Translate\\Services\\API\\Enums\\Service_Type' => $baseDir . '/includes/ai-translate/Services/API/Enums/Service_Type.php',
'ATFPP\\AI_Translate\\Services\\API\\Helpers' => $baseDir . '/includes/ai-translate/Services/API/Helpers.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Blob' => $baseDir . '/includes/ai-translate/Services/API/Types/Blob.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Candidate' => $baseDir . '/includes/ai-translate/Services/API/Types/Candidate.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Candidates' => $baseDir . '/includes/ai-translate/Services/API/Types/Candidates.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Content' => $baseDir . '/includes/ai-translate/Services/API/Types/Content.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Contracts\\Part' => $baseDir . '/includes/ai-translate/Services/API/Types/Contracts/Part.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Contracts\\Tool' => $baseDir . '/includes/ai-translate/Services/API/Types/Contracts/Tool.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\History' => $baseDir . '/includes/ai-translate/Services/API/Types/History.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\History_Entry' => $baseDir . '/includes/ai-translate/Services/API/Types/History_Entry.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Model_Metadata' => $baseDir . '/includes/ai-translate/Services/API/Types/Model_Metadata.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Parts' => $baseDir . '/includes/ai-translate/Services/API/Types/Parts.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Parts\\Abstract_Part' => $baseDir . '/includes/ai-translate/Services/API/Types/Parts/Abstract_Part.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Parts\\Function_Call_Part' => $baseDir . '/includes/ai-translate/Services/API/Types/Parts/Function_Call_Part.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Parts\\Function_Response_Part' => $baseDir . '/includes/ai-translate/Services/API/Types/Parts/Function_Response_Part.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Parts\\Text_Part' => $baseDir . '/includes/ai-translate/Services/API/Types/Parts/Text_Part.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Service_Metadata' => $baseDir . '/includes/ai-translate/Services/API/Types/Service_Metadata.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Text_Generation_Config' => $baseDir . '/includes/ai-translate/Services/API/Types/Text_Generation_Config.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Tool_Config' => $baseDir . '/includes/ai-translate/Services/API/Types/Tool_Config.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Tools' => $baseDir . '/includes/ai-translate/Services/API/Types/Tools.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Tools\\Abstract_Tool' => $baseDir . '/includes/ai-translate/Services/API/Types/Tools/Abstract_Tool.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Tools\\Function_Declarations_Tool' => $baseDir . '/includes/ai-translate/Services/API/Types/Tools/Function_Declarations_Tool.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Tools\\Web_Search_Tool' => $baseDir . '/includes/ai-translate/Services/API/Types/Tools/Web_Search_Tool.php',
'ATFPP\\AI_Translate\\Services\\Authentication\\API_Key_Authentication' => $baseDir . '/includes/ai-translate/Services/Authentication/API_Key_Authentication.php',
'ATFPP\\AI_Translate\\Services\\Base\\Abstract_AI_Model' => $baseDir . '/includes/ai-translate/Services/Base/Abstract_AI_Model.php',
'ATFPP\\AI_Translate\\Services\\Base\\Abstract_AI_Service' => $baseDir . '/includes/ai-translate/Services/Base/Abstract_AI_Service.php',
'ATFPP\\AI_Translate\\Services\\Base\\Abstract_Generation_Config' => $baseDir . '/includes/ai-translate/Services/Base/Abstract_Generation_Config.php',
'ATFPP\\AI_Translate\\Services\\Base\\Generic_AI_API_Client' => $baseDir . '/includes/ai-translate/Services/Base/Generic_AI_API_Client.php',
'ATFPP\\AI_Translate\\Services\\Base\\OpenAI_Compatible_AI_Text_Generation_Model' => $baseDir . '/includes/ai-translate/Services/Base/OpenAI_Compatible_AI_Text_Generation_Model.php',
'ATFPP\\AI_Translate\\Services\\Cache\\Service_Request_Cache' => $baseDir . '/includes/ai-translate/Services/Cache/Service_Request_Cache.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\Authentication' => $baseDir . '/includes/ai-translate/Services/Contracts/Authentication.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\Generation_Config' => $baseDir . '/includes/ai-translate/Services/Contracts/Generation_Config.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\Generative_AI_API_Client' => $baseDir . '/includes/ai-translate/Services/Contracts/Generative_AI_API_Client.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\Generative_AI_Model' => $baseDir . '/includes/ai-translate/Services/Contracts/Generative_AI_Model.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\Generative_AI_Service' => $baseDir . '/includes/ai-translate/Services/Contracts/Generative_AI_Service.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\With_API_Client' => $baseDir . '/includes/ai-translate/Services/Contracts/With_API_Client.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\With_Function_Calling' => $baseDir . '/includes/ai-translate/Services/Contracts/With_Function_Calling.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\With_JSON_Schema' => $baseDir . '/includes/ai-translate/Services/Contracts/With_JSON_Schema.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\With_Multimodal_Input' => $baseDir . '/includes/ai-translate/Services/Contracts/With_Multimodal_Input.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\With_Multimodal_Output' => $baseDir . '/includes/ai-translate/Services/Contracts/With_Multimodal_Output.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\With_Text_Generation' => $baseDir . '/includes/ai-translate/Services/Contracts/With_Text_Generation.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\With_Web_Search' => $baseDir . '/includes/ai-translate/Services/Contracts/With_Web_Search.php',
'ATFPP\\AI_Translate\\Services\\Decorators\\AI_Service_Decorator' => $baseDir . '/includes/ai-translate/Services/Decorators/AI_Service_Decorator.php',
'ATFPP\\AI_Translate\\Services\\Exception\\Generative_AI_Exception' => $baseDir . '/includes/ai-translate/Services/Exception/Generative_AI_Exception.php',
'ATFPP\\AI_Translate\\Services\\HTTP\\Contracts\\Stream_Request_Handler' => $baseDir . '/includes/ai-translate/Services/HTTP/Contracts/Stream_Request_Handler.php',
'ATFPP\\AI_Translate\\Services\\HTTP\\Contracts\\With_Stream' => $baseDir . '/includes/ai-translate/Services/HTTP/Contracts/With_Stream.php',
'ATFPP\\AI_Translate\\Services\\HTTP\\HTTP_With_Streams' => $baseDir . '/includes/ai-translate/Services/HTTP/HTTP_With_Streams.php',
'ATFPP\\AI_Translate\\Services\\HTTP\\Stream_Response' => $baseDir . '/includes/ai-translate/Services/HTTP/Stream_Response.php',
'ATFPP\\AI_Translate\\Services\\Options\\Option_Encrypter' => $baseDir . '/includes/ai-translate/Services/Options/Option_Encrypter.php',
'ATFPP\\AI_Translate\\Services\\Service_Registration' => $baseDir . '/includes/ai-translate/Services/Service_Registration.php',
'ATFPP\\AI_Translate\\Services\\Service_Registration_Context' => $baseDir . '/includes/ai-translate/Services/Service_Registration_Context.php',
'ATFPP\\AI_Translate\\Services\\Services_API' => $baseDir . '/includes/ai-translate/Services/Services_API.php',
'ATFPP\\AI_Translate\\Services\\Services_API_Instance' => $baseDir . '/includes/ai-translate/Services/Services_API_Instance.php',
'ATFPP\\AI_Translate\\Services\\Services_Loader' => $baseDir . '/includes/ai-translate/Services/Services_Loader.php',
'ATFPP\\AI_Translate\\Services\\Services_Service_Container_Builder' => $baseDir . '/includes/ai-translate/Services/Services_Service_Container_Builder.php',
'ATFPP\\AI_Translate\\Services\\Traits\\Generative_AI_API_Client_Trait' => $baseDir . '/includes/ai-translate/Services/Traits/Generative_AI_API_Client_Trait.php',
'ATFPP\\AI_Translate\\Services\\Traits\\Model_Param_System_Instruction_Trait' => $baseDir . '/includes/ai-translate/Services/Traits/Model_Param_System_Instruction_Trait.php',
'ATFPP\\AI_Translate\\Services\\Traits\\Model_Param_Text_Generation_Config_Trait' => $baseDir . '/includes/ai-translate/Services/Traits/Model_Param_Text_Generation_Config_Trait.php',
'ATFPP\\AI_Translate\\Services\\Traits\\Model_Param_Tool_Config_Trait' => $baseDir . '/includes/ai-translate/Services/Traits/Model_Param_Tool_Config_Trait.php',
'ATFPP\\AI_Translate\\Services\\Traits\\Model_Param_Tools_Trait' => $baseDir . '/includes/ai-translate/Services/Traits/Model_Param_Tools_Trait.php',
'ATFPP\\AI_Translate\\Services\\Traits\\OpenAI_Compatible_Text_Generation_With_Function_Calling_Trait' => $baseDir . '/includes/ai-translate/Services/Traits/OpenAI_Compatible_Text_Generation_With_Function_Calling_Trait.php',
'ATFPP\\AI_Translate\\Services\\Traits\\With_API_Client_Trait' => $baseDir . '/includes/ai-translate/Services/Traits/With_API_Client_Trait.php',
'ATFPP\\AI_Translate\\Services\\Traits\\With_Text_Generation_Trait' => $baseDir . '/includes/ai-translate/Services/Traits/With_Text_Generation_Trait.php',
'ATFPP\\AI_Translate\\Services\\Util\\AI_Capabilities' => $baseDir . '/includes/ai-translate/Services/Util/AI_Capabilities.php',
'ATFPP\\AI_Translate\\Services\\Util\\Data_Encryption' => $baseDir . '/includes/ai-translate/Services/Util/Data_Encryption.php',
'ATFPP\\AI_Translate\\Services\\Util\\Formatter' => $baseDir . '/includes/ai-translate/Services/Util/Formatter.php',
'ATFPP\\AI_Translate\\Services\\Util\\Strings' => $baseDir . '/includes/ai-translate/Services/Util/Strings.php',
'ATFPP\\AI_Translate\\Services\\Util\\Transformer' => $baseDir . '/includes/ai-translate/Services/Util/Transformer.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Capabilities\\Abstract_Capability' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Capabilities/Abstract_Capability.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Capabilities\\Base_Capability' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Capabilities/Base_Capability.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Capabilities\\Capability_Container' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Capabilities/Capability_Container.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Capabilities\\Capability_Controller' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Capabilities/Capability_Controller.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Capabilities\\Capability_Filters' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Capabilities/Capability_Filters.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Capabilities\\Contracts\\Capability' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Capabilities/Contracts/Capability.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Capabilities\\Meta_Capability' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Capabilities/Meta_Capability.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Array_Key_Value_Repository' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Array_Key_Value_Repository.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Array_Registry' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Array_Registry.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\Arrayable' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/Arrayable.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\Collection' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/Collection.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\Container' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/Container.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\Container_Readonly' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/Container_Readonly.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\Hook_Registrar' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/Hook_Registrar.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\Key_Value' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/Key_Value.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\Key_Value_Repository' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/Key_Value_Repository.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\Registry' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/Registry.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\With_Capabilities' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/With_Capabilities.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\With_Hooks' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/With_Hooks.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\With_Key' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/With_Key.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\With_Registration_Args' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/With_Registration_Args.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Current_User' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Current_User.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Exception\\Invalid_Type_Exception' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Exception/Invalid_Type_Exception.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Exception\\Not_Found_Exception' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Exception/Not_Found_Exception.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Exception\\WP_Error_Exception' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Exception/WP_Error_Exception.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Generic_Key_Value' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Generic_Key_Value.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Input' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Input.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Mutable_Input' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Mutable_Input.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Network_Env' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Network_Env.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Network_Runner' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Network_Runner.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Plugin_Env' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Plugin_Env.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Service_Container' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Service_Container.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Site_Env' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Site_Env.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Traits\\Cast_Value_By_Type' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Traits/Cast_Value_By_Type.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Traits\\Maybe_Throw' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/General/Traits/Maybe_Throw.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Contracts\\Request' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Contracts/Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Contracts\\Request_Handler' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Contracts/Request_Handler.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Contracts\\Response' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Contracts/Response.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Delete_Request' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Delete_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Exception\\Multiple_Requests_Exception' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Exception/Multiple_Requests_Exception.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Exception\\Request_Exception' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Exception/Request_Exception.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Generic_Request' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Generic_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Generic_Response' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Generic_Response.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Get_Request' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Get_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\HTTP' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/HTTP.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\JSON_Patch_Request' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/JSON_Patch_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\JSON_Post_Request' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/JSON_Post_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\JSON_Put_Request' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/JSON_Put_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\JSON_Request' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/JSON_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\JSON_Response' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/JSON_Response.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Patch_Request' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Patch_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Post_Request' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Post_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Put_Request' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Put_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Traits\\Sanitize_Headers' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Traits/Sanitize_Headers.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Abstract_Entity_Key_Value' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Meta/Abstract_Entity_Key_Value.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Contracts\\Entity_Key_Value' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Meta/Contracts/Entity_Key_Value.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Contracts\\Entity_Key_Value_Repository' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Meta/Contracts/Entity_Key_Value_Repository.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Contracts\\With_Entity_ID' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Meta/Contracts/With_Entity_ID.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Contracts\\With_Single' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Meta/Contracts/With_Single.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Entity_Aware_Meta_Container' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Meta/Entity_Aware_Meta_Container.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Entity_Aware_Meta_Key' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Meta/Entity_Aware_Meta_Key.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Meta_Container' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Meta/Meta_Container.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Meta_Hook_Registrar' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Meta/Meta_Hook_Registrar.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Meta_Key' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Meta/Meta_Key.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Meta_Registry' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Meta/Meta_Registry.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Meta_Repository' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Meta/Meta_Repository.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Options\\Contracts\\With_Autoload_Config' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Options/Contracts/With_Autoload_Config.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Options\\Option' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Options/Option.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Options\\Option_Container' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Options/Option_Container.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Options\\Option_Hook_Registrar' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Options/Option_Hook_Registrar.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Options\\Option_Registry' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Options/Option_Registry.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Options\\Option_Repository' => $vendorDir . '/felixarntz/wp-oop-plugin-lib/src/Options/Option_Repository.php',
'GuzzleHttp\\BodySummarizer' => $vendorDir . '/guzzlehttp/guzzle/src/BodySummarizer.php',
'GuzzleHttp\\BodySummarizerInterface' => $vendorDir . '/guzzlehttp/guzzle/src/BodySummarizerInterface.php',
'GuzzleHttp\\Client' => $vendorDir . '/guzzlehttp/guzzle/src/Client.php',
'GuzzleHttp\\ClientInterface' => $vendorDir . '/guzzlehttp/guzzle/src/ClientInterface.php',
'GuzzleHttp\\ClientTrait' => $vendorDir . '/guzzlehttp/guzzle/src/ClientTrait.php',
'GuzzleHttp\\Cookie\\CookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/CookieJar.php',
'GuzzleHttp\\Cookie\\CookieJarInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php',
'GuzzleHttp\\Cookie\\FileCookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php',
'GuzzleHttp\\Cookie\\SessionCookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php',
'GuzzleHttp\\Cookie\\SetCookie' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/SetCookie.php',
'GuzzleHttp\\Exception\\BadResponseException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/BadResponseException.php',
'GuzzleHttp\\Exception\\ClientException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ClientException.php',
'GuzzleHttp\\Exception\\ConnectException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ConnectException.php',
'GuzzleHttp\\Exception\\GuzzleException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/GuzzleException.php',
'GuzzleHttp\\Exception\\InvalidArgumentException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php',
'GuzzleHttp\\Exception\\RequestException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/RequestException.php',
'GuzzleHttp\\Exception\\ServerException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ServerException.php',
'GuzzleHttp\\Exception\\TooManyRedirectsException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php',
'GuzzleHttp\\Exception\\TransferException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/TransferException.php',
'GuzzleHttp\\HandlerStack' => $vendorDir . '/guzzlehttp/guzzle/src/HandlerStack.php',
'GuzzleHttp\\Handler\\CurlFactory' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlFactory.php',
'GuzzleHttp\\Handler\\CurlFactoryInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php',
'GuzzleHttp\\Handler\\CurlHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlHandler.php',
'GuzzleHttp\\Handler\\CurlMultiHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php',
'GuzzleHttp\\Handler\\EasyHandle' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/EasyHandle.php',
'GuzzleHttp\\Handler\\HeaderProcessor' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php',
'GuzzleHttp\\Handler\\MockHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/MockHandler.php',
'GuzzleHttp\\Handler\\Proxy' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/Proxy.php',
'GuzzleHttp\\Handler\\StreamHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/StreamHandler.php',
'GuzzleHttp\\MessageFormatter' => $vendorDir . '/guzzlehttp/guzzle/src/MessageFormatter.php',
'GuzzleHttp\\MessageFormatterInterface' => $vendorDir . '/guzzlehttp/guzzle/src/MessageFormatterInterface.php',
'GuzzleHttp\\Middleware' => $vendorDir . '/guzzlehttp/guzzle/src/Middleware.php',
'GuzzleHttp\\Pool' => $vendorDir . '/guzzlehttp/guzzle/src/Pool.php',
'GuzzleHttp\\PrepareBodyMiddleware' => $vendorDir . '/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php',
'GuzzleHttp\\Promise\\AggregateException' => $vendorDir . '/guzzlehttp/promises/src/AggregateException.php',
'GuzzleHttp\\Promise\\CancellationException' => $vendorDir . '/guzzlehttp/promises/src/CancellationException.php',
'GuzzleHttp\\Promise\\Coroutine' => $vendorDir . '/guzzlehttp/promises/src/Coroutine.php',
'GuzzleHttp\\Promise\\Create' => $vendorDir . '/guzzlehttp/promises/src/Create.php',
'GuzzleHttp\\Promise\\Each' => $vendorDir . '/guzzlehttp/promises/src/Each.php',
'GuzzleHttp\\Promise\\EachPromise' => $vendorDir . '/guzzlehttp/promises/src/EachPromise.php',
'GuzzleHttp\\Promise\\FulfilledPromise' => $vendorDir . '/guzzlehttp/promises/src/FulfilledPromise.php',
'GuzzleHttp\\Promise\\Is' => $vendorDir . '/guzzlehttp/promises/src/Is.php',
'GuzzleHttp\\Promise\\Promise' => $vendorDir . '/guzzlehttp/promises/src/Promise.php',
'GuzzleHttp\\Promise\\PromiseInterface' => $vendorDir . '/guzzlehttp/promises/src/PromiseInterface.php',
'GuzzleHttp\\Promise\\PromisorInterface' => $vendorDir . '/guzzlehttp/promises/src/PromisorInterface.php',
'GuzzleHttp\\Promise\\RejectedPromise' => $vendorDir . '/guzzlehttp/promises/src/RejectedPromise.php',
'GuzzleHttp\\Promise\\RejectionException' => $vendorDir . '/guzzlehttp/promises/src/RejectionException.php',
'GuzzleHttp\\Promise\\TaskQueue' => $vendorDir . '/guzzlehttp/promises/src/TaskQueue.php',
'GuzzleHttp\\Promise\\TaskQueueInterface' => $vendorDir . '/guzzlehttp/promises/src/TaskQueueInterface.php',
'GuzzleHttp\\Promise\\Utils' => $vendorDir . '/guzzlehttp/promises/src/Utils.php',
'GuzzleHttp\\Psr7\\AppendStream' => $vendorDir . '/guzzlehttp/psr7/src/AppendStream.php',
'GuzzleHttp\\Psr7\\BufferStream' => $vendorDir . '/guzzlehttp/psr7/src/BufferStream.php',
'GuzzleHttp\\Psr7\\CachingStream' => $vendorDir . '/guzzlehttp/psr7/src/CachingStream.php',
'GuzzleHttp\\Psr7\\DroppingStream' => $vendorDir . '/guzzlehttp/psr7/src/DroppingStream.php',
'GuzzleHttp\\Psr7\\Exception\\MalformedUriException' => $vendorDir . '/guzzlehttp/psr7/src/Exception/MalformedUriException.php',
'GuzzleHttp\\Psr7\\FnStream' => $vendorDir . '/guzzlehttp/psr7/src/FnStream.php',
'GuzzleHttp\\Psr7\\Header' => $vendorDir . '/guzzlehttp/psr7/src/Header.php',
'GuzzleHttp\\Psr7\\HttpFactory' => $vendorDir . '/guzzlehttp/psr7/src/HttpFactory.php',
'GuzzleHttp\\Psr7\\InflateStream' => $vendorDir . '/guzzlehttp/psr7/src/InflateStream.php',
'GuzzleHttp\\Psr7\\LazyOpenStream' => $vendorDir . '/guzzlehttp/psr7/src/LazyOpenStream.php',
'GuzzleHttp\\Psr7\\LimitStream' => $vendorDir . '/guzzlehttp/psr7/src/LimitStream.php',
'GuzzleHttp\\Psr7\\Message' => $vendorDir . '/guzzlehttp/psr7/src/Message.php',
'GuzzleHttp\\Psr7\\MessageTrait' => $vendorDir . '/guzzlehttp/psr7/src/MessageTrait.php',
'GuzzleHttp\\Psr7\\MimeType' => $vendorDir . '/guzzlehttp/psr7/src/MimeType.php',
'GuzzleHttp\\Psr7\\MultipartStream' => $vendorDir . '/guzzlehttp/psr7/src/MultipartStream.php',
'GuzzleHttp\\Psr7\\NoSeekStream' => $vendorDir . '/guzzlehttp/psr7/src/NoSeekStream.php',
'GuzzleHttp\\Psr7\\PumpStream' => $vendorDir . '/guzzlehttp/psr7/src/PumpStream.php',
'GuzzleHttp\\Psr7\\Query' => $vendorDir . '/guzzlehttp/psr7/src/Query.php',
'GuzzleHttp\\Psr7\\Request' => $vendorDir . '/guzzlehttp/psr7/src/Request.php',
'GuzzleHttp\\Psr7\\Response' => $vendorDir . '/guzzlehttp/psr7/src/Response.php',
'GuzzleHttp\\Psr7\\Rfc7230' => $vendorDir . '/guzzlehttp/psr7/src/Rfc7230.php',
'GuzzleHttp\\Psr7\\ServerRequest' => $vendorDir . '/guzzlehttp/psr7/src/ServerRequest.php',
'GuzzleHttp\\Psr7\\Stream' => $vendorDir . '/guzzlehttp/psr7/src/Stream.php',
'GuzzleHttp\\Psr7\\StreamDecoratorTrait' => $vendorDir . '/guzzlehttp/psr7/src/StreamDecoratorTrait.php',
'GuzzleHttp\\Psr7\\StreamWrapper' => $vendorDir . '/guzzlehttp/psr7/src/StreamWrapper.php',
'GuzzleHttp\\Psr7\\UploadedFile' => $vendorDir . '/guzzlehttp/psr7/src/UploadedFile.php',
'GuzzleHttp\\Psr7\\Uri' => $vendorDir . '/guzzlehttp/psr7/src/Uri.php',
'GuzzleHttp\\Psr7\\UriComparator' => $vendorDir . '/guzzlehttp/psr7/src/UriComparator.php',
'GuzzleHttp\\Psr7\\UriNormalizer' => $vendorDir . '/guzzlehttp/psr7/src/UriNormalizer.php',
'GuzzleHttp\\Psr7\\UriResolver' => $vendorDir . '/guzzlehttp/psr7/src/UriResolver.php',
'GuzzleHttp\\Psr7\\Utils' => $vendorDir . '/guzzlehttp/psr7/src/Utils.php',
'GuzzleHttp\\RedirectMiddleware' => $vendorDir . '/guzzlehttp/guzzle/src/RedirectMiddleware.php',
'GuzzleHttp\\RequestOptions' => $vendorDir . '/guzzlehttp/guzzle/src/RequestOptions.php',
'GuzzleHttp\\RetryMiddleware' => $vendorDir . '/guzzlehttp/guzzle/src/RetryMiddleware.php',
'GuzzleHttp\\TransferStats' => $vendorDir . '/guzzlehttp/guzzle/src/TransferStats.php',
'GuzzleHttp\\Utils' => $vendorDir . '/guzzlehttp/guzzle/src/Utils.php',
'Psr\\Http\\Client\\ClientExceptionInterface' => $vendorDir . '/psr/http-client/src/ClientExceptionInterface.php',
'Psr\\Http\\Client\\ClientInterface' => $vendorDir . '/psr/http-client/src/ClientInterface.php',
'Psr\\Http\\Client\\NetworkExceptionInterface' => $vendorDir . '/psr/http-client/src/NetworkExceptionInterface.php',
'Psr\\Http\\Client\\RequestExceptionInterface' => $vendorDir . '/psr/http-client/src/RequestExceptionInterface.php',
'Psr\\Http\\Message\\MessageInterface' => $vendorDir . '/psr/http-message/src/MessageInterface.php',
'Psr\\Http\\Message\\RequestFactoryInterface' => $vendorDir . '/psr/http-factory/src/RequestFactoryInterface.php',
'Psr\\Http\\Message\\RequestInterface' => $vendorDir . '/psr/http-message/src/RequestInterface.php',
'Psr\\Http\\Message\\ResponseFactoryInterface' => $vendorDir . '/psr/http-factory/src/ResponseFactoryInterface.php',
'Psr\\Http\\Message\\ResponseInterface' => $vendorDir . '/psr/http-message/src/ResponseInterface.php',
'Psr\\Http\\Message\\ServerRequestFactoryInterface' => $vendorDir . '/psr/http-factory/src/ServerRequestFactoryInterface.php',
'Psr\\Http\\Message\\ServerRequestInterface' => $vendorDir . '/psr/http-message/src/ServerRequestInterface.php',
'Psr\\Http\\Message\\StreamFactoryInterface' => $vendorDir . '/psr/http-factory/src/StreamFactoryInterface.php',
'Psr\\Http\\Message\\StreamInterface' => $vendorDir . '/psr/http-message/src/StreamInterface.php',
'Psr\\Http\\Message\\UploadedFileFactoryInterface' => $vendorDir . '/psr/http-factory/src/UploadedFileFactoryInterface.php',
'Psr\\Http\\Message\\UploadedFileInterface' => $vendorDir . '/psr/http-message/src/UploadedFileInterface.php',
'Psr\\Http\\Message\\UriFactoryInterface' => $vendorDir . '/psr/http-factory/src/UriFactoryInterface.php',
'Psr\\Http\\Message\\UriInterface' => $vendorDir . '/psr/http-message/src/UriInterface.php',
);

View File

@@ -0,0 +1,12 @@
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
);

View File

@@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);

View File

@@ -0,0 +1,17 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\' => array($vendorDir . '/felixarntz/wp-oop-plugin-lib/src'),
'ATFPP\\AI_Translate\\PHPUnit\\Includes\\' => array($baseDir . '/tests/phpunit/includes'),
'ATFPP\\AI_Translate\\' => array($baseDir . '/includes'),
);

View File

@@ -0,0 +1,51 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInitef786e44e6598dfaf0500750ff2a09f1
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInitef786e44e6598dfaf0500750ff2a09f1', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInitef786e44e6598dfaf0500750ff2a09f1', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitef786e44e6598dfaf0500750ff2a09f1::getInitializer($loader));
$loader->setClassMapAuthoritative(true);
$loader->register(true);
$filesToLoad = \Composer\Autoload\ComposerStaticInitef786e44e6598dfaf0500750ff2a09f1::$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;
}
}

View File

@@ -0,0 +1,344 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInitef786e44e6598dfaf0500750ff2a09f1
{
public static $files = array (
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
);
public static $prefixLengthsPsr4 = array (
'P' =>
array (
'Psr\\Http\\Message\\' => 17,
'Psr\\Http\\Client\\' => 16,
),
'G' =>
array (
'GuzzleHttp\\Psr7\\' => 16,
'GuzzleHttp\\Promise\\' => 19,
'GuzzleHttp\\' => 11,
),
'F' =>
array (
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\' => 30,
'ATFPP\\AI_Translate\\PHPUnit\\Includes\\' => 41,
'ATFPP\\AI_Translate\\' => 24,
),
);
public static $prefixDirsPsr4 = array (
'Psr\\Http\\Message\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-factory/src',
1 => __DIR__ . '/..' . '/psr/http-message/src',
),
'Psr\\Http\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-client/src',
),
'GuzzleHttp\\Psr7\\' =>
array (
0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
),
'GuzzleHttp\\Promise\\' =>
array (
0 => __DIR__ . '/..' . '/guzzlehttp/promises/src',
),
'GuzzleHttp\\' =>
array (
0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src',
),
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\' =>
array (
0 => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src',
),
'ATFPP\\AI_Translate\\PHPUnit\\Includes\\' =>
array (
0 => __DIR__ . '/../..' . '/tests/phpunit/includes',
),
'ATFPP\\AI_Translate\\' =>
array (
0 => __DIR__ . '/../..' . '/includes',
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'ATFPP\\AI_Translate\\Deepl\\Deepl_AI_API_Client' => __DIR__ . '/../..' . '/includes/ai-translate/Deepl/Deepl_AI_API_Client.php',
'ATFPP\\AI_Translate\\Deepl\\Deepl_AI_Service' => __DIR__ . '/../..' . '/includes/ai-translate/Deepl/Deepl_AI_Service.php',
'ATFPP\\AI_Translate\\Deepl\\Deepl_AI_Text_Generation_Model' => __DIR__ . '/../..' . '/includes/ai-translate/Deepl/Deepl_AI_Text_Generation_Model.php',
'ATFPP\\AI_Translate\\Google\\Google_AI_API_Client' => __DIR__ . '/../..' . '/includes/ai-translate/Google/Google_AI_API_Client.php',
'ATFPP\\AI_Translate\\Google\\Google_AI_Service' => __DIR__ . '/../..' . '/includes/ai-translate/Google/Google_AI_Service.php',
'ATFPP\\AI_Translate\\Google\\Google_AI_Text_Generation_Model' => __DIR__ . '/../..' . '/includes/ai-translate/Google/Google_AI_Text_Generation_Model.php',
'ATFPP\\AI_Translate\\Google\\Types\\Safety_Setting' => __DIR__ . '/../..' . '/includes/ai-translate/Google/Types/Safety_Setting.php',
'ATFPP\\AI_Translate\\OpenAI\\OpenAI_AI_Service' => __DIR__ . '/../..' . '/includes/ai-translate/OpenAI/OpenAI_AI_Service.php',
'ATFPP\\AI_Translate\\OpenAI\\OpenAI_AI_Text_Generation_Model' => __DIR__ . '/../..' . '/includes/ai-translate/OpenAI/OpenAI_AI_Text_Generation_Model.php',
'ATFPP\\AI_Translate\\Plugin_Main' => __DIR__ . '/../..' . '/includes/ai-translate/Plugin_Main.php',
'ATFPP\\AI_Translate\\Plugin_Service_Container_Builder' => __DIR__ . '/../..' . '/includes/ai-translate/Plugin_Service_Container_Builder.php',
'ATFPP\\AI_Translate\\Services\\API\\Candidates_Stream_Processor' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Candidates_Stream_Processor.php',
'ATFPP\\AI_Translate\\Services\\API\\Enums\\AI_Capability' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Enums/AI_Capability.php',
'ATFPP\\AI_Translate\\Services\\API\\Enums\\Abstract_Enum' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Enums/Abstract_Enum.php',
'ATFPP\\AI_Translate\\Services\\API\\Enums\\Content_Role' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Enums/Content_Role.php',
'ATFPP\\AI_Translate\\Services\\API\\Enums\\Contracts\\Enum' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Enums/Contracts/Enum.php',
'ATFPP\\AI_Translate\\Services\\API\\Enums\\Modality' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Enums/Modality.php',
'ATFPP\\AI_Translate\\Services\\API\\Enums\\Service_Type' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Enums/Service_Type.php',
'ATFPP\\AI_Translate\\Services\\API\\Helpers' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Helpers.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Blob' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Blob.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Candidate' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Candidate.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Candidates' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Candidates.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Content' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Content.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Contracts\\Part' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Contracts/Part.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Contracts\\Tool' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Contracts/Tool.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\History' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/History.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\History_Entry' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/History_Entry.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Model_Metadata' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Model_Metadata.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Parts' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Parts.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Parts\\Abstract_Part' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Parts/Abstract_Part.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Parts\\Function_Call_Part' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Parts/Function_Call_Part.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Parts\\Function_Response_Part' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Parts/Function_Response_Part.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Parts\\Text_Part' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Parts/Text_Part.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Service_Metadata' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Service_Metadata.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Text_Generation_Config' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Text_Generation_Config.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Tool_Config' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Tool_Config.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Tools' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Tools.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Tools\\Abstract_Tool' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Tools/Abstract_Tool.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Tools\\Function_Declarations_Tool' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Tools/Function_Declarations_Tool.php',
'ATFPP\\AI_Translate\\Services\\API\\Types\\Tools\\Web_Search_Tool' => __DIR__ . '/../..' . '/includes/ai-translate/Services/API/Types/Tools/Web_Search_Tool.php',
'ATFPP\\AI_Translate\\Services\\Authentication\\API_Key_Authentication' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Authentication/API_Key_Authentication.php',
'ATFPP\\AI_Translate\\Services\\Base\\Abstract_AI_Model' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Base/Abstract_AI_Model.php',
'ATFPP\\AI_Translate\\Services\\Base\\Abstract_AI_Service' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Base/Abstract_AI_Service.php',
'ATFPP\\AI_Translate\\Services\\Base\\Abstract_Generation_Config' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Base/Abstract_Generation_Config.php',
'ATFPP\\AI_Translate\\Services\\Base\\Generic_AI_API_Client' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Base/Generic_AI_API_Client.php',
'ATFPP\\AI_Translate\\Services\\Base\\OpenAI_Compatible_AI_Text_Generation_Model' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Base/OpenAI_Compatible_AI_Text_Generation_Model.php',
'ATFPP\\AI_Translate\\Services\\Cache\\Service_Request_Cache' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Cache/Service_Request_Cache.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\Authentication' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Contracts/Authentication.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\Generation_Config' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Contracts/Generation_Config.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\Generative_AI_API_Client' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Contracts/Generative_AI_API_Client.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\Generative_AI_Model' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Contracts/Generative_AI_Model.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\Generative_AI_Service' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Contracts/Generative_AI_Service.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\With_API_Client' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Contracts/With_API_Client.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\With_Function_Calling' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Contracts/With_Function_Calling.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\With_JSON_Schema' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Contracts/With_JSON_Schema.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\With_Multimodal_Input' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Contracts/With_Multimodal_Input.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\With_Multimodal_Output' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Contracts/With_Multimodal_Output.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\With_Text_Generation' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Contracts/With_Text_Generation.php',
'ATFPP\\AI_Translate\\Services\\Contracts\\With_Web_Search' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Contracts/With_Web_Search.php',
'ATFPP\\AI_Translate\\Services\\Decorators\\AI_Service_Decorator' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Decorators/AI_Service_Decorator.php',
'ATFPP\\AI_Translate\\Services\\Exception\\Generative_AI_Exception' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Exception/Generative_AI_Exception.php',
'ATFPP\\AI_Translate\\Services\\HTTP\\Contracts\\Stream_Request_Handler' => __DIR__ . '/../..' . '/includes/ai-translate/Services/HTTP/Contracts/Stream_Request_Handler.php',
'ATFPP\\AI_Translate\\Services\\HTTP\\Contracts\\With_Stream' => __DIR__ . '/../..' . '/includes/ai-translate/Services/HTTP/Contracts/With_Stream.php',
'ATFPP\\AI_Translate\\Services\\HTTP\\HTTP_With_Streams' => __DIR__ . '/../..' . '/includes/ai-translate/Services/HTTP/HTTP_With_Streams.php',
'ATFPP\\AI_Translate\\Services\\HTTP\\Stream_Response' => __DIR__ . '/../..' . '/includes/ai-translate/Services/HTTP/Stream_Response.php',
'ATFPP\\AI_Translate\\Services\\Options\\Option_Encrypter' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Options/Option_Encrypter.php',
'ATFPP\\AI_Translate\\Services\\Service_Registration' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Service_Registration.php',
'ATFPP\\AI_Translate\\Services\\Service_Registration_Context' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Service_Registration_Context.php',
'ATFPP\\AI_Translate\\Services\\Services_API' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Services_API.php',
'ATFPP\\AI_Translate\\Services\\Services_API_Instance' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Services_API_Instance.php',
'ATFPP\\AI_Translate\\Services\\Services_Loader' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Services_Loader.php',
'ATFPP\\AI_Translate\\Services\\Services_Service_Container_Builder' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Services_Service_Container_Builder.php',
'ATFPP\\AI_Translate\\Services\\Traits\\Generative_AI_API_Client_Trait' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Traits/Generative_AI_API_Client_Trait.php',
'ATFPP\\AI_Translate\\Services\\Traits\\Model_Param_System_Instruction_Trait' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Traits/Model_Param_System_Instruction_Trait.php',
'ATFPP\\AI_Translate\\Services\\Traits\\Model_Param_Text_Generation_Config_Trait' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Traits/Model_Param_Text_Generation_Config_Trait.php',
'ATFPP\\AI_Translate\\Services\\Traits\\Model_Param_Tool_Config_Trait' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Traits/Model_Param_Tool_Config_Trait.php',
'ATFPP\\AI_Translate\\Services\\Traits\\Model_Param_Tools_Trait' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Traits/Model_Param_Tools_Trait.php',
'ATFPP\\AI_Translate\\Services\\Traits\\OpenAI_Compatible_Text_Generation_With_Function_Calling_Trait' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Traits/OpenAI_Compatible_Text_Generation_With_Function_Calling_Trait.php',
'ATFPP\\AI_Translate\\Services\\Traits\\With_API_Client_Trait' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Traits/With_API_Client_Trait.php',
'ATFPP\\AI_Translate\\Services\\Traits\\With_Text_Generation_Trait' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Traits/With_Text_Generation_Trait.php',
'ATFPP\\AI_Translate\\Services\\Util\\AI_Capabilities' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Util/AI_Capabilities.php',
'ATFPP\\AI_Translate\\Services\\Util\\Data_Encryption' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Util/Data_Encryption.php',
'ATFPP\\AI_Translate\\Services\\Util\\Formatter' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Util/Formatter.php',
'ATFPP\\AI_Translate\\Services\\Util\\Strings' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Util/Strings.php',
'ATFPP\\AI_Translate\\Services\\Util\\Transformer' => __DIR__ . '/../..' . '/includes/ai-translate/Services/Util/Transformer.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Capabilities\\Abstract_Capability' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Capabilities/Abstract_Capability.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Capabilities\\Base_Capability' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Capabilities/Base_Capability.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Capabilities\\Capability_Container' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Capabilities/Capability_Container.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Capabilities\\Capability_Controller' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Capabilities/Capability_Controller.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Capabilities\\Capability_Filters' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Capabilities/Capability_Filters.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Capabilities\\Contracts\\Capability' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Capabilities/Contracts/Capability.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Capabilities\\Meta_Capability' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Capabilities/Meta_Capability.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Array_Key_Value_Repository' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Array_Key_Value_Repository.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Array_Registry' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Array_Registry.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\Arrayable' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/Arrayable.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\Collection' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/Collection.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\Container' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/Container.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\Container_Readonly' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/Container_Readonly.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\Hook_Registrar' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/Hook_Registrar.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\Key_Value' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/Key_Value.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\Key_Value_Repository' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/Key_Value_Repository.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\Registry' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/Registry.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\With_Capabilities' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/With_Capabilities.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\With_Hooks' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/With_Hooks.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\With_Key' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/With_Key.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Contracts\\With_Registration_Args' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Contracts/With_Registration_Args.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Current_User' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Current_User.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Exception\\Invalid_Type_Exception' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Exception/Invalid_Type_Exception.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Exception\\Not_Found_Exception' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Exception/Not_Found_Exception.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Exception\\WP_Error_Exception' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Exception/WP_Error_Exception.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Generic_Key_Value' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Generic_Key_Value.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Input' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Input.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Mutable_Input' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Mutable_Input.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Network_Env' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Network_Env.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Network_Runner' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Network_Runner.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Plugin_Env' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Plugin_Env.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Service_Container' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Service_Container.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Site_Env' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Site_Env.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Traits\\Cast_Value_By_Type' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Traits/Cast_Value_By_Type.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\General\\Traits\\Maybe_Throw' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/General/Traits/Maybe_Throw.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Contracts\\Request' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Contracts/Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Contracts\\Request_Handler' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Contracts/Request_Handler.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Contracts\\Response' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Contracts/Response.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Delete_Request' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Delete_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Exception\\Multiple_Requests_Exception' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Exception/Multiple_Requests_Exception.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Exception\\Request_Exception' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Exception/Request_Exception.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Generic_Request' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Generic_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Generic_Response' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Generic_Response.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Get_Request' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Get_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\HTTP' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/HTTP.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\JSON_Patch_Request' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/JSON_Patch_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\JSON_Post_Request' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/JSON_Post_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\JSON_Put_Request' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/JSON_Put_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\JSON_Request' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/JSON_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\JSON_Response' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/JSON_Response.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Patch_Request' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Patch_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Post_Request' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Post_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Put_Request' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Put_Request.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\HTTP\\Traits\\Sanitize_Headers' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/HTTP/Traits/Sanitize_Headers.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Abstract_Entity_Key_Value' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Meta/Abstract_Entity_Key_Value.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Contracts\\Entity_Key_Value' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Meta/Contracts/Entity_Key_Value.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Contracts\\Entity_Key_Value_Repository' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Meta/Contracts/Entity_Key_Value_Repository.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Contracts\\With_Entity_ID' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Meta/Contracts/With_Entity_ID.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Contracts\\With_Single' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Meta/Contracts/With_Single.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Entity_Aware_Meta_Container' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Meta/Entity_Aware_Meta_Container.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Entity_Aware_Meta_Key' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Meta/Entity_Aware_Meta_Key.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Meta_Container' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Meta/Meta_Container.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Meta_Hook_Registrar' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Meta/Meta_Hook_Registrar.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Meta_Key' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Meta/Meta_Key.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Meta_Registry' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Meta/Meta_Registry.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Meta\\Meta_Repository' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Meta/Meta_Repository.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Options\\Contracts\\With_Autoload_Config' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Options/Contracts/With_Autoload_Config.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Options\\Option' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Options/Option.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Options\\Option_Container' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Options/Option_Container.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Options\\Option_Hook_Registrar' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Options/Option_Hook_Registrar.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Options\\Option_Registry' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Options/Option_Registry.php',
'Felix_Arntz\\ATFPP\\WP_OOP_Plugin_Lib\\Options\\Option_Repository' => __DIR__ . '/..' . '/felixarntz/wp-oop-plugin-lib/src/Options/Option_Repository.php',
'GuzzleHttp\\BodySummarizer' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/BodySummarizer.php',
'GuzzleHttp\\BodySummarizerInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/BodySummarizerInterface.php',
'GuzzleHttp\\Client' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Client.php',
'GuzzleHttp\\ClientInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/ClientInterface.php',
'GuzzleHttp\\ClientTrait' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/ClientTrait.php',
'GuzzleHttp\\Cookie\\CookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/CookieJar.php',
'GuzzleHttp\\Cookie\\CookieJarInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php',
'GuzzleHttp\\Cookie\\FileCookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php',
'GuzzleHttp\\Cookie\\SessionCookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php',
'GuzzleHttp\\Cookie\\SetCookie' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/SetCookie.php',
'GuzzleHttp\\Exception\\BadResponseException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/BadResponseException.php',
'GuzzleHttp\\Exception\\ClientException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ClientException.php',
'GuzzleHttp\\Exception\\ConnectException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ConnectException.php',
'GuzzleHttp\\Exception\\GuzzleException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/GuzzleException.php',
'GuzzleHttp\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php',
'GuzzleHttp\\Exception\\RequestException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/RequestException.php',
'GuzzleHttp\\Exception\\ServerException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ServerException.php',
'GuzzleHttp\\Exception\\TooManyRedirectsException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php',
'GuzzleHttp\\Exception\\TransferException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/TransferException.php',
'GuzzleHttp\\HandlerStack' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/HandlerStack.php',
'GuzzleHttp\\Handler\\CurlFactory' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlFactory.php',
'GuzzleHttp\\Handler\\CurlFactoryInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php',
'GuzzleHttp\\Handler\\CurlHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlHandler.php',
'GuzzleHttp\\Handler\\CurlMultiHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php',
'GuzzleHttp\\Handler\\EasyHandle' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/EasyHandle.php',
'GuzzleHttp\\Handler\\HeaderProcessor' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php',
'GuzzleHttp\\Handler\\MockHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/MockHandler.php',
'GuzzleHttp\\Handler\\Proxy' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/Proxy.php',
'GuzzleHttp\\Handler\\StreamHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/StreamHandler.php',
'GuzzleHttp\\MessageFormatter' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/MessageFormatter.php',
'GuzzleHttp\\MessageFormatterInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/MessageFormatterInterface.php',
'GuzzleHttp\\Middleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Middleware.php',
'GuzzleHttp\\Pool' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Pool.php',
'GuzzleHttp\\PrepareBodyMiddleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php',
'GuzzleHttp\\Promise\\AggregateException' => __DIR__ . '/..' . '/guzzlehttp/promises/src/AggregateException.php',
'GuzzleHttp\\Promise\\CancellationException' => __DIR__ . '/..' . '/guzzlehttp/promises/src/CancellationException.php',
'GuzzleHttp\\Promise\\Coroutine' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Coroutine.php',
'GuzzleHttp\\Promise\\Create' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Create.php',
'GuzzleHttp\\Promise\\Each' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Each.php',
'GuzzleHttp\\Promise\\EachPromise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/EachPromise.php',
'GuzzleHttp\\Promise\\FulfilledPromise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/FulfilledPromise.php',
'GuzzleHttp\\Promise\\Is' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Is.php',
'GuzzleHttp\\Promise\\Promise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Promise.php',
'GuzzleHttp\\Promise\\PromiseInterface' => __DIR__ . '/..' . '/guzzlehttp/promises/src/PromiseInterface.php',
'GuzzleHttp\\Promise\\PromisorInterface' => __DIR__ . '/..' . '/guzzlehttp/promises/src/PromisorInterface.php',
'GuzzleHttp\\Promise\\RejectedPromise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/RejectedPromise.php',
'GuzzleHttp\\Promise\\RejectionException' => __DIR__ . '/..' . '/guzzlehttp/promises/src/RejectionException.php',
'GuzzleHttp\\Promise\\TaskQueue' => __DIR__ . '/..' . '/guzzlehttp/promises/src/TaskQueue.php',
'GuzzleHttp\\Promise\\TaskQueueInterface' => __DIR__ . '/..' . '/guzzlehttp/promises/src/TaskQueueInterface.php',
'GuzzleHttp\\Promise\\Utils' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Utils.php',
'GuzzleHttp\\Psr7\\AppendStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/AppendStream.php',
'GuzzleHttp\\Psr7\\BufferStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/BufferStream.php',
'GuzzleHttp\\Psr7\\CachingStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/CachingStream.php',
'GuzzleHttp\\Psr7\\DroppingStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/DroppingStream.php',
'GuzzleHttp\\Psr7\\Exception\\MalformedUriException' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Exception/MalformedUriException.php',
'GuzzleHttp\\Psr7\\FnStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/FnStream.php',
'GuzzleHttp\\Psr7\\Header' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Header.php',
'GuzzleHttp\\Psr7\\HttpFactory' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/HttpFactory.php',
'GuzzleHttp\\Psr7\\InflateStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/InflateStream.php',
'GuzzleHttp\\Psr7\\LazyOpenStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/LazyOpenStream.php',
'GuzzleHttp\\Psr7\\LimitStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/LimitStream.php',
'GuzzleHttp\\Psr7\\Message' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Message.php',
'GuzzleHttp\\Psr7\\MessageTrait' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MessageTrait.php',
'GuzzleHttp\\Psr7\\MimeType' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MimeType.php',
'GuzzleHttp\\Psr7\\MultipartStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MultipartStream.php',
'GuzzleHttp\\Psr7\\NoSeekStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/NoSeekStream.php',
'GuzzleHttp\\Psr7\\PumpStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/PumpStream.php',
'GuzzleHttp\\Psr7\\Query' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Query.php',
'GuzzleHttp\\Psr7\\Request' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Request.php',
'GuzzleHttp\\Psr7\\Response' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Response.php',
'GuzzleHttp\\Psr7\\Rfc7230' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Rfc7230.php',
'GuzzleHttp\\Psr7\\ServerRequest' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/ServerRequest.php',
'GuzzleHttp\\Psr7\\Stream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Stream.php',
'GuzzleHttp\\Psr7\\StreamDecoratorTrait' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/StreamDecoratorTrait.php',
'GuzzleHttp\\Psr7\\StreamWrapper' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/StreamWrapper.php',
'GuzzleHttp\\Psr7\\UploadedFile' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UploadedFile.php',
'GuzzleHttp\\Psr7\\Uri' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Uri.php',
'GuzzleHttp\\Psr7\\UriComparator' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UriComparator.php',
'GuzzleHttp\\Psr7\\UriNormalizer' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UriNormalizer.php',
'GuzzleHttp\\Psr7\\UriResolver' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UriResolver.php',
'GuzzleHttp\\Psr7\\Utils' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Utils.php',
'GuzzleHttp\\RedirectMiddleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RedirectMiddleware.php',
'GuzzleHttp\\RequestOptions' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RequestOptions.php',
'GuzzleHttp\\RetryMiddleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RetryMiddleware.php',
'GuzzleHttp\\TransferStats' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/TransferStats.php',
'GuzzleHttp\\Utils' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Utils.php',
'Psr\\Http\\Client\\ClientExceptionInterface' => __DIR__ . '/..' . '/psr/http-client/src/ClientExceptionInterface.php',
'Psr\\Http\\Client\\ClientInterface' => __DIR__ . '/..' . '/psr/http-client/src/ClientInterface.php',
'Psr\\Http\\Client\\NetworkExceptionInterface' => __DIR__ . '/..' . '/psr/http-client/src/NetworkExceptionInterface.php',
'Psr\\Http\\Client\\RequestExceptionInterface' => __DIR__ . '/..' . '/psr/http-client/src/RequestExceptionInterface.php',
'Psr\\Http\\Message\\MessageInterface' => __DIR__ . '/..' . '/psr/http-message/src/MessageInterface.php',
'Psr\\Http\\Message\\RequestFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/RequestFactoryInterface.php',
'Psr\\Http\\Message\\RequestInterface' => __DIR__ . '/..' . '/psr/http-message/src/RequestInterface.php',
'Psr\\Http\\Message\\ResponseFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/ResponseFactoryInterface.php',
'Psr\\Http\\Message\\ResponseInterface' => __DIR__ . '/..' . '/psr/http-message/src/ResponseInterface.php',
'Psr\\Http\\Message\\ServerRequestFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/ServerRequestFactoryInterface.php',
'Psr\\Http\\Message\\ServerRequestInterface' => __DIR__ . '/..' . '/psr/http-message/src/ServerRequestInterface.php',
'Psr\\Http\\Message\\StreamFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/StreamFactoryInterface.php',
'Psr\\Http\\Message\\StreamInterface' => __DIR__ . '/..' . '/psr/http-message/src/StreamInterface.php',
'Psr\\Http\\Message\\UploadedFileFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/UploadedFileFactoryInterface.php',
'Psr\\Http\\Message\\UploadedFileInterface' => __DIR__ . '/..' . '/psr/http-message/src/UploadedFileInterface.php',
'Psr\\Http\\Message\\UriFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/UriFactoryInterface.php',
'Psr\\Http\\Message\\UriInterface' => __DIR__ . '/..' . '/psr/http-message/src/UriInterface.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitef786e44e6598dfaf0500750ff2a09f1::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitef786e44e6598dfaf0500750ff2a09f1::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInitef786e44e6598dfaf0500750ff2a09f1::$classMap;
}, null, ClassLoader::class);
}
}

View File

@@ -0,0 +1,826 @@
{
"packages": [
{
"name": "composer/installers",
"version": "v1.12.0",
"version_normalized": "1.12.0.0",
"source": {
"type": "git",
"url": "https://github.com/composer/installers.git",
"reference": "d20a64ed3c94748397ff5973488761b22f6d3f19"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/installers/zipball/d20a64ed3c94748397ff5973488761b22f6d3f19",
"reference": "d20a64ed3c94748397ff5973488761b22f6d3f19",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0 || ^2.0"
},
"replace": {
"roundcube/plugin-installer": "*",
"shama/baton": "*"
},
"require-dev": {
"composer/composer": "1.6.* || ^2.0",
"composer/semver": "^1 || ^3",
"phpstan/phpstan": "^0.12.55",
"phpstan/phpstan-phpunit": "^0.12.16",
"symfony/phpunit-bridge": "^4.2 || ^5",
"symfony/process": "^2.3"
},
"time": "2021-09-13T08:19:44+00:00",
"type": "composer-plugin",
"extra": {
"class": "Composer\\Installers\\Plugin",
"branch-alias": {
"dev-main": "1.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Composer\\Installers\\": "src/Composer/Installers"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kyle Robinson Young",
"email": "kyle@dontkry.com",
"homepage": "https://github.com/shama"
}
],
"description": "A multi-framework Composer library installer",
"homepage": "https://composer.github.io/installers/",
"keywords": [
"Craft",
"Dolibarr",
"Eliasis",
"Hurad",
"ImageCMS",
"Kanboard",
"Lan Management System",
"MODX Evo",
"MantisBT",
"Mautic",
"Maya",
"OXID",
"Plentymarkets",
"Porto",
"RadPHP",
"SMF",
"Starbug",
"Thelia",
"Whmcs",
"WolfCMS",
"agl",
"aimeos",
"annotatecms",
"attogram",
"bitrix",
"cakephp",
"chef",
"cockpit",
"codeigniter",
"concrete5",
"croogo",
"dokuwiki",
"drupal",
"eZ Platform",
"elgg",
"expressionengine",
"fuelphp",
"grav",
"installer",
"itop",
"joomla",
"known",
"kohana",
"laravel",
"lavalite",
"lithium",
"magento",
"majima",
"mako",
"mediawiki",
"miaoxing",
"modulework",
"modx",
"moodle",
"osclass",
"pantheon",
"phpbb",
"piwik",
"ppi",
"processwire",
"puppet",
"pxcms",
"reindex",
"roundcube",
"shopware",
"silverstripe",
"sydes",
"sylius",
"symfony",
"tastyigniter",
"typo3",
"wordpress",
"yawik",
"zend",
"zikula"
],
"support": {
"issues": "https://github.com/composer/installers/issues",
"source": "https://github.com/composer/installers/tree/v1.12.0"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"install-path": "./installers"
},
{
"name": "felixarntz/wp-oop-plugin-lib",
"version": "dev-main",
"version_normalized": "dev-main",
"source": {
"type": "git",
"url": "https://github.com/felixarntz/wp-oop-plugin-lib.git",
"reference": "1fdfe74746fef54fe33a1abde9b78ffa0c27436d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/felixarntz/wp-oop-plugin-lib/zipball/1fdfe74746fef54fe33a1abde9b78ffa0c27436d",
"reference": "1fdfe74746fef54fe33a1abde9b78ffa0c27436d",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"time": "2025-07-08T21:33:08+00:00",
"default-branch": true,
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Felix_Arntz\\WP_OOP_Plugin_Lib\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-2.0-or-later"
],
"authors": [
{
"name": "Felix Arntz",
"email": "hello@felix-arntz.me",
"homepage": "https://felix-arntz.me",
"role": "Developer"
}
],
"description": "A library providing classes around WordPress APIs, to be used for example in object oriented WordPress plugins.",
"support": {
"issues": "https://github.com/felixarntz/wp-oop-plugin-lib/issues",
"source": "https://github.com/felixarntz/wp-oop-plugin-lib/tree/main"
},
"install-path": "../felixarntz/wp-oop-plugin-lib"
},
{
"name": "guzzlehttp/guzzle",
"version": "7.9.3",
"version_normalized": "7.9.3.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77",
"reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/promises": "^1.5.3 || ^2.0.3",
"guzzlehttp/psr7": "^2.7.0",
"php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0"
},
"provide": {
"psr/http-client-implementation": "1.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"ext-curl": "*",
"guzzle/client-integration-tests": "3.0.2",
"php-http/message-factory": "^1.1",
"phpunit/phpunit": "^8.5.39 || ^9.6.20",
"psr/log": "^1.1 || ^2.0 || ^3.0"
},
"suggest": {
"ext-curl": "Required for CURL handler support",
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
"psr/log": "Required for using the Log middleware"
},
"time": "2025-03-27T13:37:11+00:00",
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
}
},
"installation-source": "dist",
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"GuzzleHttp\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Jeremy Lindblom",
"email": "jeremeamia@gmail.com",
"homepage": "https://github.com/jeremeamia"
},
{
"name": "George Mponos",
"email": "gmponos@gmail.com",
"homepage": "https://github.com/gmponos"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://github.com/sagikazarmark"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
}
],
"description": "Guzzle is a PHP HTTP client library",
"keywords": [
"client",
"curl",
"framework",
"http",
"http client",
"psr-18",
"psr-7",
"rest",
"web service"
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.9.3"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
"type": "tidelift"
}
],
"install-path": "../guzzlehttp/guzzle"
},
{
"name": "guzzlehttp/promises",
"version": "2.2.0",
"version_normalized": "2.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c",
"reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^8.5.39 || ^9.6.20"
},
"time": "2025-03-27T13:27:01+00:00",
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"GuzzleHttp\\Promise\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
}
],
"description": "Guzzle promises library",
"keywords": [
"promise"
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/2.2.0"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
"type": "tidelift"
}
],
"install-path": "../guzzlehttp/promises"
},
{
"name": "guzzlehttp/psr7",
"version": "2.7.1",
"version_normalized": "2.7.1.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16",
"reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.1 || ^2.0",
"ralouphie/getallheaders": "^3.0"
},
"provide": {
"psr/http-factory-implementation": "1.0",
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"http-interop/http-factory-tests": "0.9.0",
"phpunit/phpunit": "^8.5.39 || ^9.6.20"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
},
"time": "2025-03-27T12:30:47+00:00",
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "George Mponos",
"email": "gmponos@gmail.com",
"homepage": "https://github.com/gmponos"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://github.com/sagikazarmark"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://sagikazarmark.hu"
}
],
"description": "PSR-7 message implementation that also provides common utility methods",
"keywords": [
"http",
"message",
"psr-7",
"request",
"response",
"stream",
"uri",
"url"
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.7.1"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
"type": "tidelift"
}
],
"install-path": "../guzzlehttp/psr7"
},
{
"name": "psr/http-client",
"version": "1.0.3",
"version_normalized": "1.0.3.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-client.git",
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
"shasum": ""
},
"require": {
"php": "^7.0 || ^8.0",
"psr/http-message": "^1.0 || ^2.0"
},
"time": "2023-09-23T14:17:50+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Psr\\Http\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP clients",
"homepage": "https://github.com/php-fig/http-client",
"keywords": [
"http",
"http-client",
"psr",
"psr-18"
],
"support": {
"source": "https://github.com/php-fig/http-client"
},
"install-path": "../psr/http-client"
},
{
"name": "psr/http-factory",
"version": "1.1.0",
"version_normalized": "1.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-factory.git",
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"shasum": ""
},
"require": {
"php": ">=7.1",
"psr/http-message": "^1.0 || ^2.0"
},
"time": "2024-04-15T12:06:14+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.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": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
"keywords": [
"factory",
"http",
"message",
"psr",
"psr-17",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-factory"
},
"install-path": "../psr/http-factory"
},
{
"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": "ralouphie/getallheaders",
"version": "3.0.3",
"version_normalized": "3.0.3.0",
"source": {
"type": "git",
"url": "https://github.com/ralouphie/getallheaders.git",
"reference": "120b605dfeb996808c31b6477290a714d356e822"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
"reference": "120b605dfeb996808c31b6477290a714d356e822",
"shasum": ""
},
"require": {
"php": ">=5.6"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^5 || ^6.5"
},
"time": "2019-03-08T08:55:37+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"files": [
"src/getallheaders.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ralph Khattar",
"email": "ralph.khattar@gmail.com"
}
],
"description": "A polyfill for getallheaders.",
"support": {
"issues": "https://github.com/ralouphie/getallheaders/issues",
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
},
"install-path": "../ralouphie/getallheaders"
},
{
"name": "symfony/deprecation-contracts",
"version": "v2.5.4",
"version_normalized": "2.5.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "605389f2a7e5625f273b53960dc46aeaf9c62918"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918",
"reference": "605389f2a7e5625f273b53960dc46aeaf9c62918",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"time": "2024-09-25T14:11:13+00:00",
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/contracts",
"name": "symfony/contracts"
},
"branch-alias": {
"dev-main": "2.5-dev"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"function.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/deprecation-contracts"
}
],
"dev": true,
"dev-package-names": []
}

View File

@@ -0,0 +1,145 @@
<?php return array(
'root' => array(
'name' => 'felixarntz/ai-services',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => 'df0856ea685d1966bfc85a267b1ef09562407eb3',
'type' => 'wordpress-plugin',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => true,
),
'versions' => array(
'composer/installers' => array(
'pretty_version' => 'v1.12.0',
'version' => '1.12.0.0',
'reference' => 'd20a64ed3c94748397ff5973488761b22f6d3f19',
'type' => 'composer-plugin',
'install_path' => __DIR__ . '/./installers',
'aliases' => array(),
'dev_requirement' => false,
),
'felixarntz/ai-services' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => 'df0856ea685d1966bfc85a267b1ef09562407eb3',
'type' => 'wordpress-plugin',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'felixarntz/wp-oop-plugin-lib' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '1fdfe74746fef54fe33a1abde9b78ffa0c27436d',
'type' => 'library',
'install_path' => __DIR__ . '/../felixarntz/wp-oop-plugin-lib',
'aliases' => array(
0 => '9999999-dev',
),
'dev_requirement' => false,
),
'guzzlehttp/guzzle' => array(
'pretty_version' => '7.9.3',
'version' => '7.9.3.0',
'reference' => '7b2f29fe81dc4da0ca0ea7d42107a0845946ea77',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/guzzle',
'aliases' => array(),
'dev_requirement' => false,
),
'guzzlehttp/promises' => array(
'pretty_version' => '2.2.0',
'version' => '2.2.0.0',
'reference' => '7c69f28996b0a6920945dd20b3857e499d9ca96c',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/promises',
'aliases' => array(),
'dev_requirement' => false,
),
'guzzlehttp/psr7' => array(
'pretty_version' => '2.7.1',
'version' => '2.7.1.0',
'reference' => 'c2270caaabe631b3b44c85f99e5a04bbb8060d16',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/psr7',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-client' => array(
'pretty_version' => '1.0.3',
'version' => '1.0.3.0',
'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-client',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-client-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/http-factory' => array(
'pretty_version' => '1.1.0',
'version' => '1.1.0.0',
'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-factory',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-factory-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,
),
'psr/http-message-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'ralouphie/getallheaders' => array(
'pretty_version' => '3.0.3',
'version' => '3.0.3.0',
'reference' => '120b605dfeb996808c31b6477290a714d356e822',
'type' => 'library',
'install_path' => __DIR__ . '/../ralouphie/getallheaders',
'aliases' => array(),
'dev_requirement' => false,
),
'roundcube/plugin-installer' => array(
'dev_requirement' => false,
'replaced' => array(
0 => '*',
),
),
'shama/baton' => array(
'dev_requirement' => false,
'replaced' => array(
0 => '*',
),
),
'symfony/deprecation-contracts' => array(
'pretty_version' => 'v2.5.4',
'version' => '2.5.4.0',
'reference' => '605389f2a7e5625f273b53960dc46aeaf9c62918',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
'aliases' => array(),
'dev_requirement' => false,
),
),
);

View File

@@ -0,0 +1,26 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 70205)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.5". 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
);
}

View File

@@ -0,0 +1,49 @@
# Contributing to WP OOP Plugin Lib
Thank you for your interest in contributing to this library! At this point, it is still in an early development stage, but especially because of that feedback is much appreciated!
Just two general guidelines:
* All contributors are expected to follow the [WordPress Code of Conduct](https://make.wordpress.org/handbook/community-code-of-conduct/).
* All contributors who submit a pull request are agreeing to release their contribution under the [GPLv2+ license](https://github.com/felixarntz/wp-oop-plugin-lib/blob/main/LICENSE).
## Providing feedback
If you're already using the library in a WordPress plugin, or you've started experimenting with it, you may run into limitations, or you simply may have questions on how a certain part of the infrastructure is supposed to work. You may run into a bug, or you may think about another WordPress API that you would like to see covered by this library. In any case, please let me know by [opening an issue](https://github.com/felixarntz/wp-oop-plugin-lib/issues/new/choose)!
## Sharing your use-cases
I'd love to learn how this library is being used by the WordPress ecosystem! Not only out of pure curiosity, but also because it allows me to improve it to potentially cater for those use-cases better. If you're already using the library in an open-source project, please share a link to the repository on [this issue](https://github.com/felixarntz/wp-oop-plugin-lib/issues/1)!
## Contributing code
Pull requests are welcome! For little fixes, feel free to go right ahead and open one. For new features or larger enhancements, I'd encourage you to open an issue first where we can scope and discuss the change. Though of course in any case feel free to jump right into writing code! You can do so by [forking this repository](https://github.com/felixarntz/wp-oop-plugin-lib/fork) and later opening a pull request with your changes.
### Guidelines for contributing code
If you're interested in contributing code, please consider the following guidelines and best practices:
* All code must follow the [WordPress Coding Standards and best practices](https://developer.wordpress.org/coding-standards/), including documentation. They are enforced via the project's PHP_CodeSniffer configuration.
* All code must be backward-compatible with WordPress 6.0 and PHP 7.2.
* All code must pass the automated PHP code quality requirements via the project's PHPMD and PHPStan configuration.
* All functional code changes should be accompanied by PHPUnit tests.
### Getting started with writing code
For the linting tools to work, you'll need to have [Composer](https://getcomposer.org/) installed on your machine. In order to make use of the built-in development environment including the ability to run the PHPUnit tests, you'll also need [Docker](https://www.docker.com/) and [Node.js](https://nodejs.org/).
The following linting commands are available:
* `composer lint`: Checks the code with [PHP_CodeSniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer/).
* `composer format`: Automatically fixes code problems detected by PHPCodeSniffer, where possible.
* `composer phpmd`: Checks the code with [PHPMD](https://github.com/phpmd/phpmd).
* `composer phpstan`: Checks the code with [PHPStan](https://github.com/phpstan/phpstan).
The following commands allow running PHPUnit tests using the built-in environment:
* `npm run test-php`: Runs the PHPUnit tests for a regular (single) WordPress site.
* `npm run test-php-multisite`: Runs the PHPUnit tests for a WordPress multisite.
The project comes with a demo WordPress plugin file `wp-oop-plugin-lib.php`, which does nothing but autoload the library's classes. If you need to quickly test some WordPress or library code, feel free to temporarily modify the file in your development environment. You can access the built-in environment with that demo plugin active using [wp-env](https://www.npmjs.com/package/@wordpress/env):
* `npm run wp-env start`: Starts the environment (typically available at `http://localhost:8888/`).
* `npm run wp-env stop`: Stops the environment.

View File

@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
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.
{description}
Copyright (C) {year} {fullname}
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.
{signature of Ty Coon}, 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.

View File

@@ -0,0 +1,49 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Capabilities\Abstract_Capability
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Capabilities;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Capabilities\Contracts\Capability;
/**
* Base class representing a WordPress capability.
*
* @since 0.1.0
*/
abstract class Abstract_Capability implements Capability {
/**
* Capability key.
*
* @since 0.1.0
* @var string
*/
private $key;
/**
* Constructor.
*
* @since 0.1.0
*
* @param string $key Capability key.
*/
public function __construct( string $key ) {
$this->key = $key;
}
/**
* Gets the capability key / slug.
*
* @since 0.1.0
*
* @return string Capability key.
*/
public function get_key(): string {
return $this->key;
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Capabilities\Base_Capability
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Capabilities;
/**
* Class representing a WordPress base capability.
*
* A base capability can be granted to a user role, or based on other base capabilities.
*
* @since 0.1.0
*/
class Base_Capability extends Abstract_Capability {
/**
* Required base capabilities needed to grant this capability.
*
* If empty list, it means this base capability must be granted directly on individual user roles.
*
* @since 0.1.0
* @var string[]
*/
private $required_caps;
/**
* Constructor.
*
* @since 0.1.0
*
* @param string $key Capability key.
* @param string[] $required_caps Optional. Required base capabilities needed to grant this capability. An empty
* array means this base capability must be granted directly on individual user
* roles. Default empty array.
*/
public function __construct( string $key, array $required_caps = array() ) {
parent::__construct( $key );
$this->required_caps = $required_caps;
}
/**
* Sets the required base capabilities from the user's role(s) to grant this base capability dynamically.
*
* While WordPress's built-in capabilities are stored in the database on their relevant roles, this approach is
* usually sub optimal for plugins and can lead to out of sync data. Instead, granting custom capabilities
* depending on one or more base capabilities controls custom capabilities programmatically.
*
* If the capability should instead be granted on a specific user role, an empty array can be provided to skip
* granting the capability based on any built-in capabilities.
*
* @since 0.1.0
*
* @param string[] $required_caps Required base capabilities needed to grant this capability. An empty array means
* this base capability must be granted directly on individual user roles. Default
* empty array.
*/
public function set_required_caps( array $required_caps ): void {
$this->required_caps = $required_caps;
}
/**
* Gets the required base capabilities from the user's role(s) to grant this capability.
*
* An empty array means the base capability should be granted directly on individual user roles.
*
* @since 0.1.0
*
* @return string[] Required capabilities needed to grant this capability, or empty array to indicate that this
* base capability must be granted directly on individual user roles.
*/
public function get_required_caps(): array {
return $this->required_caps;
}
}

View File

@@ -0,0 +1,202 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Capabilities\Capability_Container
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Capabilities;
use ArrayAccess;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Capabilities\Contracts\Capability;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Container;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Exception\Invalid_Type_Exception;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Exception\Not_Found_Exception;
/**
* Class for a capability container.
*
* @since 0.1.0
*/
class Capability_Container implements Container, ArrayAccess {
/**
* Capabilities stored in the container.
*
* @since 0.1.0
* @var array<string, callable>
*/
private $capabilities = array();
/**
* Capability instances already created.
*
* @since 0.1.0
* @var array<string, Capability>
*/
private $instances = array();
/**
* Checks if a capability for the given key exists in the container.
*
* @since 0.1.0
*
* @param string $key Capability key.
* @return bool True if the capability exists in the container, false otherwise.
*/
public function has( string $key ): bool {
return isset( $this->capabilities[ $key ] );
}
/**
* Gets the capability for the given key from the container.
*
* @since 0.1.0
*
* @param string $key Capability key.
* @return Capability Capability for the given key.
*
* @throws Not_Found_Exception Thrown when option with given key is not found.
* @throws Invalid_Type_Exception Thrown when option with given key has invalid type.
*/
public function get( string $key ) {
if ( ! isset( $this->capabilities[ $key ] ) ) {
throw new Not_Found_Exception(
esc_html(
sprintf(
/* translators: %s: capability key */
__( 'Capability with key %s was not found in container', 'wp-oop-plugin-lib' ),
$key
)
)
);
}
if ( ! isset( $this->instances[ $key ] ) ) {
$instance = $this->capabilities[ $key ]( $this );
if ( ! $instance instanceof Capability ) {
throw new Invalid_Type_Exception(
esc_html(
sprintf(
/* translators: %s: capability key */
__( 'Capability with key %s is not of type Capability', 'wp-oop-plugin-lib' ),
$key
)
)
);
}
$this->instances[ $key ] = $instance;
}
return $this->instances[ $key ];
}
/**
* Sets the given capability under the given key in the container.
*
* @since 0.1.0
*
* @param string $key Capability key.
* @param callable $creator Capability creator closure.
*/
public function set( string $key, callable $creator ): void {
$this->capabilities[ $key ] = $creator;
unset( $this->instances[ $key ] );
}
/**
* Sets a capability using the given required capabilities under the given key in the container.
*
* @since 0.1.0
*
* @param string $key Capability key.
* @param string[]|callable $required_caps Array with required base capabilities if this is a base capability,
* or callback function to dynamically determine the required base
* capabilities if this is a meta capability.
*/
public function set_by_args( string $key, $required_caps ): void {
$this->set(
$key,
function () use ( $key, $required_caps ) {
if ( is_callable( $required_caps ) ) {
return new Meta_Capability( $key, $required_caps );
}
return new Base_Capability( $key, $required_caps );
}
);
}
/**
* Unsets the capability under the given key in the container.
*
* @since 0.1.0
*
* @param string $key Capability key.
*/
public function unset( string $key ): void {
unset( $this->capabilities[ $key ], $this->instances[ $key ] );
}
/**
* Gets all keys in the container.
*
* @since 0.1.0
*
* @return string[] List of keys.
*/
public function get_keys(): array {
return array_keys( $this->capabilities );
}
/**
* Checks if a capability for the given key exists in the container.
*
* @since 0.1.0
*
* @param mixed $key Capability key.
* @return bool True if the capability exists in the container, false otherwise.
*/
#[\ReturnTypeWillChange]
public function offsetExists( $key ) {
return $this->has( $key );
}
/**
* Gets the capability for the given key from the container.
*
* @since 0.1.0
*
* @param mixed $key Capability key.
* @return Capability Capability for the given key.
*/
#[\ReturnTypeWillChange]
public function offsetGet( $key ) {
return $this->get( $key );
}
/**
* Sets the given capability under the given key in the container.
*
* @since 0.1.0
*
* @param mixed $key Capability key.
* @param mixed $value Capability creator closure.
*/
#[\ReturnTypeWillChange]
public function offsetSet( $key, $value ) {
$this->set( $key, $value );
}
/**
* Unsets the capability under the given key in the container.
*
* @since 0.1.0
*
* @param mixed $key Capability key.
*/
#[\ReturnTypeWillChange]
public function offsetUnset( $key ) {
$this->unset( $key );
}
}

View File

@@ -0,0 +1,105 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Capabilities\Capability_Controller
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Capabilities;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Exception\Not_Found_Exception;
/**
* Class for controlling how to grant a specific set of capabilities.
*
* This is useful for allowing to customize how capabilities are granted, e.g. by triggering a WordPress action hook
* and passing an instance of the class to it.
*
* @since 0.1.0
*/
class Capability_Controller {
/**
* Capability container.
*
* @since 0.1.0
* @var Capability_Container
*/
private $container;
/**
* Constructor.
*
* @since 0.1.0
*
* @param Capability_Container $container Container with the capabilities that this controller instance should be
* able to control.
*/
public function __construct( Capability_Container $container ) {
$this->container = $container;
}
/**
* Grants the given capability dynamically depending on required base capabilities from the user's role(s).
*
* While WordPress's built-in capabilities are stored in the database on their relevant roles, this approach is
* usually sub optimal for plugins and can lead to out of sync data. Instead, granting custom capabilities
* depending on one or more base capabilities controls custom capabilities programmatically.
*
* If the capability should instead be granted on a specific user role, an empty array can be provided to skip
* granting the capability based on any built-in capabilities.
*
* @since 0.1.0
*
* @param string $cap Base capability to grant. Must be part of the plugin capabilities in this
* controller.
* @param string[] $required_caps Required capabilities needed to grant this capability. An empty array means this
* is a base capability and must be granted directly on individual user roles.
* Default empty array.
*
* @throws Not_Found_Exception Thrown when the capability is not found in the container or is not a base capability.
*/
public function grant_cap_for_base_caps( string $cap, array $required_caps ): void {
$capability = $this->container->get( $cap );
if ( ! $capability instanceof Base_Capability ) {
throw new Not_Found_Exception(
sprintf(
/* translators: %s: capability key */
esc_html__( 'Capability %s must be a base capability to grant it based on other capabilities.', 'wp-oop-plugin-lib' ), // phpcs:ignore Generic.Files.LineLength.TooLong
esc_html( $cap )
)
);
}
$capability->set_required_caps( $required_caps );
}
/**
* Grants the given meta capability dynamically depending on a callback function.
*
* @since 0.1.0
*
* @param string $cap Meta capability to grant. Must be part of the plugin capabilities in this
* controller.
* @param callable $map_callback Callback function to determine the required base capabilities needed to grant this
* meta capability. The function receives the user ID and any additional parameters
* passed alongside the capability check and must return an array.
*
* @throws Not_Found_Exception Thrown when the capability is not found in the container or is not a meta capability.
*/
public function set_meta_map_callback( string $cap, callable $map_callback ): void {
$capability = $this->container->get( $cap );
if ( ! $capability instanceof Meta_Capability ) {
throw new Not_Found_Exception(
sprintf(
/* translators: %s: capability key */
esc_html__( 'Capability %s must be a meta capability to set a map callback.', 'wp-oop-plugin-lib' ), // phpcs:ignore Generic.Files.LineLength.TooLong
esc_html( $cap )
)
);
}
$capability->set_map_callback( $map_callback );
}
}

View File

@@ -0,0 +1,171 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Capabilities\Capability_Filters
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Capabilities;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\With_Hooks;
/**
* Class that adds filters to dynamically grant base capabilities and meta capabilities.
*
* @since 0.1.0
*/
class Capability_Filters implements With_Hooks {
/**
* Capability container.
*
* @since 0.1.0
* @var Capability_Container
*/
private $container;
/**
* Constructor.
*
* @since 0.1.0
*
* @param Capability_Container $container Container with the capabilities that filters should be added for.
*/
public function __construct( Capability_Container $container ) {
$this->container = $container;
}
/**
* Adds relevant WordPress hooks.
*
* @since 0.1.0
*/
public function add_hooks(): void {
add_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), 10, 2 );
add_filter( 'map_meta_cap', array( $this, 'filter_map_meta_cap' ), 10, 4 );
}
/**
* Gets the map of dynamic capabilities and which base capabilities they should map to.
*
* @since 0.1.0
*
* @return array<string, string[]> Map of `$cap => $required_caps` pairs.
*/
public function get_required_base_caps_map(): array {
$keys = $this->container->get_keys();
$caps_map = array();
foreach ( $keys as $key ) {
$capability = $this->container->get( $key );
if ( ! $capability instanceof Base_Capability ) {
continue;
}
$required_caps = $capability->get_required_caps();
if ( ! $required_caps ) { // Skip capabilities that aren't dynamic.
continue;
}
$caps_map[ $key ] = $required_caps;
}
return $caps_map;
}
/**
* Gets the map of meta capabilities and their map callbacks.
*
* @since 0.1.0
*
* @return array<string, callable> Map of `$cap => $map_callback` pairs.
*/
public function get_meta_map_callbacks_map(): array {
$keys = $this->container->get_keys();
$callbacks_map = array();
foreach ( $keys as $key ) {
$capability = $this->container->get( $key );
if ( ! $capability instanceof Meta_Capability ) {
continue;
}
$callbacks_map[ $key ] = $capability->get_map_callback();
}
return $callbacks_map;
}
/**
* Filters a user's capabilities, granting dynamic capabilities based on existing base capabilities.
*
* This should be used as a callback for the {@see 'user_has_cap'} filter.
*
* @since 0.1.0
*
* @param array<string, bool> $allcaps Array of key/value pairs where keys represent a capability name and boolean
* values represent whether the user has that capability.
* @param string[] $caps Required primitive capabilities for the requested capability.
* @return array<string, bool> Filtered $allcaps, including dynamically granted custom capabilities.
*/
public function filter_user_has_cap( array $allcaps, array $caps ): array {
// Bail early if not checking for any of the relevant capabilities.
$relevant_caps = array_filter(
$caps,
function ( string $cap ) {
return $this->container->has( $cap );
}
);
if ( count( $relevant_caps ) === 0 ) {
return $allcaps;
}
$required_base_caps_map = $this->get_required_base_caps_map();
foreach ( $required_base_caps_map as $cap => $required_caps ) {
$grant = true;
foreach ( $required_caps as $required_cap ) {
if ( ! isset( $allcaps[ $required_cap ] ) || ! $allcaps[ $required_cap ] ) {
$grant = false;
break;
}
}
$allcaps[ $cap ] = $grant;
}
return $allcaps;
}
/**
* Filters the mapping of a meta capability to one or more base capabilities.
*
* This should be used as a callback for the {@see 'map_meta_cap'} filter.
*
* @since 0.1.0
*
* @param string[] $caps Primitive capabilities required of the user.
* @param string $cap Capability being checked.
* @param int $user_id User ID.
* @param mixed[] $args Additional arguments passed alongside the capability check.
* @return string[] Filtered $caps, potentially altered by the relevant map callback.
*/
public function filter_map_meta_cap( array $caps, string $cap, int $user_id, array $args ): array {
// Bail early if not checking for any of the relevant capabilities.
if ( ! $this->container->has( $cap ) ) {
return $caps;
}
$meta_map_callbacks_map = $this->get_meta_map_callbacks_map();
if ( ! isset( $meta_map_callbacks_map[ $cap ] ) ) {
return $caps;
}
$map_callback = $meta_map_callbacks_map[ $cap ];
$required_caps = $map_callback( $user_id, ...$args );
if ( ! is_array( $required_caps ) || ! $required_caps ) { // Prevent invalid return values.
return $caps;
}
return $required_caps;
}
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Capabilities\Contracts\Capability
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Capabilities\Contracts;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\With_Key;
/**
* Interface for a WordPress capability.
*
* @since 0.1.0
*/
interface Capability extends With_Key {
}

View File

@@ -0,0 +1,71 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Capabilities\Meta_Capability
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Capabilities;
/**
* Class representing a WordPress meta capability.
*
* A meta capability is a capability that is mapped to one or more base capabilities based on dynamic logic.
*
* @since 0.1.0
*/
class Meta_Capability extends Abstract_Capability {
/**
* Callback function to determine the required base capabilities needed to grant this meta capability.
*
* @since 0.1.0
* @var callable
*/
private $map_callback;
/**
* Constructor.
*
* @since 0.1.0
*
* @param string $key Capability key.
* @param ?callable $map_callback Optional. Callback function to determine the required base capabilities needed to
* grant this meta capability. function receives the user ID and any additional
* parameters passed alongside the capability check and must return an array.
* Default null.
*/
public function __construct( string $key, ?callable $map_callback = null ) {
parent::__construct( $key );
$this->map_callback = $map_callback ?? function () {
return array();
};
}
/**
* Sets the callback function to determine the required base capabilities needed to grant this meta capability.
*
* @since 0.1.0
*
* @param callable $map_callback Callback function to determine the required base capabilities needed to grant this
* meta capability. The function receives the user ID and any additional parameters
* passed alongside the capability check and must return an array.
*/
public function set_map_callback( callable $map_callback ): void {
$this->map_callback = $map_callback;
}
/**
* Gets the callback function to determine the required base capabilities needed to grant this meta capability.
*
* @since 0.1.0
*
* @return callable Callback function to determine the required base capabilities needed to grant this meta
* capability. The function receives the user ID and any additional parameters passed alongside
* the capability check and must return an array.
*/
public function get_map_callback(): callable {
return $this->map_callback;
}
}

View File

@@ -0,0 +1,93 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Array_Key_Value_Repository
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Key_Value_Repository;
/**
* Class for a repository that stores keys and values in an array.
*
* @since 0.1.0
*/
class Array_Key_Value_Repository implements Key_Value_Repository {
/**
* The items in the repository.
*
* @since 0.1.0
* @var array<string, mixed>
*/
private $items = array();
/**
* Constructor.
*
* @since 0.1.0
*
* @param array<string, mixed> $initial_items Optional. Initial keys and values. Default empty array.
*/
public function __construct( array $initial_items = array() ) {
$this->items = $initial_items;
}
/**
* Checks whether a value for the given key exists in the repository.
*
* @since 0.1.0
*
* @param string $key Item key.
* @return bool True if a value for the key exists, false otherwise.
*/
public function exists( string $key ): bool {
return isset( $this->items[ $key ] );
}
/**
* Gets the value for a given key from the repository.
*
* @since 0.1.0
*
* @param string $key Item key.
* @param mixed $default Optional. Value to return if no value exists for the key. Default null.
* @return mixed Value for the key, or the default if no value exists.
*/
public function get( string $key, $default = null ) {
return isset( $this->items[ $key ] ) ? $this->items[ $key ] : $default;
}
/**
* Updates the value for a given key in the repository.
*
* @since 0.1.0
*
* @param string $key Item key.
* @param mixed $value New value to set for the key.
* @return bool True on success, false on failure.
*/
public function update( string $key, $value ): bool {
$this->items[ $key ] = $value;
return true;
}
/**
* Deletes the data for a given key from the repository.
*
* @since 0.1.0
*
* @param string $key Item key.
* @return bool True on success, false on failure.
*/
public function delete( string $key ): bool {
if ( ! isset( $this->items[ $key ] ) ) {
return false;
}
unset( $this->items[ $key ] );
return true;
}
}

View File

@@ -0,0 +1,172 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Array_Registry
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General;
use ArrayAccess;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Arrayable;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Registry;
/**
* Class for a registry that registers arguments on an associative array.
*
* This can be used to provide a consistent registration API around data structures which in WordPress can only be
* added via filters.
*
* @since 0.1.0
*/
class Array_Registry implements Registry, Arrayable, ArrayAccess {
/**
* The registered items.
*
* @since 0.1.0
* @var array<string, object>
*/
private $items = array();
/**
* Constructor.
*
* @since 0.1.0
*
* @param array<string, array<string, mixed>> $initial_items Optional. Initial array of registered items. Default
* empty array.
*/
public function __construct( array $initial_items = array() ) {
$this->items = array_map(
static function ( $item ) {
return (object) $item;
},
$initial_items
);
}
/**
* Registers an item with the given key and arguments.
*
* @since 0.1.0
*
* @param string $key Item key.
* @param array<string, mixed> $args Item registration arguments.
* @return bool True on success, false on failure.
*/
public function register( string $key, array $args ): bool {
$this->items[ $key ] = (object) $args;
return true;
}
/**
* Checks whether an item with the given key is registered.
*
* @since 0.1.0
*
* @param string $key Item key.
* @return bool True if the item is registered, false otherwise.
*/
public function is_registered( string $key ): bool {
return isset( $this->items[ $key ] );
}
/**
* Gets the registered item for the given key from the registry.
*
* @since 0.1.0
*
* @param string $key Item key.
* @return object|null The registered item definition, or `null` if not registered.
*/
public function get_registered( string $key ) {
if ( ! isset( $this->items[ $key ] ) ) {
return null;
}
return $this->items[ $key ];
}
/**
* Gets all items from the registry.
*
* @since 0.1.0
*
* @return array<string, object> Associative array of keys and their item definitions, or empty array if nothing is
* registered. This is effectively the array representation of the registry.
*/
public function get_all_registered(): array {
return $this->items;
}
/**
* Returns the array representation of the registry.
*
* @since 0.1.0
*
* @return array<string, mixed> Array representation.
*/
public function to_array(): array {
return array_map(
static function ( $item ) {
if ( $item instanceof Arrayable ) {
return $item->to_array();
}
return (array) $item;
},
$this->items
);
}
/**
* Checks if an item for the given key is registered.
*
* @since 0.1.0
*
* @param mixed $key Item key.
* @return bool True if the item is registered, false otherwise.
*/
#[\ReturnTypeWillChange]
public function offsetExists( $key ) {
return $this->is_registered( $key );
}
/**
* Gets the item for the given key from the container.
*
* @since 0.1.0
*
* @param mixed $key Item key.
* @return object|null The registered item definition, or `null` if not registered.
*/
#[\ReturnTypeWillChange]
public function offsetGet( $key ) {
return $this->get_registered( $key );
}
/**
* Registers an item with the given key and arguments.
*
* @since 0.1.0
*
* @param string $key Item key.
* @param array<string, mixed> $args Item registration arguments.
*/
#[\ReturnTypeWillChange]
public function offsetSet( $key, $args ) {
$this->register( $key, $args );
}
/**
* Magic unset method. Does nothing at this time, as , registries do not allow unregistering items.
*
* @since 0.1.0
*
* @param mixed $key Item key.
*/
#[\ReturnTypeWillChange]
public function offsetUnset( $key ) {
// Empty method body.
}
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Arrayable
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts;
/**
* Interface for something that can return its array representation.
*
* @since 0.1.0
*/
interface Arrayable {
/**
* Returns the array representation.
*
* @since 0.1.0
*
* @return mixed[] Array representation.
*/
public function to_array(): array;
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Collection
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts;
use Countable;
use IteratorAggregate;
use Traversable;
/**
* Interface for a collection.
*
* @since 0.1.0
*/
interface Collection extends IteratorAggregate, Countable {
/**
* Returns an iterator for the collection.
*
* @since 0.1.0
*
* @return Traversable Collection iterator.
*/
public function getIterator(): Traversable; /* @phpstan-ignore-line */
/**
* Returns the size of the collection.
*
* @since 0.1.0
*
* @return int Collection size.
*/
public function count(): int;
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Container
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts;
/**
* Interface for a container with read and write access.
*
* @since 0.1.0
*/
interface Container extends Container_Readonly {
/**
* Sets the given entry under the given key in the container.
*
* @since 0.1.0
*
* @param string $key Key.
* @param callable $creator Entry creator closure.
*/
public function set( string $key, callable $creator ): void;
/**
* Unsets the entry under the given key in the container.
*
* @since 0.1.0
*
* @param string $key Key.
*/
public function unset( string $key ): void;
}

View File

@@ -0,0 +1,50 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Container_Readonly
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Exception\Not_Found_Exception;
/**
* Interface for a container with readonly access.
*
* @since 0.1.0
*/
interface Container_Readonly {
/**
* Checks if an entry for the given key exists in the container.
*
* @since 0.1.0
*
* @param string $key Key.
* @return bool True if the entry exists in the container, false otherwise.
*/
public function has( string $key ): bool;
/**
* Gets the entry for the given key from the container.
*
* @since 0.1.0
*
* @param string $key Key.
* @return mixed Entry for the given key.
*
* @throws Not_Found_Exception Thrown when entry with given key is not found.
*/
public function get( string $key );
/**
* Gets all keys in the container.
*
* @since 0.1.0
*
* @return string[] List of keys.
*/
public function get_keys(): array;
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Hook_Registrar
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts;
/**
* Interface for a class that adds the relevant hook to register items.
*
* @since 0.1.0
*/
interface Hook_Registrar {
/**
* Adds a callback that registers the items to the relevant hook.
*
* The callback receives a registry instance as the sole parameter, allowing to call the
* {@see Registry::register()} method.
*
* @since 0.1.0
*
* @param callable $register_callback Callback to register the items.
*/
public function add_register_callback( callable $register_callback ): void;
}

View File

@@ -0,0 +1,54 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Key_Value
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts;
/**
* Interface for a key-value pair.
*
* @since 0.1.0
*/
interface Key_Value extends With_Key {
/**
* Checks whether the item has a value set.
*
* @since 0.1.0
*
* @return bool True if a value is set, false otherwise.
*/
public function has_value(): bool;
/**
* Gets the value for the item.
*
* @since 0.1.0
*
* @return mixed Value for the item.
*/
public function get_value();
/**
* Updates the value for the item.
*
* @since 0.1.0
*
* @param mixed $value New value to set for the item.
* @return bool True on success, false on failure.
*/
public function update_value( $value ): bool;
/**
* Deletes the data for the item.
*
* @since 0.1.0
*
* @return bool True on success, false on failure.
*/
public function delete_value(): bool;
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Key_Value_Repository
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts;
/**
* Interface for a repository for key-value pairs.
*
* @since 0.1.0
*/
interface Key_Value_Repository {
/**
* Checks whether a value for the given key exists in the repository.
*
* @since 0.1.0
*
* @param string $key Item key.
* @return bool True if a value for the key exists, false otherwise.
*/
public function exists( string $key ): bool;
/**
* Gets the value for a given key from the repository.
*
* @since 0.1.0
*
* @param string $key Item key.
* @param mixed $default Optional. Value to return if no value exists for the key. Default null.
* @return mixed Value for the key, or the default if no value exists.
*/
public function get( string $key, $default = null );
/**
* Updates the value for a given key in the repository.
*
* @since 0.1.0
*
* @param string $key Item key.
* @param mixed $value New value to set for the key.
* @return bool True on success, false on failure.
*/
public function update( string $key, $value ): bool;
/**
* Deletes the data for a given key from the repository.
*
* @since 0.1.0
*
* @param string $key Item key.
* @return bool True on success, false on failure.
*/
public function delete( string $key ): bool;
}

View File

@@ -0,0 +1,58 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Registry
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts;
/**
* Interface for a registry of items.
*
* @since 0.1.0
*/
interface Registry {
/**
* Registers an item with the given key and arguments.
*
* @since 0.1.0
*
* @param string $key Item key.
* @param array<string, mixed> $args Item registration arguments.
* @return bool True on success, false on failure.
*/
public function register( string $key, array $args ): bool;
/**
* Checks whether an item with the given key is registered.
*
* @since 0.1.0
*
* @param string $key Item key.
* @return bool True if the item is registered, false otherwise.
*/
public function is_registered( string $key ): bool;
/**
* Gets the registered item for the given key from the registry.
*
* @since 0.1.0
*
* @param string $key Item key.
* @return object|null The registered item definition, or `null` if not registered.
*/
public function get_registered( string $key );
/**
* Gets all items from the registry.
*
* @since 0.1.0
*
* @return array<string, mixed> Associative array of keys and their item definitions, or empty array if nothing is
* registered.
*/
public function get_all_registered(): array;
}

View File

@@ -0,0 +1,28 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\With_Capabilities
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts;
/**
* Interface for a class that can check for capabilities.
*
* @since 0.1.0
*/
interface With_Capabilities {
/**
* Checks whether the entity has the given capability.
*
* @since 0.1.0
*
* @param string $cap Capability name.
* @param mixed ...$args Optional further parameters, typically starting with an entity ID.
* @return bool True if the entity has the given capability false otherwise.
*/
public function has_cap( string $cap, ...$args ): bool;
}

View File

@@ -0,0 +1,24 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\With_Hooks
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts;
/**
* Interface for a class that includes WordPress hooks (actions and/or filters).
*
* @since 0.1.0
*/
interface With_Hooks {
/**
* Adds relevant WordPress hooks.
*
* @since 0.1.0
*/
public function add_hooks(): void;
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\With_Key
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts;
/**
* Interface for an item that has a key (a string identifier unique for the kind of item).
*
* @since 0.1.0
*/
interface With_Key {
/**
* Gets the key of the item.
*
* @since 0.1.0
*
* @return string Item key.
*/
public function get_key(): string;
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\With_Registration_Args
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts;
/**
* Interface for an item with registration arguments.
*
* @since 0.1.0
*/
interface With_Registration_Args {
/**
* Gets the registration arguments for the item.
*
* @since 0.1.0
*
* @return array<string, mixed> Item registration arguments.
*/
public function get_registration_args(): array;
}

View File

@@ -0,0 +1,103 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Current_User
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\With_Capabilities;
/**
* Class representing the current user.
*
* @since 0.1.0
*/
class Current_User implements With_Capabilities {
/**
* Gets the current user ID.
*
* @since 0.1.0
*
* @return int The current user ID, or `0` if no user is signed in.
*/
public function get_id(): int {
return get_current_user_id();
}
/**
* Sets the current user to the one with the given ID.
*
* @since 0.1.0
*
* @param int $id User ID.
*/
public function set( int $id ): void {
wp_set_current_user( $id );
}
/**
* Checks whether current user is logged in.
*
* @since 0.1.0
*
* @return bool True if the user is logged in, false otherwise.
*/
public function is_logged_in(): bool {
return is_user_logged_in();
}
/**
* Checks whether the current user has the given capability.
*
* @since 0.1.0
*
* @param string $cap Capability name.
* @param mixed ...$args Optional further parameters, typically starting with an entity ID.
* @return bool True if the user has the given capability false otherwise.
*/
public function has_cap( string $cap, ...$args ): bool {
return wp_get_current_user()->has_cap( $cap, ...$args );
}
/**
* Creates a cryptographic token tied to the given action and the current user session.
*
* @since 0.1.0
*
* @param string $action Action to add context to the nonce.
* @return string The token.
*/
public function create_nonce( string $action ): string {
return wp_create_nonce( $action );
}
/**
* Verifies that the given security nonce is correct for the given action and the current user session.
*
* @since 0.1.0
*
* @param string $nonce Nonce value to verify.
* @param string $action Action context for the nonce.
* @return bool True if the nonce is valid, false otherwise.
*/
public function verify_nonce( string $nonce, string $action ): bool {
return (bool) wp_verify_nonce( $nonce, $action );
}
/**
* Checks whether the current user is a super admin.
*
* By default, super admins have access to all capabilities, unless explicitly denied to everyone.
*
* @since 0.1.0
*
* @return bool True if the user is a super admin, false otherwise.
*/
public function is_super_admin(): bool {
return is_super_admin();
}
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Exception\Invalid_Type_Exception
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Exception;
use RuntimeException;
/**
* Exception class for when a value has an invalid type.
*
* @since 0.1.0
*/
class Invalid_Type_Exception extends RuntimeException {
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Exception\Not_Found_Exception
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Exception;
use RuntimeException;
/**
* Exception class for when a value for a key is not found.
*
* @since 0.1.0
*/
class Not_Found_Exception extends RuntimeException {
}

View File

@@ -0,0 +1,86 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Exception\WP_Error_Exception
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Exception;
use RuntimeException;
use WP_Error;
/**
* Exception class equivalent to a WP_Error object.
*
* @since 0.1.0
*/
class WP_Error_Exception extends RuntimeException {
/**
* Text based error code.
*
* @since 0.1.0
* @var string
*/
private $error_code = '';
/**
* Gets the text based error code.
*
* @since 0.1.0
*
* @return string Text based error code.
*/
public function get_error_code(): string {
if ( ! $this->error_code ) {
// Fall back to using error message as error code.
return preg_replace(
'/[^a-z0-9_]+/',
'',
str_replace( array( ' ', '-' ), '_', strtolower( $this->getMessage() ) )
);
}
return $this->error_code;
}
/**
* Sets the text based error code.
*
* @since 0.1.0
*
* @param string $error_code Text based error code.
*/
public function set_error_code( string $error_code ): void {
$this->error_code = $error_code;
}
/**
* Creates a new WP_Error exception based on error code, and error message.
*
* @since 0.1.0
*
* @param string $error_code Text based error code.
* @param string $message Error message.
* @return WP_Error_Exception New exception instance.
*/
public static function create( string $error_code, string $message ): WP_Error_Exception {
$instance = new self( $message );
$instance->set_error_code( $error_code );
return $instance;
}
/**
* Creates a new WP_Error exception from the given WP_Error object.
*
* @since 0.1.0
*
* @param WP_Error $error WP_Error object.
* @return WP_Error_Exception New exception instance.
*/
public static function from_wp_error( WP_Error $error ): WP_Error_Exception {
return self::create( $error->get_error_code(), $error->get_error_message() );
}
}

View File

@@ -0,0 +1,123 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Generic_Key_Value
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Key_Value;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Key_Value_Repository;
/**
* Class representing a generic key-value pair.
*
* Should typically not be used directly, but rather through a more specific class extending it.
*
* @since 0.1.0
*/
class Generic_Key_Value implements Key_Value {
/**
* Repository used for the item.
*
* @since 0.1.0
* @var Key_Value_Repository
*/
protected $repository;
/**
* Item key.
*
* @since 0.1.0
* @var string
*/
protected $key;
/**
* Item default value.
*
* @since 0.1.0
* @var mixed
*/
protected $default_value;
/**
* Constructor.
*
* @since 0.1.0
*
* @param Key_Value_Repository $repository Repository used for the item.
* @param string $key Item key.
* @param mixed $default_value Optional. Default value for the item if not set in the repository.
* If null, it will be ignored. Default null.
*/
public function __construct( Key_Value_Repository $repository, string $key, $default_value = null ) {
$this->repository = $repository;
$this->key = $key;
$this->default_value = $default_value;
}
/**
* Checks whether the item has a value set.
*
* @since 0.1.0
*
* @return bool True if a value is set, false otherwise.
*/
public function has_value(): bool {
return $this->repository->exists( $this->key );
}
/**
* Gets the value for the item.
*
* @since 0.1.0
*
* @return mixed Value for the item.
*/
public function get_value() {
// Pass default value if set.
if ( isset( $this->default_value ) ) {
return $this->repository->get( $this->key, $this->default_value );
}
return $this->repository->get( $this->key );
}
/**
* Updates the value for the item.
*
* @since 0.1.0
*
* @param mixed $value New value to set for the item.
* @return bool True on success, false on failure.
*/
public function update_value( $value ): bool {
return $this->repository->update( $this->key, $value );
}
/**
* Deletes the data for the item.
*
* @since 0.1.0
*
* @return bool True on success, false on failure.
*/
public function delete_value(): bool {
return $this->repository->delete( $this->key );
}
/**
* Gets the key of the item.
*
* @since 0.1.0
*
* @return string Item key.
*/
public function get_key(): string {
return $this->key;
}
}

View File

@@ -0,0 +1,76 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Input
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General;
/**
* Read-only class for filtering input, effectively for access of immutable input data.
*
* @since 0.1.0
*/
class Input {
/**
* Map of input type to superglobal array.
*
* For use as fallback only.
*
* @since 0.1.0
* @var array<int, array<string, mixed>>
*/
private $fallback_map;
/**
* Constructor.
*
* @since 0.1.0
*/
public function __construct() {
// Fallback map for environments where filter_input may not work with ENV or SERVER types.
$this->fallback_map = array(
INPUT_ENV => $_ENV,
INPUT_SERVER => $_SERVER, // phpcs:ignore WordPress.VIP.SuperGlobalInputUsage
);
}
/**
* Gets a specific external variable by name and optionally filters it.
*
* @since 0.1.0
*
* @link https://php.net/manual/en/function.filter-input.php
*
* @param int $type One of INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER, or INPUT_ENV.
* @param string $variable_name Name of a variable to get.
* @param int $filter Optional. The ID of the filter to apply. The manual page lists the available
* filters.
* @param mixed $options Optional. Associative array of options or bitwise disjunction of flags. If filter
* accepts options, flags can be provided in "flags" field of array.
* @return mixed Value of the requested variable on success, false if the filter fails, null if the $variable_name
* variable is not set. If the flag FILTER_NULL_ON_FAILURE is used, it returns false if the variable
* is not set and null if the filter fails.
*/
public function filter( $type, $variable_name, $filter = FILTER_DEFAULT, $options = 0 ) {
/* @phpstan-ignore-next-line */
$value = filter_input( $type, $variable_name, $filter, $options );
/*
* Fallback for environments where filter_input may not work with specific types.
* This is only used for affected input types and if the value is not set.
*/
if (
isset( $this->fallback_map[ $type ] )
&& in_array( $value, array( null, false ), true )
&& array_key_exists( $variable_name, $this->fallback_map[ $type ] )
) {
return filter_var( $this->fallback_map[ $type ][ $variable_name ], $filter, $options );
}
return $value;
}
}

View File

@@ -0,0 +1,67 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Mutable_Input
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General;
/**
* Read-only class for filtering mutable input, effectively for superglobal access.
*
* It is recommended to use the regular Input class for most input handling, while this class is useful for e.g. unit
* tests where you want to mock input values.
*
* @since 0.1.0
*/
class Mutable_Input extends Input {
/**
* Gets a specific external variable by name and optionally filters it.
*
* @since 0.1.0
*
* @link https://php.net/manual/en/function.filter-input.php
*
* @param int $type One of INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER, or INPUT_ENV.
* @param string $variable_name Name of a variable to get.
* @param int $filter Optional. The ID of the filter to apply. The manual page lists the available
* filters.
* @param mixed $options Optional. Associative array of options or bitwise disjunction of flags. If filter
* accepts options, flags can be provided in "flags" field of array.
* @return mixed Value of the requested variable on success, false if the filter fails, null if the $variable_name
* variable is not set. If the flag FILTER_NULL_ON_FAILURE is used, it returns false if the variable
* is not set and null if the filter fails.
*/
public function filter( $type, $variable_name, $filter = FILTER_DEFAULT, $options = 0 ) {
switch ( $type ) {
case INPUT_GET:
// phpcs:ignore WordPress.Security.NonceVerification
$superglobal = $_GET;
break;
case INPUT_POST:
// phpcs:ignore WordPress.Security.NonceVerification
$superglobal = $_POST;
break;
case INPUT_SERVER:
$superglobal = $_SERVER;
break;
case INPUT_COOKIE:
$superglobal = $_COOKIE;
break;
case INPUT_ENV:
$superglobal = $_ENV;
break;
default:
return null;
}
if ( ! isset( $superglobal[ $variable_name ] ) ) {
return null;
}
return filter_var( $superglobal[ $variable_name ], $filter, $options );
}
}

View File

@@ -0,0 +1,113 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Network_Env
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General;
/**
* Read-only class containing utilities for the network environment.
*
* @since 0.1.0
*/
class Network_Env {
/**
* Checks whether this WordPress installation is a multisite installation.
*
* @since 0.1.0
*
* @return bool True if a multisite installation, false otherwise.
*/
public function is_multisite(): bool {
return is_multisite();
}
/**
* Returns the network ID.
*
* @since 0.1.0
*
* @return int The network ID.
*/
public function id(): int {
return get_current_network_id();
}
/**
* Returns the network URL, i.e. relative to the home page.
*
* @since 0.1.0
*
* @param string $relative_path Optional. Relative path. Default '/'.
* @return string The site URL.
*/
public function url( string $relative_path = '/' ): string {
$url = network_home_url( $relative_path );
/*
* In Multisite, network_home_url() returns a URL with a trailing slash, even if the path is empty.
* This is inconsistent with home_url(), so we fix that here.
*/
if ( '' === $relative_path ) {
$url = untrailingslashit( $url );
}
return $url;
}
/**
* Returns the network's WordPress URL, in which WordPress core is installed.
*
* @since 0.1.0
*
* @param string $relative_path Optional. Relative path. Default '/'.
* @return string The WordPress URL.
*/
public function wp_url( string $relative_path = '/' ): string {
$url = network_site_url( $relative_path );
/*
* In Multisite, network_site_url() returns a URL with a trailing slash, even if the path is empty.
* This is inconsistent with site_url(), so we fix that here.
*/
if ( '' === $relative_path ) {
$url = untrailingslashit( $url );
}
return $url;
}
/**
* Returns the network admin URL (typically the 'wp-admin/network' directory within the WordPress URL).
*
* @since 0.1.0
*
* @param string $relative_path Optional. Relative path. Default '/'.
* @return string The admin URL.
*/
public function admin_url( string $relative_path = '/' ): string {
return network_admin_url( $relative_path );
}
/**
* Returns the active plugins for the network.
*
* @since 0.1.0
*
* @return string[] List of plugin basenames, relative to the plugins directory.
*/
public function get_active_plugins(): array {
if ( ! $this->is_multisite() ) {
return array();
}
$active_plugins = (array) get_site_option( 'active_sitewide_plugins', array() );
$active_plugins = array_keys( $active_plugins );
sort( $active_plugins );
return $active_plugins;
}
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Network_Runner
*
* @since n.e.x.t
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General;
/**
* Class for running logic across the network.
*
* @since n.e.x.t
*/
class Network_Runner {
/**
* Network environment.
*
* @since n.e.x.t
* @var Network_Env
*/
private $network_env;
/**
* Constructor.
*
* @since n.e.x.t
*
* @param Network_Env $network_env Network environment.
*/
public function __construct( Network_Env $network_env ) {
$this->network_env = $network_env;
}
/**
* Runs a given callback for all sites in the network or for sites matching certain criteria.
*
* If not in a Multisite environment, the callback will simply be invoked once for the current site.
*
* @since n.e.x.t
*
* @param callable $callback Callback function to run for each site. It must be parameter-less
* and return a boolean for whether it completed successfully or not.
* @param array<string, mixed> $site_query_args Optional. Additional arguments for querying sites.
* @return bool True if the callback successfully ran for all relevant sites, false otherwise.
*/
public function run_for_sites( callable $callback, array $site_query_args = array() ): bool {
if ( ! $this->network_env->is_multisite() ) {
return (bool) call_user_func( $callback );
}
$site_query_args = wp_parse_args(
$site_query_args,
array( 'number' => 100 )
);
$site_query_args['fields'] = 'ids';
$site_ids = get_sites( $site_query_args );
// Iterate through the site and store for which ones the callback ran successfully.
$success_ids = array();
foreach ( $site_ids as $site_id ) {
switch_to_blog( $site_id );
if ( call_user_func( $callback ) ) {
$success_ids[] = $site_id;
}
restore_current_blog();
}
return count( $site_ids ) === count( $success_ids );
}
}

View File

@@ -0,0 +1,103 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Plugin_Env
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General;
/**
* Read-only class containing utilities for the plugin environment.
*
* @since 0.1.0
*/
class Plugin_Env {
/**
* Absolute path of the plugin main file.
*
* @since 0.1.0
* @var string
*/
private $main_file;
/**
* Current plugin version number.
*
* @since 0.1.0
* @var string
*/
private $version;
/**
* Constructor.
*
* @since 0.1.0
*
* @param string $main_file Absolute path to the plugin main file.
* @param string $version Current plugin version number.
*/
public function __construct( string $main_file, string $version ) {
$this->main_file = $main_file;
$this->version = $version;
}
/**
* Returns the absolute path to the plugin main file.
*
* @since 0.1.0
*
* @return string Absolute path to the plugin main file.
*/
public function main_file(): string {
return $this->main_file;
}
/**
* Returns the current plugin version number.
*
* @since 0.1.0
*
* @return string Current plugin version number.
*/
public function version(): string {
return $this->version;
}
/**
* Returns the plugin basename.
*
* @since 0.1.0
*
* @return string Plugin basename.
*/
public function basename(): string {
return plugin_basename( $this->main_file );
}
/**
* Returns the absolute path for a relative path to the plugin directory.
*
* @since 0.1.0
*
* @param string $relative_path Optional. Relative path. Default '/'.
* @return string Absolute path.
*/
public function path( string $relative_path = '/' ): string {
return plugin_dir_path( $this->main_file ) . ltrim( $relative_path, '/' );
}
/**
* Returns the full URL for a path relative to the plugin directory.
*
* @since 0.1.0
*
* @param string $relative_path Optional. Relative path. Default '/'.
* @return string Full URL.
*/
public function url( string $relative_path = '/' ): string {
return plugin_dir_url( $this->main_file ) . ltrim( $relative_path, '/' );
}
}

View File

@@ -0,0 +1,289 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Service_Container
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General;
use ArrayAccess;
use Closure;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Container;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Exception\Not_Found_Exception;
/**
* Class for a service container.
*
* @since 0.1.0
*/
class Service_Container implements Container, ArrayAccess {
/**
* Services stored in the container.
*
* @since 0.1.0
* @var array<string, callable>
*/
private $services = array();
/**
* Service instances already created.
*
* @since 0.1.0
* @var array<string, object>
*/
private $instances = array();
/**
* Listener callbacks.
*
* These callbacks are attached to a specific service and called whenever that service is resolved.
*
* @since 0.1.0
* @var array<string, callable[]>
*/
private $listener_callbacks = array();
/**
* Checks if a service for the given key exists in the container.
*
* @since 0.1.0
*
* @param string $key Service key.
* @return bool True if the service exists in the container, false otherwise.
*/
public function has( string $key ): bool {
return $this->is_bound( $key );
}
/**
* Gets the service for the given key from the container.
*
* @since 0.1.0
*
* @param string $key Service key.
* @return object Service for the given key.
*
* @throws Not_Found_Exception Thrown when service with given key is not found.
*/
public function get( string $key ) {
if ( ! isset( $this->services[ $key ] ) ) {
throw new Not_Found_Exception(
esc_html(
sprintf(
/* translators: %s: service key */
__( 'Service with key %s was not found in container', 'wp-oop-plugin-lib' ),
$key
)
)
);
}
return $this->resolve( $key );
}
/**
* Sets the given service under the given key in the container.
*
* @since 0.1.0
*
* @param string $key Service key.
* @param callable $creator Service creator closure.
*/
public function set( string $key, callable $creator ): void {
$this->bind( $key, $creator );
}
/**
* Unsets the service under the given key in the container.
*
* @since 0.1.0
*
* @param string $key Service key.
*/
public function unset( string $key ): void {
unset( $this->services[ $key ], $this->instances[ $key ] );
}
/**
* Gets all keys in the container.
*
* @since 0.1.0
*
* @return string[] List of keys.
*/
public function get_keys(): array {
return array_keys( $this->services );
}
/**
* Adds a listener callback for a service in the container.
*
* The callback will be called whenever that service is resolved, whether it is for the initial resolve, or after a
* subsequent change. The callback will receive the latest service instance and a reference to the container as
* parameters.
*
* @since 0.1.0
*
* @param string $key Service key.
* @param Closure $callback Listener callback.
*/
public function listen( string $key, Closure $callback ): void {
if ( ! isset( $this->listener_callbacks[ $key ] ) ) {
$this->listener_callbacks[ $key ] = array();
}
$this->listener_callbacks[ $key ][] = $callback;
}
/**
* Checks if a service for the given key exists in the container.
*
* @since 0.1.0
*
* @param string $key Service key.
* @return bool True if the service exists in the container, false otherwise.
*/
#[\ReturnTypeWillChange]
public function offsetExists( $key ) {
return $this->has( $key );
}
/**
* Gets the service for the given key from the container.
*
* @since 0.1.0
*
* @param string $key Service key.
* @return mixed Service for the given key.
*/
#[\ReturnTypeWillChange]
public function offsetGet( $key ) {
return $this->get( $key );
}
/**
* Sets the given service under the given key in the container.
*
* @since 0.1.0
*
* @param string $key Service key.
* @param mixed $value Service creator closure.
*/
#[\ReturnTypeWillChange]
public function offsetSet( $key, $value ) {
$this->set( $key, $value );
}
/**
* Unsets the service under the given key in the container.
*
* @since 0.1.0
*
* @param string $key Service key.
*/
#[\ReturnTypeWillChange]
public function offsetUnset( $key ) {
$this->unset( $key );
}
/**
* Binds the given service creator closure under the given key in the container.
*
* @since 0.1.0
*
* @param string $key Service key.
* @param Closure $service_creator Service creator closure.
*/
private function bind( string $key, Closure $service_creator ): void {
$this->services[ $key ] = $service_creator;
// If the service was already resolved, update it.
if ( $this->is_resolved( $key ) ) {
$this->drop_stale_instances( $key );
// If there are listeners attached, resolve immediately so that dependents do not get stale.
// Otherwise, it only needs to be resolved once explicitly retrieved.
if ( isset( $this->listener_callbacks[ $key ] ) ) {
$this->resolve( $key );
}
}
}
/**
* Resolves the service for the given key and returns the resolved instance.
*
* If the service was already resolved, it will simply return the existing instance.
*
* @since 0.1.0
*
* @param string $key Service key.
* @return object Service resolved for the given key.
*/
private function resolve( string $key ) {
if ( isset( $this->instances[ $key ] ) ) {
return $this->instances[ $key ];
}
$this->instances[ $key ] = $this->services[ $key ]( $this );
$this->fire_listener_callbacks( $key );
return $this->instances[ $key ];
}
/**
* Checks whether the service under the given key has been bound.
*
* @since 0.1.0
*
* @param string $key Service key.
* @return bool True if the service has been bound, false otherwise.
*/
private function is_bound( string $key ): bool {
return isset( $this->services[ $key ] ) || isset( $this->instances[ $key ] );
}
/**
* Checks whether the service under the given key has already been resolved.
*
* @since 0.1.0
*
* @param string $key Service key.
* @return bool True if the service has been resolved, false otherwise.
*/
private function is_resolved( string $key ): bool {
return isset( $this->instances[ $key ] );
}
/**
* Drops the instances under the given key.
*
* This method can be used to force the {@see Service_Container::resolve()} method to re-resolve.
*
* @since 0.1.0
*
* @param string $key Service key.
*/
private function drop_stale_instances( string $key ): void {
unset( $this->instances[ $key ] );
}
/**
* Fires all listener callbacks under the given key, if any are set.
*
* @since 0.1.0
*
* @param string $key Service key.
*/
private function fire_listener_callbacks( string $key ): void {
if ( ! isset( $this->listener_callbacks[ $key ] ) ) {
return;
}
foreach ( $this->listener_callbacks[ $key ] as $callback ) {
$callback( $this->instances[ $key ], $this );
}
}
}

View File

@@ -0,0 +1,117 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Site_Env
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General;
/**
* Read-only class containing utilities for the site environment.
*
* @since 0.1.0
*/
class Site_Env {
/**
* Returns the site ID.
*
* @since 0.1.0
*
* @return int The site ID.
*/
public function id(): int {
return get_current_blog_id();
}
/**
* Returns information about the site.
*
* @since 0.1.0
* @see get_bloginfo()
*
* @param string $field The site field to retrieve.
* @return string The site field value.
*/
public function info( string $field ): string {
return (string) get_bloginfo( $field );
}
/**
* Returns the site URL, i.e. relative to the home page.
*
* @since 0.1.0
*
* @param string $relative_path Optional. Relative path. Default '/'.
* @return string The site URL.
*/
public function url( string $relative_path = '/' ): string {
return home_url( $relative_path );
}
/**
* Returns the site's WordPress URL, in which WordPress core is installed.
*
* @since 0.1.0
*
* @param string $relative_path Optional. Relative path. Default '/'.
* @return string The WordPress URL.
*/
public function wp_url( string $relative_path = '/' ): string {
return site_url( $relative_path );
}
/**
* Returns the site admin URL (typically the 'wp-admin' directory within the WordPress URL).
*
* @since 0.1.0
*
* @param string $relative_path Optional. Relative path. Default '/'.
* @return string The admin URL.
*/
public function admin_url( string $relative_path = '/' ): string {
return admin_url( $relative_path );
}
/**
* Returns the active plugins for the site.
*
* Does not include network-activated plugins (relevant for multisite installations).
*
* @since 0.1.0
*
* @return string[] List of plugin basenames, relative to the plugins directory.
*/
public function get_active_plugins(): array {
$active_plugins = (array) get_option( 'active_plugins', array() );
$network_env = new Network_Env();
if ( ! $network_env->is_multisite() ) {
return $active_plugins;
}
return array_values( array_diff( $active_plugins, $network_env->get_active_plugins() ) );
}
/**
* Returns the active themes for the site.
*
* This is either just the active theme, or the active theme and the child theme if a child theme is active.
*
* @since 0.1.0
*
* @return string[] List of theme directories, relative to the themes directory.
*/
public function get_active_themes(): array {
$parent_theme = get_template();
$child_theme = get_stylesheet();
$themes = array();
if ( $child_theme !== $parent_theme ) {
$themes[] = $child_theme;
}
$themes[] = $parent_theme;
return $themes;
}
}

View File

@@ -0,0 +1,51 @@
<?php
/**
* Trait Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Traits\Cast_Value_By_Type
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Traits;
/**
* Trait with a function to cast a value by a type.
*
* @since 0.1.0
*/
trait Cast_Value_By_Type {
/**
* Casts the given value into the given type identifier.
*
* @since 0.1.0
*
* @param mixed $value Value to cast.
* @param string $type Type identifier. Supported values are 'bool', 'int', 'float', 'string', 'array', and
* 'object'.
* @return mixed The cast value.
*/
protected function cast_value_by_type( $value, string $type ) {
switch ( $type ) {
case 'bool':
case 'boolean':
return (bool) $value;
case 'int':
case 'integer':
return (int) $value;
case 'double':
case 'float':
return (float) $value;
case 'string':
return (string) $value;
case 'array':
case 'object': // Objects are handled as associative arrays in WordPress.
if ( ! $value ) {
return array();
}
return (array) $value;
}
return $value;
}
}

View File

@@ -0,0 +1,66 @@
<?php
/**
* Trait Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Traits\Maybe_Throw
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Traits;
use Exception;
/**
* Trait with a function to cast a value by a type.
*
* @since 0.1.0
*/
trait Maybe_Throw {
/**
* Calls a callback function that may throw exceptions, and catches exceptions unless `WP_DEBUG` is enabled.
*
* @since 0.1.0
*
* @param callable $callback Callback function.
* @param mixed[] $callback_args Parameters to pass to the callback function.
* @return bool True on success, or false if an exception was caught.
*
* @throws Exception Thrown based on the underlying callback, and only if `WP_DEBUG` is enabled.
*/
protected function maybe_throw( callable $callback, array $callback_args ): bool {
if ( WP_DEBUG ) {
call_user_func_array( $callback, $callback_args );
return true;
}
try {
call_user_func_array( $callback, $callback_args );
} catch ( Exception $e ) {
return false;
}
return true;
}
/**
* Calls a callback function that may return a `WP_Error`, and throws an exception only if `WP_DEBUG` is enabled.
*
* @since 0.1.0
*
* @param callable $callback Callback function.
* @param mixed[] $callback_args Parameters to pass to the callback function.
* @return bool True on success, or false if a `WP_Error` was returned.
*
* @throws Exception Thrown based on the underlying callback, and only if `WP_DEBUG` is enabled.
*/
protected function maybe_throw_wp_error( callable $callback, array $callback_args ): bool {
$result = call_user_func_array( $callback, $callback_args );
if ( is_wp_error( $result ) ) {
if ( WP_DEBUG ) {
throw new Exception( esc_html( $result->get_error_message() ) );
}
return false;
}
return true;
}
}

View File

@@ -0,0 +1,114 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts;
/**
* Interface for an HTTP request to another URL.
*
* @since 0.1.0
*/
interface Request {
const GET = 'GET';
const POST = 'POST';
const PUT = 'PUT';
const PATCH = 'PATCH';
const DELETE = 'DELETE';
const HEAD = 'HEAD';
const OPTIONS = 'OPTIONS';
const TRACE = 'TRACE';
/**
* Retrieves the URL to which the request should be sent.
*
* @since 0.1.0
*
* @return string The request URL.
*/
public function get_url(): string;
/**
* Retrieves the HTTP method to be used for the request.
*
* @since 0.1.0
*
* @return string The HTTP method for the request.
*/
public function get_method(): string;
/**
* Retrieves the data to be sent with the request.
*
* @since 0.1.0
*
* @return array<string, mixed> The request data, or an empty array. If the request method is not GET or HEAD, in
* case of an empty array the request body should be used instead.
*/
public function get_data(): array;
/**
* Retrieves the body to be sent with the request.
*
* A request may have either data or a body, but not both.
*
* @since 0.1.0
*
* @return string The request body, or an empty string. Only relevant if the request method is not GET or HEAD. In
* case of an empty string, the request data should be used instead.
*/
public function get_body(): string;
/**
* Retrieves the headers to be sent with the request.
*
* @since 0.1.0
*
* @return array<string, string> The request headers.
*/
public function get_headers(): array;
/**
* Retrieves the options to be used for sending the request.
*
* @since 0.1.0
*
* @return array<string, mixed> The request options.
*/
public function get_options(): array;
/**
* Adds a header to the request.
*
* @since 0.1.0
*
* @param string $name The header name.
* @param string $value The header value.
*/
public function add_header( string $name, string $value ): void;
/**
* Adds data to the request.
*
* @since 0.1.0
*
* @param string $name The name under which to send the data.
* @param mixed $value The value to send.
*/
public function add_data( string $name, $value ): void;
/**
* Adds an option to the request.
*
* @since 0.1.0
*
* @param string $name The option name.
* @param mixed $value The option value.
*/
public function add_option( string $name, $value ): void;
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request_Handler
*
* @since n.e.x.t
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Exception\Multiple_Requests_Exception;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Exception\Request_Exception;
use InvalidArgumentException;
/**
* Interface for an HTTP request to another URL.
*
* @since n.e.x.t
*/
interface Request_Handler {
/**
* Sends an HTTP request and returns the response.
*
* @since n.e.x.t
*
* @param Request $request The request to send.
* @return Response The response received.
*
* @throws Request_Exception Thrown if the request fails.
*/
public function request( Request $request ): Response;
/**
* Sends multiple HTTP requests and returns the responses.
*
* The returned responses are in the same order / use the same keys as the requests.
*
* If any of the requests fail, a Multiple_Requests_Exception will be thrown. The exception will contain the
* responses of the requests that succeeded, and the exceptions of the requests that failed.
*
* @since n.e.x.t
*
* @param array<string|int, Request> $requests The requests to send.
* @return array<string|int, Response> The responses received.
*
* @throws Multiple_Requests_Exception Thrown if one or more requests fail. If any requests succeeded, their
* responses will be included in the exception.
* @throws InvalidArgumentException Thrown if an invalid request is provided.
*/
public function request_multiple( array $requests ): array;
}

View File

@@ -0,0 +1,65 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Response
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts;
/**
* Interface for an HTTP response from another URL.
*
* @since 0.1.0
*/
interface Response {
/**
* Retrieves the HTTP status code received with the response.
*
* @since 0.1.0
*
* @return int The 3-digit HTTP status code.
*/
public function get_status(): int;
/**
* Retrieves the data received with the response.
*
* @since 0.1.0
*
* @return array<string, mixed> The response data, or an empty array if it could not automatically be decoded. In
* this case, the raw response body should be used.
*/
public function get_data(): array;
/**
* Retrieves the body received with the response.
*
* @since 0.1.0
*
* @return string The raw response body. If response data could be automatically decoded, this should be empty, and
* the response data should be used instead.
*/
public function get_body(): string;
/**
* Retrieves the headers received with the response.
*
* @since 0.1.0
*
* @return array<string, string> The response headers.
*/
public function get_headers(): array;
/**
* Retrieves a specific header received with the response.
*
* @since 0.1.0
*
* @param string $name The name of the header to retrieve.
* @return string The value of the header, or an empty string if the header was not found.
*/
public function get_header( string $name ): string;
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Delete_Request
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request;
/**
* Class for a HTTP DELETE request to another URL.
*
* @since 0.1.0
*/
class Delete_Request extends Generic_Request {
/**
* Constructor.
*
* @since 0.1.0
*
* @param string $url The URL to which the request should be sent.
* @param array<string, mixed> $data Optional. The data to be sent with the request. Default empty array.
* @param array<string, mixed> $args Optional. Additional options for the request. See {@see WP_Http::request()}
* for possible options. Providing the 'body' key is only allowed if the data
* parameter is empty, and only as a string. Default empty array.
*/
public function __construct( string $url, array $data = array(), array $args = array() ) {
$args['method'] = Request::DELETE;
parent::__construct( $url, $data, $args );
}
}

View File

@@ -0,0 +1,152 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Exception\Multiple_Requests_Exception
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Exception;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Response;
/**
* Exception class for when one or more HTTP requests of multiple requests sent together fail.
*
* @since 0.1.0
*/
class Multiple_Requests_Exception extends Request_Exception {
/**
* The exceptions for the requests that failed.
*
* @since 0.1.0
* @var array<string|int, Request_Exception>
*/
private $request_exceptions;
/**
* The responses for the requests that succeeded.
*
* @since 0.1.0
* @var array<string|int, Response>
*/
private $successful_responses;
/**
* Constructor.
*
* @since 0.1.0
*
* @param array<string|int, Request_Exception> $request_exceptions The exceptions for the requests that failed.
* @param array<string|int, Response> $successful_responses Optional. The responses for the requests that
* succeeded. Default empty array.
*/
public function __construct( array $request_exceptions, array $successful_responses = array() ) {
if ( ! $successful_responses ) {
$message = __( 'All requests failed.', 'wp-oop-plugin-lib' );
} elseif ( count( $request_exceptions ) === 1 ) {
$message = sprintf(
/* translators: %d: overall number of requests */
__( 'One out of %d requests failed.', 'wp-oop-plugin-lib' ),
count( $request_exceptions ) + count( $successful_responses )
);
} else {
$message = sprintf(
/* translators: 1: number of failed requests, 2: overall number of requests */
__( '%1$d out of %2$d requests failed.', 'wp-oop-plugin-lib' ),
count( $request_exceptions ),
count( $request_exceptions ) + count( $successful_responses )
);
}
parent::__construct( esc_html( $message ) );
$this->request_exceptions = $request_exceptions;
$this->successful_responses = $successful_responses;
}
/**
* Checks whether any of the requests succeeded.
*
* @since 0.1.0
*
* @return bool True if any of the requests succeeded, false otherwise.
*/
public function has_successful_responses(): bool {
return ! empty( $this->successful_responses );
}
/**
* Checks whether a specific request failed.
*
* @since 0.1.0
*
* @param string|int $key The key of the request.
* @return bool True if the request failed, false otherwise.
*/
public function has_failed( $key ): bool {
return isset( $this->request_exceptions[ $key ] );
}
/**
* Checks whether a specific request succeeded.
*
* @since 0.1.0
*
* @param string|int $key The key of the request.
* @return bool True if the request succeeded, false otherwise.
*/
public function has_succeeded( $key ): bool {
return isset( $this->successful_responses[ $key ] );
}
/**
* Retrieves the exception for a specific request that failed.
*
* Before calling this method, you should check whether the request failed using the has_failed() method.
*
* @since 0.1.0
*
* @param string|int $key The key of the request.
* @return Request_Exception The exception for the request that failed.
*/
public function get_exception( $key ): Request_Exception {
return $this->request_exceptions[ $key ];
}
/**
* Retrieves the response for a specific request that succeeded.
*
* Before calling this method, you should check whether the request succeeded using the has_succeeded() method.
*
* @since 0.1.0
*
* @param string|int $key The key of the request.
* @return Response The response for the request that succeeded.
*/
public function get_response( $key ): Response {
return $this->successful_responses[ $key ];
}
/**
* Retrieves the exceptions for the requests that failed.
*
* @since 0.1.0
*
* @return array<string|int, Request_Exception> The exceptions for the requests that failed.
*/
public function get_individual_exceptions(): array {
return $this->request_exceptions;
}
/**
* Retrieves the responses for the requests that succeeded.
*
* @since 0.1.0
*
* @return array<string|int, Response> The responses for the requests that succeeded.
*/
public function get_successful_responses(): array {
return $this->successful_responses;
}
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Exception\Request_Exception
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Exception;
use RuntimeException;
/**
* Exception class for when an HTTP request fails.
*
* @since 0.1.0
*/
class Request_Exception extends RuntimeException {
}

View File

@@ -0,0 +1,299 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Generic_Request
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Traits\Sanitize_Headers;
/**
* Class for a generic HTTP request to another URL.
*
* @since 0.1.0
*/
class Generic_Request implements Request {
use Sanitize_Headers;
/**
* The URL to which the request should be sent.
*
* @since 0.1.0
* @var string
*/
private $url;
/**
* The HTTP method to be used for the request.
*
* @since 0.1.0
* @var string
*/
private $method;
/**
* The data to be sent with the request.
*
* This is alternative to the body property, and should be used if the data will be sent as form data (e.g. for a
* POST request) or as query parameters (e.g. for a GET request).
*
* @since 0.1.0
* @var array<string, mixed>
*/
private $data;
/**
* The body to be sent with the request.
*
* This is alternative to the data property and should only be used if the data is not suitable for form data or
* query parameters.
*
* @since 0.1.0
* @var string
*/
private $body;
/**
* The headers to be sent with the request.
*
* @since 0.1.0
* @var array<string, string>
*/
private $headers;
/**
* Additional options for the request.
*
* @since 0.1.0
* @var array<string, mixed>
*/
private $options;
/**
* Constructor.
*
* @since 0.1.0
*
* @param string $url The URL to which the request should be sent.
* @param array<string, mixed> $data Optional. The data to be sent with the request. Default empty array.
* @param array<string, mixed> $args Optional. Additional options for the request. See {@see WP_Http::request()}
* for possible options. Providing the 'body' key is only allowed if the data
* parameter is empty, and only as a string. Default empty array.
*/
public function __construct( string $url, array $data = array(), array $args = array() ) {
if ( $data && isset( $args['body'] ) && $args['body'] ) {
_doing_it_wrong(
__METHOD__,
// phpcs:ignore Generic.Files.LineLength.TooLong
esc_html__( 'Both a request data array and a request body were provided, but only one of them is allowed.', 'wp-oop-plugin-lib' ),
''
);
unset( $args['body'] );
}
$args = $this->sanitize_args( $args, __METHOD__ );
$this->url = $url;
$this->method = $args['method'] ?? Request::GET;
$this->data = $data;
$this->body = $args['body'] ?? '';
$this->headers = isset( $args['headers'] ) ? $this->sanitize_headers( $args['headers'] ) : array();
unset( $args['method'], $args['body'], $args['headers'] );
$this->options = $args;
}
/**
* Retrieves the URL to which the request should be sent.
*
* @since 0.1.0
*
* @return string The request URL.
*/
public function get_url(): string {
return $this->url;
}
/**
* Retrieves the HTTP method to be used for the request.
*
* @since 0.1.0
*
* @return string The HTTP method for the request.
*/
public function get_method(): string {
return $this->method;
}
/**
* Retrieves the data to be sent with the request.
*
* @since 0.1.0
*
* @return array<string, mixed> The request data, or an empty array. If the request method is not GET or HEAD, in
* case of an empty array the request body should be used instead.
*/
public function get_data(): array {
return $this->data;
}
/**
* Retrieves the body to be sent with the request.
*
* A request may have either data or a body, but not both.
*
* @since 0.1.0
*
* @return string The request body, or an empty string. Only relevant if the request method is not GET or HEAD. In
* case of an empty string, the request data should be used instead.
*/
public function get_body(): string {
return $this->body;
}
/**
* Retrieves the headers to be sent with the request.
*
* @since 0.1.0
*
* @return array<string, string> The request headers.
*/
public function get_headers(): array {
return $this->headers;
}
/**
* Retrieves the options to be used for sending the request.
*
* @since 0.1.0
*
* @return array<string, mixed> The request options.
*/
public function get_options(): array {
return $this->options;
}
/**
* Adds a header to the request.
*
* @since 0.1.0
*
* @param string $name The header name.
* @param string $value The header value.
*/
public function add_header( string $name, string $value ): void {
$this->headers[ $name ] = $value;
}
/**
* Adds data to the request.
*
* This is only possible if no hard-coded request body was provided for the request.
*
* @since 0.1.0
*
* @param string $name The name under which to send the data.
* @param mixed $value The value to send.
*/
public function add_data( string $name, $value ): void {
if ( $this->body ) {
_doing_it_wrong(
__METHOD__,
esc_html__( 'Data cannot be added to a request that already has a body.', 'wp-oop-plugin-lib' ),
''
);
return;
}
$this->data[ $name ] = $value;
}
/**
* Adds an option to the request.
*
* @since 0.1.0
*
* @param string $name The option name.
* @param mixed $value The option value.
*/
public function add_option( string $name, $value ): void {
$this->options[ $name ] = $value;
}
/**
* Sanitizes the provided arguments.
*
* For any invalid arguments, PHP warnings may be triggered, and they will be stripped.
*
* @since 0.1.0
*
* @param array<string, mixed> $args Request arguments, including but not limited to options.
* @param string $method PHP class method to reference in potential PHP warnings.
* @return array<string, mixed> The sanitized arguments.
*/
private function sanitize_args( array $args, string $method ): array {
if ( isset( $args['method'] ) && ! $this->is_valid_method( $args['method'] ) ) {
_doing_it_wrong(
esc_html( $method ),
esc_html(
sprintf(
/* translators: %s: invalid method string */
__( 'The value %s is not a valid HTTP request method.', 'wp-oop-plugin-lib' ),
(string) $args['method']
)
),
''
);
unset( $args['method'] );
}
if ( isset( $args['body'] ) && ! is_string( $args['body'] ) ) {
_doing_it_wrong(
esc_html( $method ),
esc_html__( 'The request body must be a string.', 'wp-oop-plugin-lib' ),
''
);
unset( $args['body'] );
}
if (
isset( $args['headers'] ) &&
( ! is_array( $args['headers'] ) || ( $args['headers'] && wp_is_numeric_array( $args['headers'] ) ) )
) {
_doing_it_wrong(
esc_html( $method ),
esc_html__( 'The request headers must be an associative array.', 'wp-oop-plugin-lib' ),
''
);
unset( $args['headers'] );
}
return $args;
}
/**
* Checks whether the given request method is valid.
*
* @since 0.1.0
*
* @param string $method The request method to check.
* @return bool True if the request method is valid, false otherwise.
*/
private function is_valid_method( string $method ): bool {
return in_array(
$method,
array(
Request::DELETE,
Request::GET,
Request::PATCH,
Request::POST,
Request::PUT,
),
true
);
}
}

View File

@@ -0,0 +1,126 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Generic_Response
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Response;
use WpOrg\Requests\Utility\CaseInsensitiveDictionary;
/**
* Class for a generic HTTP response from another URL.
*
* @since 0.1.0
*/
class Generic_Response implements Response {
/**
* The HTTP status code received with the response.
*
* @since 0.1.0
* @var int
*/
private $status;
/**
* The body received with the response.
*
* @since 0.1.0
* @var string
*/
private $body;
/**
* The headers received with the response.
*
* @since 0.1.0
* @var CaseInsensitiveDictionary
*/
private $headers;
/**
* Constructor.
*
* @since 0.1.0
*
* @param int $status The HTTP status code received with the response.
* @param string $body The body received with the response.
* @param array<string, string> $headers The headers received with the response.
*/
public function __construct( int $status, string $body, array $headers ) {
// Prior to WordPress 6.2, this class had a different name.
if ( ! class_exists( CaseInsensitiveDictionary::class ) ) {
class_alias( 'Requests_Utility_CaseInsensitiveDictionary', CaseInsensitiveDictionary::class );
}
$this->status = $status;
$this->body = $body;
$this->headers = new CaseInsensitiveDictionary( $headers );
}
/**
* Retrieves the HTTP status code received with the response.
*
* @since 0.1.0
*
* @return int The 3-digit HTTP status code.
*/
public function get_status(): int {
return $this->status;
}
/**
* Retrieves the data received with the response.
*
* @since 0.1.0
*
* @return array<string, mixed> The response data, or an empty array if it could not automatically be decoded. In
* this case, the raw response body should be used.
*/
public function get_data(): array {
// This is a generic response, so we don't know how to decode the body.
return array();
}
/**
* Retrieves the body received with the response.
*
* @since 0.1.0
*
* @return string The raw response body. If response data could be automatically decoded, this should be empty, and
* the response data should be used instead.
*/
public function get_body(): string {
return $this->body;
}
/**
* Retrieves the headers received with the response.
*
* @since 0.1.0
*
* @return array<string, string> The response headers.
*/
public function get_headers(): array {
return $this->headers->getAll();
}
/**
* Retrieves a specific header received with the response.
*
* @since 0.1.0
*
* @param string $name The name of the header to retrieve.
* @return string The value of the header, or an empty string if the header was not found.
*/
public function get_header( string $name ): string {
if ( ! isset( $this->headers[ $name ] ) ) {
return '';
}
return $this->headers[ $name ];
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Get_Request
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request;
/**
* Class for a HTTP GET request to another URL.
*
* @since 0.1.0
*/
class Get_Request extends Generic_Request {
/**
* Constructor.
*
* @since 0.1.0
*
* @param string $url The URL to which the request should be sent.
* @param array<string, mixed> $data Optional. The data to be sent with the request. Default empty array.
* @param array<string, mixed> $args Optional. Additional options for the request. See {@see WP_Http::request()}
* for possible options. Providing the 'body' key is only allowed if the data
* parameter is empty, and only as a string. Default empty array.
*/
public function __construct( string $url, array $data = array(), array $args = array() ) {
$args['method'] = Request::GET;
parent::__construct( $url, $data, $args );
}
}

View File

@@ -0,0 +1,432 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\HTTP
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request_Handler;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Response;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Exception\Multiple_Requests_Exception;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Exception\Request_Exception;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Traits\Sanitize_Headers;
use InvalidArgumentException;
use WP_HTTP_Proxy;
use WpOrg\Requests\Exception as Requests_Exception;
use WpOrg\Requests\Proxy\Http as Requests_Proxy_HTTP;
use WpOrg\Requests\Requests;
use WpOrg\Requests\Response as Requests_Response;
use WpOrg\Requests\Utility\CaseInsensitiveDictionary;
/**
* Class for sending HTTP requests and processing responses.
*
* @since 0.1.0
*/
class HTTP implements Request_Handler {
use Sanitize_Headers;
/**
* Default options to use for all requests.
*
* @since 0.1.0
* @var array<string, mixed>
*/
private $default_options;
/**
* Constructor.
*
* @since 0.1.0
*
* @param array<string, mixed> $default_options Optional. Default options to use for all requests. Default empty
* array.
*/
public function __construct( array $default_options = array() ) {
// Prior to WordPress 6.2, the Requests library was not using namespaces.
if ( ! class_exists( Requests_Exception::class ) ) {
class_alias( 'Requests_Exception', Requests_Exception::class );
}
if ( ! class_exists( Requests_Proxy_HTTP::class ) ) {
class_alias( 'Requests_Proxy_HTTP', Requests_Proxy_HTTP::class );
}
if ( ! class_exists( Requests_Response::class ) ) {
class_alias( 'Requests_Response', Requests_Response::class );
}
if ( ! class_exists( CaseInsensitiveDictionary::class ) ) {
class_alias( 'Requests_Utility_CaseInsensitiveDictionary', CaseInsensitiveDictionary::class );
}
// Remove potentially conflicting entries that are not actually options.
unset( $default_options['method'], $default_options['headers'], $default_options['body'] );
$this->default_options = $default_options;
}
/**
* Sends an HTTP request and returns the response.
*
* @since 0.1.0
*
* @param Request $request The request to send.
* @return Response The response received.
*
* @throws Request_Exception Thrown if the request fails.
*/
public function request( Request $request ): Response {
$headers = $request->get_headers();
$data = $request->get_data();
if ( ! $data ) {
$data = $request->get_body();
}
$args = wp_parse_args( $request->get_options(), $this->default_options );
$args['method'] = $request->get_method();
if ( $headers ) {
$args['headers'] = $headers;
}
if ( $data ) {
$args['body'] = $data;
}
$response = wp_remote_request( $request->get_url(), $args );
if ( is_wp_error( $response ) ) {
throw new Request_Exception( esc_html( $response->get_error_message() ) );
}
$status = (int) wp_remote_retrieve_response_code( $response );
$body = wp_remote_retrieve_body( $response );
$headers = wp_remote_retrieve_headers( $response );
if ( $headers instanceof CaseInsensitiveDictionary ) {
$headers = $headers->getAll();
}
$headers = $this->sanitize_headers( $headers );
return $this->create_response( $status, $body, $headers );
}
/**
* Sends multiple HTTP requests and returns the responses.
*
* The returned responses are in the same order / use the same keys as the requests.
*
* If any of the requests fail, a Multiple_Requests_Exception will be thrown. The exception will contain the
* responses of the requests that succeeded, and the exceptions of the requests that failed.
*
* @since 0.1.0
*
* @param array<string|int, Request> $requests The requests to send.
* @return array<string|int, Response> The responses received.
*
* @throws Multiple_Requests_Exception Thrown if one or more requests fail. If any requests succeeded, their
* responses will be included in the exception.
* @throws InvalidArgumentException Thrown if an invalid request is provided.
*/
public function request_multiple( array $requests ): array {
// Ensure all values are Request objects.
foreach ( $requests as $request ) {
if ( ! $request instanceof Request ) {
throw new InvalidArgumentException(
esc_html__( 'Invalid request provided.', 'wp-oop-plugin-lib' )
);
}
}
$requests_args = array();
$responses = array();
foreach ( $requests as $key => $request ) {
// Assemble the options with WordPress defaults included.
$request_args = $this->build_request_args( $request );
// Allow short-circuiting requests, just like in WP_Http::request().
$pre_response = $this->run_wp_pre_http_request_filter( $request_args );
if ( null !== $pre_response ) {
$responses[ $key ] = $pre_response;
continue;
}
// Prepare the options for usage with the Requests library.
$request_args['options'] = $this->prepare_options_for_requests(
$request_args['options'],
$request_args['url'],
$request_args['type']
);
$requests_args[ $key ] = $request_args;
}
// If all requests were handled by the response pre filter, we don't actually need to send any requests.
if ( count( $requests_args ) > 0 ) {
// Similar to WP_Http::request(), avoid issues where mbstring.func_overload is enabled.
mbstring_binary_safe_encoding();
$responses = array_merge(
$responses,
Requests::request_multiple( $requests_args )
);
// See above.
reset_mbstring_encoding();
}
$successful = array();
$failed = array();
foreach ( $responses as $key => $response ) {
if ( $response instanceof Requests_Exception ) {
$failed[ $key ] = new Request_Exception( $response->getMessage() );
continue;
}
$status = (int) $response->status_code;
$body = $response->body;
$headers = $this->sanitize_headers( $response->headers->getAll() );
$successful[ $key ] = $this->create_response( $status, $body, $headers );
}
/*
* If any requests failed, throw a bulk exception.
* The successful responses will be included in the exception, so they can still be used if needed.
*/
if ( $failed ) {
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
throw new Multiple_Requests_Exception( $failed, $successful );
}
// If this is reached, all requests succeeded.
return $successful;
}
/**
* Assembles the request arguments for the given request, to pass to the Requests library.
*
* @since 0.1.0
*
* @param Request $request The request to send.
* @return array<string, mixed> Request arguments.
*/
protected function build_request_args( Request $request ): array {
$headers = $request->get_headers();
$data = $request->get_data();
if ( ! $data ) {
$data = $request->get_body();
}
$request_args = array(
'url' => $request->get_url(),
'type' => $request->get_method(),
'options' => wp_parse_args( $request->get_options(), $this->default_options ),
);
if ( $headers ) {
$request_args['headers'] = $headers;
}
if ( $data ) {
$request_args['data'] = $data;
}
// Include the defaults from WP_Http::request(), since the Requests library does not include them.
$request_args['options'] = $this->merge_wp_default_options(
$request_args['options'],
$request_args['url'],
$request_args['type']
);
return $request_args;
}
/**
* Runs the WordPress 'pre_http_request' filter to allow short-circuiting requests.
*
* This is only used for multi requests, as single requests are handled by WP_Http::request() directly.
*
* When used in a multi request, the filter will be run for every request. For any request where it returns a value
* other than `false`, the request will not be actually sent and instead the data from the filter is used to create
* the response. If all requests within a multi request receive their response data in that way, no request is sent
* at all.
*
* @since 0.1.0
*
* @param array<string, mixed> $request_args Request arguments.
* @return Requests_Response|Requests_Exception|null Response object or exception based on the 'pre_http_request'
* filter data, or null if not filtered.
*/
private function run_wp_pre_http_request_filter( array $request_args ) {
$parsed_args = $request_args['options'];
$parsed_args['method'] = $request_args['type'];
$parsed_args['headers'] = $request_args['headers'] ?? array();
$parsed_args['cookies'] = $request_args['options']['cookies'] ?? array();
$parsed_args['body'] = $request_args['data'] ?? null;
// Allow short-circuiting requests, just like in WP_Http::request().
$pre = apply_filters( 'pre_http_request', false, $parsed_args, $request_args['url'] );
if ( false !== $pre ) {
if ( is_wp_error( $pre ) ) {
return new Requests_Exception( $pre->get_error_message(), 'pre_http_request' );
}
$response = new Requests_Response();
if ( $pre['response']['code'] ) {
$response->status_code = $pre['response']['code'];
}
if ( $pre['body'] ) {
$response->body = $pre['body'];
}
if ( $pre['headers'] ) {
foreach ( $pre['headers'] as $header_name => $header_value ) {
$response->headers[ $header_name ] = $header_value;
}
}
return $response;
}
return null;
}
/**
* Creates a response object based on the response data.
*
* @see https://www.rfc-editor.org/rfc/rfc1341.html#page-7
*
* @since 0.1.0
*
* @param int $status The HTTP status code received with the response.
* @param string $body The body received with the response.
* @param array<string, string> $headers The headers received with the response.
* @return Response The response object.
*/
private function create_response( int $status, string $body, array $headers ): Response {
if (
isset( $headers['content-type'] )
&& in_array( 'application/json', array_map( 'trim', explode( ';', $headers['content-type'] ) ), true )
) {
return new JSON_Response( $status, $body, $headers );
}
return new Generic_Response( $status, $body, $headers );
}
/**
* Populates the given options array with defaults.
*
* WordPress's API only allows making a single request at a time, while the Requests library allows making multiple
* requests. However, the Requests library does not include the WordPress defaults for requests, such as the default
* timeout. This method ensures that they include these defaults.
*
* Most of the code in this method is similar to code in WP_Http::request() in WordPress core.
*
* @since 0.1.0
*
* @param array<string, mixed> $options The options to prepare.
* @param string $url The request URL, only relevant as context for various filters.
* @param string $method The request method, relevant to determine some defaults.
* @return array<string, mixed> The prepared options, including WordPress defaults.
*/
private function merge_wp_default_options( array $options, string $url, string $method ): array {
$wp_user_agent = 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' );
$defaults = array(
'timeout' => apply_filters( 'http_request_timeout', 5, $url ),
'redirection' => apply_filters( 'http_request_redirection_count', 5, $url ),
'user-agent' => apply_filters( 'http_headers_useragent', $wp_user_agent, $url ),
'reject_unsafe_urls' => apply_filters( 'http_request_reject_unsafe_urls', false, $url ),
'blocking' => true,
'sslverify' => true,
'sslcertificates' => ABSPATH . WPINC . '/certificates/ca-bundle.crt',
'stream' => false,
'filename' => null,
'limit_response_size' => null,
);
if ( 'HEAD' === $method ) {
$defaults['redirection'] = 0;
}
if ( isset( $options['stream'] ) && $options['stream'] ) {
$defaults['filename'] = get_temp_dir() . basename( $url );
}
return wp_parse_args( $options, $defaults );
}
/**
* Prepares the options for a request directly via the Requests library.
*
* WordPress's API only allows making a single request at a time, while the Requests library allows making multiple
* requests. However, the Requests library uses different argument names, so this method prepares the WordPress
* options for usage with the Requests library.
*
* Most of the code in this method is similar to code in WP_Http::request() in WordPress core.
*
* @since 0.1.0
*
* @param array<string, mixed> $options The options to prepare.
* @param string $url The request URL, only relevant as context for various filters.
* @param string $method The request method, relevant to determine some defaults.
* @return array<string, mixed> The prepared options.
*/
private function prepare_options_for_requests( array $options, string $url, string $method ): array {
// Migrate WordPress options to Requests options.
$options = $this->migrate_wp_options_to_requests_options( $options );
// Enforce additional behavior similar to WordPress core.
if ( $options['filename'] ) {
$options['blocking'] = true;
}
if ( 'HEAD' !== $method && 'GET' !== $method ) {
$options['data_format'] = 'body';
}
$options['verify'] = apply_filters( 'https_ssl_verify', $options['verify'], $url );
// Add proxy settings if necessary, similar to WordPress core.
$proxy = new WP_HTTP_Proxy();
if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
$options['proxy'] = new Requests_Proxy_HTTP( $proxy->host() . ':' . $proxy->port() );
if ( $proxy->use_authentication() ) {
$options['proxy']->use_authentication = true;
$options['proxy']->user = $proxy->username();
$options['proxy']->pass = $proxy->password();
}
}
return $options;
}
/**
* Migrates WordPress options to Requests options.
*
* @since 0.1.0
*
* @param array<string, mixed> $options The options to migrate.
* @return array<string, mixed> The migrated options.
*/
private function migrate_wp_options_to_requests_options( array $options ): array {
if ( isset( $options['limit_response_size'] ) ) {
$options['max_bytes'] = $options['limit_response_size'];
}
if ( ! $options['redirection'] ) {
$options['follow_redirects'] = false;
} else {
$options['redirects'] = $options['redirection'];
}
if ( ! $options['sslverify'] ) {
$options['verify'] = false;
$options['verifyname'] = false;
} else {
$options['verify'] = $options['sslcertificates'];
}
$options['useragent'] = $options['user-agent'];
unset(
$options['limit_response_size'],
$options['redirection'],
$options['sslverify'],
$options['sslcertificates'],
$options['stream'], // This is irrelevant as the 'filename' presence alone handles it.
$options['user-agent']
);
return $options;
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\JSON_Patch_Request
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request;
/**
* Class for a HTTP PATCH request that sends JSON to another URL.
*
* @since 0.1.0
*/
class JSON_Patch_Request extends JSON_Request {
/**
* Constructor.
*
* @since 0.1.0
*
* @param string $url The URL to which the request should be sent.
* @param array<string, mixed> $data Optional. The data to be sent with the request. Default empty array.
* @param array<string, mixed> $args Optional. Additional options for the request. See {@see WP_Http::request()}
* for possible options. Providing the 'body' key is only allowed if the data
* parameter is empty, and only as a string. Default empty array.
*/
public function __construct( string $url, array $data = array(), array $args = array() ) {
$args['method'] = Request::PATCH;
parent::__construct( $url, $data, $args );
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\JSON_Post_Request
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request;
/**
* Class for a HTTP POST request that sends JSON to another URL.
*
* @since 0.1.0
*/
class JSON_Post_Request extends JSON_Request {
/**
* Constructor.
*
* @since 0.1.0
*
* @param string $url The URL to which the request should be sent.
* @param array<string, mixed> $data Optional. The data to be sent with the request. Default empty array.
* @param array<string, mixed> $args Optional. Additional options for the request. See {@see WP_Http::request()}
* for possible options. Providing the 'body' key is only allowed if the data
* parameter is empty, and only as a string. Default empty array.
*/
public function __construct( string $url, array $data = array(), array $args = array() ) {
$args['method'] = Request::POST;
parent::__construct( $url, $data, $args );
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\JSON_Put_Request
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request;
/**
* Class for a HTTP PUT request that sends JSON to another URL.
*
* @since 0.1.0
*/
class JSON_Put_Request extends JSON_Request {
/**
* Constructor.
*
* @since 0.1.0
*
* @param string $url The URL to which the request should be sent.
* @param array<string, mixed> $data Optional. The data to be sent with the request. Default empty array.
* @param array<string, mixed> $args Optional. Additional options for the request. See {@see WP_Http::request()}
* for possible options. Providing the 'body' key is only allowed if the data
* parameter is empty, and only as a string. Default empty array.
*/
public function __construct( string $url, array $data = array(), array $args = array() ) {
$args['method'] = Request::PUT;
parent::__construct( $url, $data, $args );
}
}

View File

@@ -0,0 +1,88 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\JSON_Request
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP;
/**
* Class for an HTTP request that sends JSON to another URL.
*
* @since 0.1.0
*/
class JSON_Request extends Generic_Request {
/**
* Constructor.
*
* @since 0.1.0
*
* @param string $url The URL to which the request should be sent.
* @param array<string, mixed> $data Optional. The data to be sent with the request. Default empty array.
* @param array<string, mixed> $args Optional. Additional options for the request. See {@see WP_Http::request()}
* for possible options. Providing the 'body' key is only allowed if the data
* parameter is empty, and only as a string. Default empty array.
*/
public function __construct( string $url, array $data = array(), array $args = array() ) {
// If the body is provided directly, it must be JSON-encoded data.
if ( isset( $args['body'] ) && is_string( $args['body'] ) && $args['body'] ) {
json_decode( $args['body'], true );
if ( json_last_error() !== JSON_ERROR_NONE ) {
_doing_it_wrong(
__METHOD__,
// phpcs:ignore Generic.Files.LineLength.TooLong
esc_html__( 'When providing the JSON request body directly, it must be a valid JSON string.', 'wp-oop-plugin-lib' ),
''
);
unset( $args['body'] );
}
}
// Ensure the Content-Type header is set to application/json, unless otherwise specified.
if ( ! isset( $args['headers'] ) ) {
$args['headers'] = array( 'Content-Type' => 'application/json' );
} elseif ( ! isset( $args['headers']['Content-Type'] ) && ! isset( $args['headers']['content-type'] ) ) {
$args['headers']['Content-Type'] = 'application/json';
}
parent::__construct( $url, $data, $args );
}
/**
* Retrieves the data to be sent with the request.
*
* @since 0.1.0
*
* @return array<string, mixed> The request data, or an empty array. If the request method is not GET or HEAD, in
* case of an empty array the request body should be used instead.
*/
public function get_data(): array {
// The data should be sent as JSON, so the body should be used instead.
return array();
}
/**
* Retrieves the body to be sent with the request.
*
* A request may have either data or a body, but not both.
*
* @since 0.1.0
*
* @return string The request body, or an empty string. Only relevant if the request method is not GET or HEAD. In
* case of an empty string, the request data should be used instead.
*/
public function get_body(): string {
$body = parent::get_body();
if ( $body ) {
return $body;
}
$data = parent::get_data();
if ( ! $data ) {
return '';
}
return wp_json_encode( $data );
}
}

View File

@@ -0,0 +1,43 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\JSON_Response
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP;
/**
* Class for a JSON HTTP response from another URL.
*
* @since 0.1.0
*/
class JSON_Response extends Generic_Response {
/**
* Data decoded from the JSON response body.
*
* @since 0.1.0
* @var array<string, mixed>|null
*/
private $data;
/**
* Retrieves the data received with the response.
*
* @since 0.1.0
*
* @return array<string, mixed> The response data, or an empty array if it could not automatically be decoded. In
* this case, the raw response body should be used.
*/
public function get_data(): array {
if ( ! isset( $this->data ) ) {
$this->data = json_decode( $this->get_body(), true );
if ( null === $this->data ) {
$this->data = array();
}
}
return $this->data;
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Patch_Request
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request;
/**
* Class for a HTTP PATCH request to another URL.
*
* @since 0.1.0
*/
class Patch_Request extends Generic_Request {
/**
* Constructor.
*
* @since 0.1.0
*
* @param string $url The URL to which the request should be sent.
* @param array<string, mixed> $data Optional. The data to be sent with the request. Default empty array.
* @param array<string, mixed> $args Optional. Additional options for the request. See {@see WP_Http::request()}
* for possible options. Providing the 'body' key is only allowed if the data
* parameter is empty, and only as a string. Default empty array.
*/
public function __construct( string $url, array $data = array(), array $args = array() ) {
$args['method'] = Request::PATCH;
parent::__construct( $url, $data, $args );
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Post_Request
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request;
/**
* Class for a HTTP POST request to another URL.
*
* @since 0.1.0
*/
class Post_Request extends Generic_Request {
/**
* Constructor.
*
* @since 0.1.0
*
* @param string $url The URL to which the request should be sent.
* @param array<string, mixed> $data Optional. The data to be sent with the request. Default empty array.
* @param array<string, mixed> $args Optional. Additional options for the request. See {@see WP_Http::request()}
* for possible options. Providing the 'body' key is only allowed if the data
* parameter is empty, and only as a string. Default empty array.
*/
public function __construct( string $url, array $data = array(), array $args = array() ) {
$args['method'] = Request::POST;
parent::__construct( $url, $data, $args );
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Put_Request
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Contracts\Request;
/**
* Class for a HTTP PUT request to another URL.
*
* @since 0.1.0
*/
class Put_Request extends Generic_Request {
/**
* Constructor.
*
* @since 0.1.0
*
* @param string $url The URL to which the request should be sent.
* @param array<string, mixed> $data Optional. The data to be sent with the request. Default empty array.
* @param array<string, mixed> $args Optional. Additional options for the request. See {@see WP_Http::request()}
* for possible options. Providing the 'body' key is only allowed if the data
* parameter is empty, and only as a string. Default empty array.
*/
public function __construct( string $url, array $data = array(), array $args = array() ) {
$args['method'] = Request::PUT;
parent::__construct( $url, $data, $args );
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* Trait Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Traits\Sanitize_Headers
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\HTTP\Traits;
/**
* Trait with a function to sanitize headers into an associative array of strings.
*
* @since 0.1.0
*/
trait Sanitize_Headers {
/**
* Sanitizes the given headers associative array to make sure all values are strings.
*
* Multiple values for the same header will be concatenated with a comma.
*
* @since 0.1.0
*
* @param array<string, string|array<string>> $headers The headers to sanitize.
* @return array<string, string> The sanitized headers.
*/
protected function sanitize_headers( array $headers ): array {
$sanitized_headers = array();
foreach ( $headers as $name => $value ) {
if ( is_array( $value ) ) {
$sanitized_headers[ $name ] = implode( ', ', $value );
} else {
$sanitized_headers[ $name ] = $value;
}
}
return $sanitized_headers;
}
}

View File

@@ -0,0 +1,127 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Abstract_Entity_Key_Value
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Contracts\Entity_Key_Value;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Contracts\Entity_Key_Value_Repository;
/**
* Base class representing an key-value pair that is connected to an entity.
*
* Should typically not be used directly, but rather through a more specific class extending it.
*
* @since 0.1.0
*/
class Abstract_Entity_Key_Value implements Entity_Key_Value {
/**
* Repository used for the item.
*
* @since 0.1.0
* @var Entity_Key_Value_Repository
*/
protected $repository;
/**
* Item key.
*
* @since 0.1.0
* @var string
*/
protected $key;
/**
* Item default value.
*
* @since 0.1.0
* @var mixed
*/
protected $default_value;
/**
* Constructor.
*
* @since 0.1.0
*
* @param Entity_Key_Value_Repository $repository Repository used for the item.
* @param string $key Item key.
* @param mixed $default_value Optional. Default value for the item if not set in the
* repository. If null, it will be ignored. Default null.
*/
public function __construct( Entity_Key_Value_Repository $repository, string $key, $default_value = null ) {
$this->repository = $repository;
$this->key = $key;
$this->default_value = $default_value;
}
/**
* Checks whether the item has a value set in the given entity.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @return bool True if a value is set, false otherwise.
*/
public function has_value( int $entity_id ): bool {
return $this->repository->exists( $entity_id, $this->key );
}
/**
* Gets the value for the item in the given entity.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @return mixed Value for the item.
*/
public function get_value( int $entity_id ) {
// Pass default value if set.
if ( isset( $this->default_value ) ) {
return $this->repository->get( $entity_id, $this->key, $this->default_value );
}
return $this->repository->get( $entity_id, $this->key );
}
/**
* Updates the value for the item in the given entity.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @param mixed $value New value to set for the item.
* @return bool True on success, false on failure.
*/
public function update_value( int $entity_id, $value ): bool {
return $this->repository->update( $entity_id, $this->key, $value );
}
/**
* Deletes the data for the item in the given entity.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @return bool True on success, false on failure.
*/
public function delete_value( int $entity_id ): bool {
return $this->repository->delete( $entity_id, $this->key );
}
/**
* Gets the key of the item.
*
* @since 0.1.0
*
* @return string Item key.
*/
public function get_key(): string {
return $this->key;
}
}

View File

@@ -0,0 +1,67 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Contracts\Entity_Key_Value
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Contracts;
/**
* Interface for a key-value pair that is connected to an entity.
*
* @since 0.1.0
*/
interface Entity_Key_Value {
/**
* Checks whether the item has a value set in the given entity.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @return bool True if a value is set, false otherwise.
*/
public function has_value( int $entity_id ): bool;
/**
* Gets the value for the item in the given entity.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @return mixed Value for the item.
*/
public function get_value( int $entity_id );
/**
* Updates the value for the item in the given entity.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @param mixed $value New value to set for the item.
* @return bool True on success, false on failure.
*/
public function update_value( int $entity_id, $value ): bool;
/**
* Deletes the data for the item in the given entity.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @return bool True on success, false on failure.
*/
public function delete_value( int $entity_id ): bool;
/**
* Gets the key of the item.
*
* @since 0.1.0
*
* @return string Item key.
*/
public function get_key(): string;
}

View File

@@ -0,0 +1,65 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Contracts\Entity_Key_Value_Repository
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Contracts;
/**
* Interface for a repository for key-value pairs that are connected to an entity.
*
* @since 0.1.0
*/
interface Entity_Key_Value_Repository {
/**
* Checks whether a value for the given entity and key exists in the repository.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @param string $key Item key.
* @return bool True if a value for the key exists, false otherwise.
*/
public function exists( int $entity_id, string $key ): bool;
/**
* Gets the value for a given entity and key from the repository.
*
* Always returns a single value.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @param string $key Item key.
* @param mixed $default Optional. Value to return if no value exists for the key. Default null.
* @return mixed Value for the key, or the default if no value exists.
*/
public function get( int $entity_id, string $key, $default = null );
/**
* Updates the value for a given entity and key in the repository.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @param string $key Item key.
* @param mixed $value New value to set for the key.
* @return bool True on success, false on failure.
*/
public function update( int $entity_id, string $key, $value ): bool;
/**
* Deletes the data for a given entity and key from the repository.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @param string $key Item key.
* @return bool True on success, false on failure.
*/
public function delete( int $entity_id, string $key ): bool;
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Contracts\With_Entity_ID
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Contracts;
/**
* Interface for a class that is aware of a specific entity ID.
*
* @since 0.1.0
*/
interface With_Entity_ID {
/**
* Gets the entity ID.
*
* @since 0.1.0
*
* @return int The entity ID.
*/
public function get_entity_id(): int;
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Contracts\With_Single
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Contracts;
/**
* Interface for a key-value pair repository with support for differentiating between entries with a single value vs
* with multiple values.
*
* @since 0.1.0
*/
interface With_Single {
/**
* Gets the 'single' config for a given key in the repository.
*
* @since 0.1.0
*
* @param string $key Item key.
* @return bool Whether or not the item should be singleed.
*/
public function get_single( string $key ): bool;
/**
* Sets the 'single' config for a given key in the repository.
*
* @since 0.1.0
*
* @param string $key Item key.
* @param bool $single Item single config.
*/
public function set_single( string $key, bool $single ): void;
}

View File

@@ -0,0 +1,178 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Entity_Aware_Meta_Container
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta;
use ArrayAccess;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Container_Readonly;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Exception\Not_Found_Exception;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Contracts\With_Entity_ID;
/**
* Class for a meta container scoped to a specific entity.
*
* @since 0.1.0
*/
class Entity_Aware_Meta_Container implements Container_Readonly, ArrayAccess, With_Entity_ID {
/**
* The original meta container.
*
* @since 0.1.0
* @var Meta_Container
*/
private $wrapped_container;
/**
* ID of the entity to scope this instance to.
*
* @since 0.1.0
* @var int
*/
private $entity_id;
/**
* Item instances already created.
*
* @since 0.1.0
* @var array<string, Entity_Aware_Meta_Key>
*/
private $instances = array();
/**
* Constructor.
*
* @since 0.1.0
*
* @param Meta_Container $wrapped_container Underlying entity aware instance that this scoped instance
* should inherit from.
* @param int $entity_id ID of the entity to scope this instance to.
*/
public function __construct( Meta_Container $wrapped_container, int $entity_id ) {
$this->wrapped_container = $wrapped_container;
$this->entity_id = $entity_id;
}
/**
* Checks if a meta key for the given key exists in the container.
*
* @since 0.1.0
*
* @param string $key Meta key.
* @return bool True if the meta key exists in the container, false otherwise.
*/
public function has( string $key ): bool {
return $this->wrapped_container->has( $key );
}
/**
* Gets the meta key for the given key from the container.
*
* @since 0.1.0
*
* @param string $key Meta key.
* @return Entity_Aware_Meta_Key Meta key for the given key.
*
* @throws Not_Found_Exception Thrown when meta key with given key is not found.
*/
public function get( string $key ) {
if ( ! $this->wrapped_container->has( $key ) ) {
throw new Not_Found_Exception(
esc_html(
sprintf(
/* translators: %s: meta key */
__( 'Meta key with key %s was not found in container', 'wp-oop-plugin-lib' ),
$key
)
)
);
}
if ( ! isset( $this->instances[ $key ] ) ) {
$this->instances[ $key ] = new Entity_Aware_Meta_Key(
$this->wrapped_container->get( $key ),
$this->entity_id
);
}
return $this->instances[ $key ];
}
/**
* Gets all keys in the container.
*
* @since 0.1.0
*
* @return string[] List of keys.
*/
public function get_keys(): array {
return $this->wrapped_container->get_keys();
}
/**
* Checks if a meta key for the given key exists in the container.
*
* @since 0.1.0
*
* @param mixed $key Meta key.
* @return bool True if the meta key exists in the container, false otherwise.
*/
#[\ReturnTypeWillChange]
public function offsetExists( $key ) {
return $this->has( $key );
}
/**
* Gets the meta key for the given key from the container.
*
* @since 0.1.0
*
* @param mixed $key Meta key.
* @return mixed Meta key for the given key.
*/
#[\ReturnTypeWillChange]
public function offsetGet( $key ) {
return $this->get( $key );
}
/**
* Sets the given meta key under the given key in the container.
*
* @since 0.1.0
*
* @param mixed $key Meta key.
* @param mixed $value Item creator closure.
*/
#[\ReturnTypeWillChange]
public function offsetSet( $key, $value ) {
// Does nothing as this class is read-only.
}
/**
* Unsets the meta key under the given key in the container.
*
* @since 0.1.0
*
* @param mixed $key Meta key.
*/
#[\ReturnTypeWillChange]
public function offsetUnset( $key ) {
// Does nothing as this class is read-only.
}
/**
* Gets the entity ID.
*
* @since 0.1.0
*
* @return int The entity ID.
*/
public function get_entity_id(): int {
return $this->entity_id;
}
}

View File

@@ -0,0 +1,117 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Entity_Aware_Meta_Key
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Key_Value;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Contracts\With_Entity_ID;
/**
* Wrapper representing a WordPress meta key scoped to a specific entity.
*
* @since 0.1.0
*/
class Entity_Aware_Meta_Key implements With_Entity_ID, Key_Value {
/**
* Underlying, general entity aware instance.
*
* @since 0.1.0
* @var Meta_Key
*/
private $wrapped_meta;
/**
* ID of the entity to scope this instance to.
*
* @since 0.1.0
* @var int
*/
private $entity_id;
/**
* Constructor.
*
* @since 0.1.0
*
* @param Meta_Key $wrapped_meta Underlying entity aware instance that this scoped instance
* should inherit from.
* @param int $entity_id ID of the entity to scope this instance to.
*/
public function __construct( Meta_Key $wrapped_meta, int $entity_id ) {
$this->wrapped_meta = $wrapped_meta;
$this->entity_id = $entity_id;
}
/**
* Checks whether the item has a value set.
*
* @since 0.1.0
*
* @return bool True if a value is set, false otherwise.
*/
public function has_value(): bool {
return $this->wrapped_meta->has_value( $this->entity_id );
}
/**
* Gets the value for the item.
*
* @since 0.1.0
*
* @return mixed Value for the item.
*/
public function get_value() {
return $this->wrapped_meta->get_value( $this->entity_id );
}
/**
* Updates the value for the item.
*
* @since 0.1.0
*
* @param mixed $value New value to set for the item.
* @return bool True on success, false on failure.
*/
public function update_value( $value ): bool {
return $this->wrapped_meta->update_value( $this->entity_id, $value );
}
/**
* Deletes the data for the item.
*
* @since 0.1.0
*
* @return bool True on success, false on failure.
*/
public function delete_value(): bool {
return $this->wrapped_meta->delete_value( $this->entity_id );
}
/**
* Gets the key of the item.
*
* @since 0.1.0
*
* @return string Option key.
*/
public function get_key(): string {
return $this->wrapped_meta->get_key();
}
/**
* Gets the entity ID.
*
* @since 0.1.0
*
* @return int The entity ID.
*/
public function get_entity_id(): int {
return $this->entity_id;
}
}

View File

@@ -0,0 +1,211 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Meta_Container
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta;
use ArrayAccess;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Container;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Exception\Invalid_Type_Exception;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Exception\Not_Found_Exception;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Contracts\Entity_Key_Value_Repository;
/**
* Class for a meta container.
*
* @since 0.1.0
*/
class Meta_Container implements Container, ArrayAccess {
/**
* Meta keys stored in the container.
*
* @since 0.1.0
* @var array<string, callable>
*/
private $meta_keys = array();
/**
* Meta key instances already created.
*
* @since 0.1.0
* @var array<string, Meta_Key>
*/
private $instances = array();
/**
* Checks if a meta key for the given key exists in the container.
*
* @since 0.1.0
*
* @param string $key Meta key.
* @return bool True if the meta key exists in the container, false otherwise.
*/
public function has( string $key ): bool {
return isset( $this->meta_keys[ $key ] );
}
/**
* Gets the meta key for the given key from the container.
*
* @since 0.1.0
*
* @param string $key Meta key.
* @return Meta_Key Meta key for the given key.
*
* @throws Not_Found_Exception Thrown when meta key with given key is not found.
* @throws Invalid_Type_Exception Thrown when meta key with given key has invalid type.
*/
public function get( string $key ) {
if ( ! isset( $this->meta_keys[ $key ] ) ) {
throw new Not_Found_Exception(
esc_html(
sprintf(
/* translators: %s: meta key */
__( 'Meta key with key %s was not found in container', 'wp-oop-plugin-lib' ),
$key
)
)
);
}
if ( ! isset( $this->instances[ $key ] ) ) {
$instance = $this->meta_keys[ $key ]( $this );
if ( ! $instance instanceof Meta_Key ) {
throw new Invalid_Type_Exception(
esc_html(
sprintf(
/* translators: %s: meta key */
__( 'Meta key with key %s is not of type Meta_Key', 'wp-oop-plugin-lib' ),
$key
)
)
);
}
$this->instances[ $key ] = $instance;
}
return $this->instances[ $key ];
}
/**
* Sets the given meta key under the given key in the container.
*
* @since 0.1.0
*
* @param string $key Meta key.
* @param callable $creator Meta key creator closure.
*/
public function set( string $key, callable $creator ): void {
$this->meta_keys[ $key ] = $creator;
unset( $this->instances[ $key ] );
}
/**
* Sets a meta key using the given repository and arguments under the given key in the container.
*
* @since 0.1.0
*
* @param string $key Meta key.
* @param Entity_Key_Value_Repository $repository Repository used for the meta key.
* @param array<string, mixed> $registration_args Optional. Meta key registration arguments. Default empty
* array.
*/
public function set_by_args( string $key, Entity_Key_Value_Repository $repository, array $registration_args = array() ): void { // phpcs:ignore Generic.Files.LineLength.TooLong
$this->set(
$key,
function () use ( $repository, $key, $registration_args ) {
return new Meta_Key( $repository, $key, $registration_args );
}
);
}
/**
* Unsets the meta key under the given key in the container.
*
* @since 0.1.0
*
* @param string $key Meta key.
*/
public function unset( string $key ): void {
unset( $this->meta_keys[ $key ], $this->instances[ $key ] );
}
/**
* Gets all keys in the container.
*
* @since 0.1.0
*
* @return string[] List of keys.
*/
public function get_keys(): array {
return array_keys( $this->meta_keys );
}
/**
* Checks if a meta key for the given key exists in the container.
*
* @since 0.1.0
*
* @param mixed $key Meta key.
* @return bool True if the meta key exists in the container, false otherwise.
*/
#[\ReturnTypeWillChange]
public function offsetExists( $key ) {
return $this->has( $key );
}
/**
* Gets the meta key for the given key from the container.
*
* @since 0.1.0
*
* @param mixed $key Meta key.
* @return Meta_Key Meta key for the given key.
*/
#[\ReturnTypeWillChange]
public function offsetGet( $key ) {
return $this->get( $key );
}
/**
* Sets the given meta key under the given key in the container.
*
* @since 0.1.0
*
* @param mixed $key Meta key.
* @param mixed $value Meta key creator closure.
*/
#[\ReturnTypeWillChange]
public function offsetSet( $key, $value ) {
$this->set( $key, $value );
}
/**
* Unsets the meta key under the given key in the container.
*
* @since 0.1.0
*
* @param mixed $key Meta key.
*/
#[\ReturnTypeWillChange]
public function offsetUnset( $key ) {
$this->unset( $key );
}
/**
* Creates an instance similar to this container, but scoped to the given entity.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @return Entity_Aware_Meta_Container New container scoped to the object.
*/
public function create_entity_aware( int $entity_id ): Entity_Aware_Meta_Container {
return new Entity_Aware_Meta_Container( $this, $entity_id );
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Meta_Hook_Registrar
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Hook_Registrar;
/**
* Class that adds the relevant hook to register WordPress metadata.
*
* @since 0.1.0
*/
class Meta_Hook_Registrar implements Hook_Registrar {
/**
* WordPress metadata registry instance.
*
* @since 0.1.0
* @var Meta_Registry
*/
private $registry;
/**
* Constructor.
*
* @param Meta_Registry $registry WordPress metadata registry instance.
*/
public function __construct( Meta_Registry $registry ) {
$this->registry = $registry;
}
/**
* Adds a callback that registers the metadata to the relevant hook.
*
* The callback receives a registry instance as the sole parameter, allowing to call the
* {@see Meta_Registry::register()} method.
*
* @since 0.1.0
*
* @param callable $register_callback Callback to register the metadata.
*/
public function add_register_callback( callable $register_callback ): void {
add_action(
'init',
function () use ( $register_callback ) {
$register_callback( $this->registry );
}
);
}
}

View File

@@ -0,0 +1,154 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Meta_Key
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\With_Registration_Args;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Traits\Cast_Value_By_Type;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Contracts\Entity_Key_Value_Repository;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Contracts\With_Single;
/**
* Class representing a WordPress meta key.
*
* @since 0.1.0
*/
class Meta_Key extends Abstract_Entity_Key_Value implements With_Registration_Args {
use Cast_Value_By_Type;
/**
* Meta key registration arguments.
*
* @since 0.1.0
* @var array<string, mixed>
*/
protected $registration_args;
/**
* Constructor.
*
* @since 0.1.0
*
* @param Entity_Key_Value_Repository $repository Repository used for the meta key.
* @param string $key Meta key.
* @param array<string, mixed> $registration_args Optional. Meta key registration arguments. Default empty
* array.
*/
public function __construct( Entity_Key_Value_Repository $repository, string $key, array $registration_args = array() ) { // phpcs:ignore Generic.Files.LineLength.TooLong
// Extract default value from registration arguments if passed.
$default = null;
if ( isset( $registration_args['default'] ) ) {
$default = $registration_args['default'];
}
// Set 'single' value from registration arguments if passed.
if ( $repository instanceof With_Single && isset( $registration_args['single'] ) ) {
$repository->set_single( $key, (bool) $registration_args['single'] );
}
parent::__construct( $repository, $key, $default );
$this->registration_args = $registration_args;
}
/**
* Checks whether the meta key has a value set for the given entity ID.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @return bool True if a value is set, false otherwise.
*/
public function has_value( int $entity_id ): bool {
return parent::has_value( $entity_id );
}
/**
* Gets the value for the meta key for the given entity ID.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @return mixed Value for the meta key.
*/
public function get_value( int $entity_id ) {
$value = parent::get_value( $entity_id );
/*
* By default, meta keys are assumed to have a single value. This may be overwritten via meta registration by
* setting the 'single' argument to `false`. In this case the value is always an array, and the individual
* items should be type-casted as needed.
*/
if ( isset( $this->registration_args['single'] ) && ! $this->registration_args['single'] ) {
$value = (array) $value;
if ( isset( $this->registration_args['type'] ) ) {
$value = array_map(
function ( $item ) {
return $this->cast_value_by_type( $item, $this->registration_args['type'] );
},
$value
);
}
return $value;
}
// Otherwise, this is a single value that should be type-casted as needed.
if ( isset( $this->registration_args['type'] ) ) {
return $this->cast_value_by_type( $value, $this->registration_args['type'] );
}
return $value;
}
/**
* Updates the value for the meta key for the given entity ID.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @param mixed $value New value to set for the meta key.
* @return bool True on success, false on failure.
*/
public function update_value( int $entity_id, $value ): bool {
return parent::update_value( $entity_id, $value );
}
/**
* Deletes the data for the meta key for the given entity ID.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @return bool True on success, false on failure.
*/
public function delete_value( int $entity_id ): bool {
return parent::delete_value( $entity_id );
}
/**
* Gets the meta key.
*
* @since 0.1.0
*
* @return string Meta key.
*/
public function get_key(): string {
return parent::get_key();
}
/**
* Gets the registration arguments for the meta key.
*
* @since 0.1.0
*
* @return array<string, mixed> Meta key registration arguments.
*/
public function get_registration_args(): array {
return $this->registration_args;
}
}

View File

@@ -0,0 +1,91 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Meta_Registry
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Registry;
/**
* Class for a registry of WordPress metadata.
*
* @since 0.1.0
*/
class Meta_Registry implements Registry {
/**
* Object type.
*
* @since 0.1.0
* @var string
*/
protected $object_type;
/**
* Constructor.
*
* @since 0.1.0
*
* @param string $object_type Object type.
*/
public function __construct( string $object_type ) {
$this->object_type = $object_type;
}
/**
* Registers a metadata item with the given key and arguments.
*
* @since 0.1.0
*
* @param string $key Meta key.
* @param array<string, mixed> $args Meta key registration arguments.
* @return bool True on success, false on failure.
*/
public function register( string $key, array $args ): bool {
return register_meta( $this->object_type, $key, $args );
}
/**
* Checks whether a metadata item with the given key is registered.
*
* @since 0.1.0
*
* @param string $key Meta key.
* @return bool True if the metadata item is registered, false otherwise.
*/
public function is_registered( string $key ): bool {
return registered_meta_key_exists( $this->object_type, $key );
}
/**
* Gets the registered metadata item for the given key from the registry.
*
* @since 0.1.0
*
* @param string $key Meta key.
* @return object|null The registered metadata definition, or `null` if not registered.
*/
public function get_registered( string $key ) {
$registered = get_registered_meta_keys( $this->object_type );
if ( ! isset( $registered[ $key ] ) ) {
return null;
}
return (object) $registered[ $key ];
}
/**
* Gets all metadata items from the registry.
*
* @since 0.1.0
*
* @return array<string, mixed> Associative array of keys and their metadata definitions, or empty array if nothing
* is registered.
*/
public function get_all_registered(): array {
return get_registered_meta_keys( $this->object_type );
}
}

View File

@@ -0,0 +1,194 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Meta_Repository
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Contracts\Entity_Key_Value_Repository;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Meta\Contracts\With_Single;
/**
* Class for a repository of WordPress metadata.
*
* @since 0.1.0
*/
class Meta_Repository implements Entity_Key_Value_Repository, With_Single {
/**
* Object type.
*
* @since 0.1.0
* @var string
*/
protected $object_type;
/**
* Single config as $key => $single pairs.
*
* @since 0.1.0
* @var array<string, bool>
*/
private $single_config = array();
/**
* Constructor.
*
* @since 0.1.0
*
* @param string $object_type Object type.
*/
public function __construct( string $object_type ) {
$this->object_type = $object_type;
}
/**
* Checks whether a value for the given entity and meta key exists in the database.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @param string $key Meta key.
* @return bool True if a value for the meta key exists, false otherwise.
*/
public function exists( int $entity_id, string $key ): bool {
return metadata_exists( $this->object_type, $entity_id, $key );
}
/**
* Gets the value for a given entity and meta key from the database.
*
* Always returns a single value.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @param string $key Meta key.
* @param mixed $default Optional. Value to return if no value exists for the meta key. Default null.
* @return mixed Value for the meta key, or the default if no value exists.
*/
public function get( int $entity_id, string $key, $default = null ) {
if ( ! metadata_exists( $this->object_type, $entity_id, $key ) ) {
// If not single, ensure the default is within an array.
if ( ! $this->get_single( $key ) ) {
if ( null !== $default ) {
return array( $default );
}
return array();
}
return $default;
}
return get_metadata( $this->object_type, $entity_id, $key, $this->get_single( $key ) );
}
/**
* Updates the value for a given entity and meta key in the database.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @param string $key Meta key.
* @param mixed $value New value to set for the meta key.
* @return bool True on success, false on failure.
*/
public function update( int $entity_id, string $key, $value ): bool {
/*
* If multiple values, delete the original ones first and then add the new ones individually, but only if the
* passed value is an indexed (not associative) array.
* There is only one caveat with this, but that is an edge-case: If the individual values of a multi-value meta
* key are themselves indexed arrays, this can lead to unexpected behavior with this implementation. A
* workaround would be to wrap them in another array before passing them to this method.
*/
if ( ! $this->get_single( $key ) && wp_is_numeric_array( $value ) ) {
delete_metadata( $this->object_type, $entity_id, $key );
foreach ( $value as $single_value ) {
add_metadata( $this->object_type, $entity_id, $key, $single_value );
}
return true;
}
return (bool) update_metadata( $this->object_type, $entity_id, $key, $value );
}
/**
* Deletes the data for a given entity and meta key from the database.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @param string $key Meta key.
* @return bool True on success, false on failure.
*/
public function delete( int $entity_id, string $key ): bool {
return (bool) delete_metadata( $this->object_type, $entity_id, $key );
}
/**
* Deletes all data for the given entity from the database.
*
* @since 0.1.0
*
* @param int $entity_id Entity ID.
* @return bool True on success, false on failure.
*/
public function delete_all( int $entity_id ): bool {
global $wpdb;
$table_name = $this->object_type . 'meta';
$table_col = $this->object_type . '_id';
$meta_ids = $wpdb->get_col(
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT meta_id FROM {$wpdb->$table_name} WHERE $table_col = %d ",
$entity_id
)
);
foreach ( $meta_ids as $mid ) {
delete_metadata_by_mid( $this->object_type, $mid );
}
return true;
}
/**
* Updates the metadata caches for the given entity IDs.
*
* @since 0.1.0
*
* @param int[] $entity_ids Entity IDs.
* @return bool True on success, or false on failure.
*/
public function prime_caches( array $entity_ids ): bool {
return (bool) update_meta_cache( $this->object_type, $entity_ids );
}
/**
* Gets the 'single' config for a given key in the repository.
*
* @since 0.1.0
*
* @param string $key Item key.
* @return bool Whether or not the item has a single value.
*/
public function get_single( string $key ): bool {
// The default value is true.
return $this->single_config[ $key ] ?? true;
}
/**
* Sets the 'single' config for a given key in the repository.
*
* @since 0.1.0
*
* @param string $key Item key.
* @param bool $single Item 'single' config.
*/
public function set_single( string $key, bool $single ): void {
$this->single_config[ $key ] = $single;
}
}

View File

@@ -0,0 +1,37 @@
<?php
/**
* Interface Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Options\Contracts\With_Autoload_Config
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Options\Contracts;
/**
* Interface for a key-value pair repository with autoload support.
*
* @since 0.1.0
*/
interface With_Autoload_Config {
/**
* Gets the autoload config for a given key in the repository.
*
* @since 0.1.0
*
* @param string $key Item key.
* @return bool|null Whether or not the item should be autoloaded, or null if not specified.
*/
public function get_autoload_config( string $key );
/**
* Sets the autoload config for a given key in the repository.
*
* @since 0.1.0
*
* @param string $key Item key.
* @param bool $autoload Item autoload config.
*/
public function set_autoload_config( string $key, bool $autoload ): void;
}

View File

@@ -0,0 +1,131 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Options\Option
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Options;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Key_Value_Repository;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\With_Registration_Args;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Generic_Key_Value;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Traits\Cast_Value_By_Type;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Options\Contracts\With_Autoload_Config;
/**
* Class representing a WordPress option.
*
* @since 0.1.0
*/
class Option extends Generic_Key_Value implements With_Registration_Args {
use Cast_Value_By_Type;
/**
* Option registration arguments.
*
* @since 0.1.0
* @var array<string, mixed>
*/
protected $registration_args;
/**
* Constructor.
*
* @since 0.1.0
*
* @param Key_Value_Repository $repository Repository used for the option.
* @param string $key Option key.
* @param array<string, mixed> $registration_args Optional. Option registration arguments. Default empty array.
*/
public function __construct( Key_Value_Repository $repository, string $key, array $registration_args = array() ) {
// Extract default value from registration arguments if passed.
$default = $registration_args['default'] ?? null;
// Set autoload value from registration arguments if passed.
if ( $repository instanceof With_Autoload_Config && isset( $registration_args['autoload'] ) ) {
$repository->set_autoload_config( $key, (bool) $registration_args['autoload'] );
}
// Unset autoload value in registration arguments, since it is not used by WordPress.
unset( $registration_args['autoload'] );
parent::__construct( $repository, $key, $default );
$this->registration_args = $registration_args;
}
/**
* Checks whether the option has a value set.
*
* @since 0.1.0
*
* @return bool True if a value is set, false otherwise.
*/
public function has_value(): bool {
return parent::has_value();
}
/**
* Gets the value for the option.
*
* @since 0.1.0
*
* @return mixed Value for the option.
*/
public function get_value() {
$value = parent::get_value();
if ( isset( $this->registration_args['type'] ) ) {
return $this->cast_value_by_type( $value, $this->registration_args['type'] );
}
return $value;
}
/**
* Updates the value for the option.
*
* @since 0.1.0
*
* @param mixed $value New value to set for the option.
* @return bool True on success, false on failure.
*/
public function update_value( $value ): bool {
return parent::update_value( $value );
}
/**
* Deletes the data for the option.
*
* @since 0.1.0
*
* @return bool True on success, false on failure.
*/
public function delete_value(): bool {
return parent::delete_value();
}
/**
* Gets the key of the option.
*
* @since 0.1.0
*
* @return string Option key.
*/
public function get_key(): string {
return parent::get_key();
}
/**
* Gets the registration arguments for the option.
*
* @since 0.1.0
*
* @return array<string, mixed> Option registration arguments.
*/
public function get_registration_args(): array {
return $this->registration_args;
}
}

View File

@@ -0,0 +1,198 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Options\Option_Container
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Options;
use ArrayAccess;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Container;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Key_Value_Repository;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Exception\Invalid_Type_Exception;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Exception\Not_Found_Exception;
/**
* Class for an option container.
*
* @since 0.1.0
*/
class Option_Container implements Container, ArrayAccess {
/**
* Options stored in the container.
*
* @since 0.1.0
* @var array<string, callable>
*/
private $options = array();
/**
* Option instances already created.
*
* @since 0.1.0
* @var array<string, Option>
*/
private $instances = array();
/**
* Checks if an option for the given key exists in the container.
*
* @since 0.1.0
*
* @param string $key Option key.
* @return bool True if the option exists in the container, false otherwise.
*/
public function has( string $key ): bool {
return isset( $this->options[ $key ] );
}
/**
* Gets the option for the given key from the container.
*
* @since 0.1.0
*
* @param string $key Option key.
* @return Option Option for the given key.
*
* @throws Not_Found_Exception Thrown when option with given key is not found.
* @throws Invalid_Type_Exception Thrown when option with given key has invalid type.
*/
public function get( string $key ) {
if ( ! isset( $this->options[ $key ] ) ) {
throw new Not_Found_Exception(
esc_html(
sprintf(
/* translators: %s: option key */
__( 'Option with key %s was not found in container', 'wp-oop-plugin-lib' ),
$key
)
)
);
}
if ( ! isset( $this->instances[ $key ] ) ) {
$instance = $this->options[ $key ]( $this );
if ( ! $instance instanceof Option ) {
throw new Invalid_Type_Exception(
esc_html(
sprintf(
/* translators: %s: option key */
__( 'Option with key %s is not of type Option', 'wp-oop-plugin-lib' ),
$key
)
)
);
}
$this->instances[ $key ] = $instance;
}
return $this->instances[ $key ];
}
/**
* Sets the given option under the given key in the container.
*
* @since 0.1.0
*
* @param string $key Option key.
* @param callable $creator Option creator closure.
*/
public function set( string $key, callable $creator ): void {
$this->options[ $key ] = $creator;
unset( $this->instances[ $key ] );
}
/**
* Sets an option using the given repository and arguments under the given key in the container.
*
* @since 0.1.0
*
* @param string $key Option key.
* @param Key_Value_Repository $repository Repository used for the option.
* @param array<string, mixed> $registration_args Optional. Option registration arguments. Default empty array.
*/
public function set_by_args( string $key, Key_Value_Repository $repository, array $registration_args = array() ): void { // phpcs:ignore Generic.Files.LineLength.TooLong
$this->set(
$key,
function () use ( $repository, $key, $registration_args ) {
return new Option( $repository, $key, $registration_args );
}
);
}
/**
* Unsets the option under the given key in the container.
*
* @since 0.1.0
*
* @param string $key Option key.
*/
public function unset( string $key ): void {
unset( $this->options[ $key ], $this->instances[ $key ] );
}
/**
* Gets all keys in the container.
*
* @since 0.1.0
*
* @return string[] List of keys.
*/
public function get_keys(): array {
return array_keys( $this->options );
}
/**
* Checks if an option for the given key exists in the container.
*
* @since 0.1.0
*
* @param mixed $key Option key.
* @return bool True if the option exists in the container, false otherwise.
*/
#[\ReturnTypeWillChange]
public function offsetExists( $key ) {
return $this->has( $key );
}
/**
* Gets the option for the given key from the container.
*
* @since 0.1.0
*
* @param mixed $key Option key.
* @return Option Option for the given key.
*/
#[\ReturnTypeWillChange]
public function offsetGet( $key ) {
return $this->get( $key );
}
/**
* Sets the given option under the given key in the container.
*
* @since 0.1.0
*
* @param mixed $key Option key.
* @param mixed $value Option creator closure.
*/
#[\ReturnTypeWillChange]
public function offsetSet( $key, $value ) {
$this->set( $key, $value );
}
/**
* Unsets the option under the given key in the container.
*
* @since 0.1.0
*
* @param mixed $key Option key.
*/
#[\ReturnTypeWillChange]
public function offsetUnset( $key ) {
$this->unset( $key );
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Options\Option_Hook_Registrar
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Options;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Hook_Registrar;
/**
* Class that adds the relevant hook to register WordPress options.
*
* @since 0.1.0
*/
class Option_Hook_Registrar implements Hook_Registrar {
/**
* WordPress option registry instance.
*
* @since 0.1.0
* @var Option_Registry
*/
private $registry;
/**
* Constructor.
*
* @param Option_Registry $registry WordPress option registry instance.
*/
public function __construct( Option_Registry $registry ) {
$this->registry = $registry;
}
/**
* Adds a callback that registers the options to the relevant hook.
*
* The callback receives a registry instance as the sole parameter, allowing to call the
* {@see Option_Registry::register()} method.
*
* @since 0.1.0
*
* @param callable $register_callback Callback to register the options.
*/
public function add_register_callback( callable $register_callback ): void {
add_action(
'init',
function () use ( $register_callback ) {
$register_callback( $this->registry );
}
);
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Options\Option_Registry
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Options;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Registry;
/**
* Class for a registry of WordPress options.
*
* @since 0.1.0
*/
class Option_Registry implements Registry {
/**
* Default option group to use.
*
* @since 0.1.0
* @var string
*/
private $default_group;
/**
* Constructor.
*
* @since 0.1.0
*
* @param string $default_group Default option group to use.
*/
public function __construct( string $default_group ) {
$this->default_group = $default_group;
}
/**
* Registers an option with the given key and arguments.
*
* If the arguments include a 'group' key, that value will be used as the option group as used by WordPress core.
* Otherwise, the default group will be used.
*
* @since 0.1.0
*
* @param string $key Option key.
* @param array<string, mixed> $args Option registration arguments.
* @return bool True on success, false on failure.
*/
public function register( string $key, array $args ): bool {
// Use provided group, or default group otherwise.
$group = $args['group'] ?? $this->default_group;
register_setting( $group, $key, $args );
return true;
}
/**
* Checks whether an option with the given key is registered.
*
* @since 0.1.0
*
* @param string $key Option key.
* @return bool True if the option is registered, false otherwise.
*/
public function is_registered( string $key ): bool {
$registered = get_registered_settings();
return isset( $registered[ $key ] );
}
/**
* Gets the registered option for the given key from the registry.
*
* @since 0.1.0
*
* @param string $key Option key.
* @return object|null The registered option definition, or `null` if not registered.
*/
public function get_registered( string $key ) {
$registered = get_registered_settings();
return $registered[ $key ] ?? null;
}
/**
* Gets all options from the registry.
*
* @since 0.1.0
*
* @return array<string, mixed> Associative array of keys and their option definitions, or empty array if nothing
* is registered.
*/
public function get_all_registered(): array {
return get_registered_settings();
}
}

View File

@@ -0,0 +1,123 @@
<?php
/**
* Class Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Options\Option_Repository
*
* @since 0.1.0
* @package wp-oop-plugin-lib
*/
namespace Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Options;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\General\Contracts\Key_Value_Repository;
use Felix_Arntz\ATFPP\WP_OOP_Plugin_Lib\Options\Contracts\With_Autoload_Config;
/**
* Class for a repository of WordPress options.
*
* @since 0.1.0
*/
class Option_Repository implements Key_Value_Repository, With_Autoload_Config {
/**
* Autoload config as $key => $autoload pairs.
*
* @since 0.1.0
* @var array<string, bool>
*/
private $autoload_config = array();
/**
* Checks whether a value for the given option exists in the database.
*
* @since 0.1.0
*
* @param string $key Option key.
* @return bool True if a value for the option exists, false otherwise.
*/
public function exists( string $key ): bool {
$value = get_option( $key, null );
return null !== $value;
}
/**
* Gets the value for a given option from the database.
*
* @since 0.1.0
*
* @param string $key Option key.
* @param mixed $default Optional. Value to return if no value exists for the option. Default null.
* @return mixed Value for the option, or the default if no value exists.
*/
public function get( string $key, $default = null ) {
return get_option( $key, $default );
}
/**
* Updates the value for a given option in the database.
*
* @since 0.1.0
*
* @param string $key Option key.
* @param mixed $value New value to set for the option.
* @return bool True on success, false on failure.
*/
public function update( string $key, $value ): bool {
$autoload = $this->get_autoload_config( $key );
// Warn if no autoload config is set.
if ( null === $autoload ) {
$message = __( 'Updating an option without having an autoload value specified is discouraged.', 'wp-oop-plugin-lib' ); // phpcs:ignore Generic.Files.LineLength.TooLong
$message .= ' ' . sprintf(
/* translators: 1: Method name, 2: Argument name, 3: Method name */
__( 'Use the %1$s method or pass the "%2$s" argument to the %3$s to specify an autoload value.', 'wp-oop-plugin-lib' ), // phpcs:ignore Generic.Files.LineLength.TooLong
__CLASS__ . '::set_autoload_config()',
'autoload',
Option::class . '::__construct()'
);
_doing_it_wrong(
__METHOD__,
esc_html( $message ),
''
);
}
return (bool) update_option( $key, $value, $autoload );
}
/**
* Deletes the data for a given option from the database.
*
* @since 0.1.0
*
* @param string $key Option key.
* @return bool True on success, false on failure.
*/
public function delete( string $key ): bool {
return (bool) delete_option( $key );
}
/**
* Gets the autoload config for a given option in the database.
*
* @since 0.1.0
*
* @param string $key Option key.
* @return bool|null Whether or not the item should be autoloaded, or null if not specified.
*/
public function get_autoload_config( string $key ) {
// The default value is true.
return $this->autoload_config[ $key ] ?? null;
}
/**
* Sets the autoload config for a given option in the database.
*
* @since 0.1.0
*
* @param string $key Option key.
* @param bool $autoload Option autoload config.
*/
public function set_autoload_config( string $key, bool $autoload ): void {
$this->autoload_config[ $key ] = $autoload;
}
}

View File

@@ -0,0 +1,27 @@
The MIT License (MIT)
Copyright (c) 2011 Michael Dowling <mtdowling@gmail.com>
Copyright (c) 2012 Jeremy Lindblom <jeremeamia@gmail.com>
Copyright (c) 2014 Graham Campbell <hello@gjcampbell.co.uk>
Copyright (c) 2015 Márk Sági-Kazár <mark.sagikazar@gmail.com>
Copyright (c) 2015 Tobias Schultze <webmaster@tubo-world.de>
Copyright (c) 2016 Tobias Nyholm <tobias.nyholm@gmail.com>
Copyright (c) 2016 George Mponos <gmponos@gmail.com>
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.

View File

@@ -0,0 +1,28 @@
<?php
namespace GuzzleHttp;
use Psr\Http\Message\MessageInterface;
final class BodySummarizer implements BodySummarizerInterface
{
/**
* @var int|null
*/
private $truncateAt;
public function __construct(?int $truncateAt = null)
{
$this->truncateAt = $truncateAt;
}
/**
* Returns a summarized message body.
*/
public function summarize(MessageInterface $message): ?string
{
return $this->truncateAt === null
? Psr7\Message::bodySummary($message)
: Psr7\Message::bodySummary($message, $this->truncateAt);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace GuzzleHttp;
use Psr\Http\Message\MessageInterface;
interface BodySummarizerInterface
{
/**
* Returns a summarized message body.
*/
public function summarize(MessageInterface $message): ?string;
}

View File

@@ -0,0 +1,483 @@
<?php
namespace GuzzleHttp;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\InvalidArgumentException;
use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;
/**
* @final
*/
class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
{
use ClientTrait;
/**
* @var array Default request options
*/
private $config;
/**
* Clients accept an array of constructor parameters.
*
* Here's an example of creating a client using a base_uri and an array of
* default request options to apply to each request:
*
* $client = new Client([
* 'base_uri' => 'http://www.foo.com/1.0/',
* 'timeout' => 0,
* 'allow_redirects' => false,
* 'proxy' => '192.168.16.1:10'
* ]);
*
* Client configuration settings include the following options:
*
* - handler: (callable) Function that transfers HTTP requests over the
* wire. The function is called with a Psr7\Http\Message\RequestInterface
* and array of transfer options, and must return a
* GuzzleHttp\Promise\PromiseInterface that is fulfilled with a
* Psr7\Http\Message\ResponseInterface on success.
* If no handler is provided, a default handler will be created
* that enables all of the request options below by attaching all of the
* default middleware to the handler.
* - base_uri: (string|UriInterface) Base URI of the client that is merged
* into relative URIs. Can be a string or instance of UriInterface.
* - **: any request option
*
* @param array $config Client configuration settings.
*
* @see RequestOptions for a list of available request options.
*/
public function __construct(array $config = [])
{
if (!isset($config['handler'])) {
$config['handler'] = HandlerStack::create();
} elseif (!\is_callable($config['handler'])) {
throw new InvalidArgumentException('handler must be a callable');
}
// Convert the base_uri to a UriInterface
if (isset($config['base_uri'])) {
$config['base_uri'] = Psr7\Utils::uriFor($config['base_uri']);
}
$this->configureDefaults($config);
}
/**
* @param string $method
* @param array $args
*
* @return PromiseInterface|ResponseInterface
*
* @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0.
*/
public function __call($method, $args)
{
if (\count($args) < 1) {
throw new InvalidArgumentException('Magic request methods require a URI and optional options array');
}
$uri = $args[0];
$opts = $args[1] ?? [];
return \substr($method, -5) === 'Async'
? $this->requestAsync(\substr($method, 0, -5), $uri, $opts)
: $this->request($method, $uri, $opts);
}
/**
* Asynchronously send an HTTP request.
*
* @param array $options Request options to apply to the given
* request and to the transfer. See \GuzzleHttp\RequestOptions.
*/
public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface
{
// Merge the base URI into the request URI if needed.
$options = $this->prepareDefaults($options);
return $this->transfer(
$request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')),
$options
);
}
/**
* Send an HTTP request.
*
* @param array $options Request options to apply to the given
* request and to the transfer. See \GuzzleHttp\RequestOptions.
*
* @throws GuzzleException
*/
public function send(RequestInterface $request, array $options = []): ResponseInterface
{
$options[RequestOptions::SYNCHRONOUS] = true;
return $this->sendAsync($request, $options)->wait();
}
/**
* The HttpClient PSR (PSR-18) specify this method.
*
* {@inheritDoc}
*/
public function sendRequest(RequestInterface $request): ResponseInterface
{
$options[RequestOptions::SYNCHRONOUS] = true;
$options[RequestOptions::ALLOW_REDIRECTS] = false;
$options[RequestOptions::HTTP_ERRORS] = false;
return $this->sendAsync($request, $options)->wait();
}
/**
* Create and send an asynchronous HTTP request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well. Use an array to provide a URL
* template and additional variables to use in the URL template expansion.
*
* @param string $method HTTP method
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply. See \GuzzleHttp\RequestOptions.
*/
public function requestAsync(string $method, $uri = '', array $options = []): PromiseInterface
{
$options = $this->prepareDefaults($options);
// Remove request modifying parameter because it can be done up-front.
$headers = $options['headers'] ?? [];
$body = $options['body'] ?? null;
$version = $options['version'] ?? '1.1';
// Merge the URI into the base URI.
$uri = $this->buildUri(Psr7\Utils::uriFor($uri), $options);
if (\is_array($body)) {
throw $this->invalidBody();
}
$request = new Psr7\Request($method, $uri, $headers, $body, $version);
// Remove the option so that they are not doubly-applied.
unset($options['headers'], $options['body'], $options['version']);
return $this->transfer($request, $options);
}
/**
* Create and send an HTTP request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well.
*
* @param string $method HTTP method.
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply. See \GuzzleHttp\RequestOptions.
*
* @throws GuzzleException
*/
public function request(string $method, $uri = '', array $options = []): ResponseInterface
{
$options[RequestOptions::SYNCHRONOUS] = true;
return $this->requestAsync($method, $uri, $options)->wait();
}
/**
* Get a client configuration option.
*
* These options include default request options of the client, a "handler"
* (if utilized by the concrete client), and a "base_uri" if utilized by
* the concrete client.
*
* @param string|null $option The config option to retrieve.
*
* @return mixed
*
* @deprecated Client::getConfig will be removed in guzzlehttp/guzzle:8.0.
*/
public function getConfig(?string $option = null)
{
return $option === null
? $this->config
: ($this->config[$option] ?? null);
}
private function buildUri(UriInterface $uri, array $config): UriInterface
{
if (isset($config['base_uri'])) {
$uri = Psr7\UriResolver::resolve(Psr7\Utils::uriFor($config['base_uri']), $uri);
}
if (isset($config['idn_conversion']) && ($config['idn_conversion'] !== false)) {
$idnOptions = ($config['idn_conversion'] === true) ? \IDNA_DEFAULT : $config['idn_conversion'];
$uri = Utils::idnUriConvert($uri, $idnOptions);
}
return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
}
/**
* Configures the default options for a client.
*/
private function configureDefaults(array $config): void
{
$defaults = [
'allow_redirects' => RedirectMiddleware::$defaultSettings,
'http_errors' => true,
'decode_content' => true,
'verify' => true,
'cookies' => false,
'idn_conversion' => false,
];
// Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.
// We can only trust the HTTP_PROXY environment variable in a CLI
// process due to the fact that PHP has no reliable mechanism to
// get environment variables that start with "HTTP_".
if (\PHP_SAPI === 'cli' && ($proxy = Utils::getenv('HTTP_PROXY'))) {
$defaults['proxy']['http'] = $proxy;
}
if ($proxy = Utils::getenv('HTTPS_PROXY')) {
$defaults['proxy']['https'] = $proxy;
}
if ($noProxy = Utils::getenv('NO_PROXY')) {
$cleanedNoProxy = \str_replace(' ', '', $noProxy);
$defaults['proxy']['no'] = \explode(',', $cleanedNoProxy);
}
$this->config = $config + $defaults;
if (!empty($config['cookies']) && $config['cookies'] === true) {
$this->config['cookies'] = new CookieJar();
}
// Add the default user-agent header.
if (!isset($this->config['headers'])) {
$this->config['headers'] = ['User-Agent' => Utils::defaultUserAgent()];
} else {
// Add the User-Agent header if one was not already set.
foreach (\array_keys($this->config['headers']) as $name) {
if (\strtolower($name) === 'user-agent') {
return;
}
}
$this->config['headers']['User-Agent'] = Utils::defaultUserAgent();
}
}
/**
* Merges default options into the array.
*
* @param array $options Options to modify by reference
*/
private function prepareDefaults(array $options): array
{
$defaults = $this->config;
if (!empty($defaults['headers'])) {
// Default headers are only added if they are not present.
$defaults['_conditional'] = $defaults['headers'];
unset($defaults['headers']);
}
// Special handling for headers is required as they are added as
// conditional headers and as headers passed to a request ctor.
if (\array_key_exists('headers', $options)) {
// Allows default headers to be unset.
if ($options['headers'] === null) {
$defaults['_conditional'] = [];
unset($options['headers']);
} elseif (!\is_array($options['headers'])) {
throw new InvalidArgumentException('headers must be an array');
}
}
// Shallow merge defaults underneath options.
$result = $options + $defaults;
// Remove null values.
foreach ($result as $k => $v) {
if ($v === null) {
unset($result[$k]);
}
}
return $result;
}
/**
* Transfers the given request and applies request options.
*
* The URI of the request is not modified and the request options are used
* as-is without merging in default options.
*
* @param array $options See \GuzzleHttp\RequestOptions.
*/
private function transfer(RequestInterface $request, array $options): PromiseInterface
{
$request = $this->applyOptions($request, $options);
/** @var HandlerStack $handler */
$handler = $options['handler'];
try {
return P\Create::promiseFor($handler($request, $options));
} catch (\Exception $e) {
return P\Create::rejectionFor($e);
}
}
/**
* Applies the array of request options to a request.
*/
private function applyOptions(RequestInterface $request, array &$options): RequestInterface
{
$modify = [
'set_headers' => [],
];
if (isset($options['headers'])) {
if (array_keys($options['headers']) === range(0, count($options['headers']) - 1)) {
throw new InvalidArgumentException('The headers array must have header name as keys.');
}
$modify['set_headers'] = $options['headers'];
unset($options['headers']);
}
if (isset($options['form_params'])) {
if (isset($options['multipart'])) {
throw new InvalidArgumentException('You cannot use '
.'form_params and multipart at the same time. Use the '
.'form_params option if you want to send application/'
.'x-www-form-urlencoded requests, and the multipart '
.'option to send multipart/form-data requests.');
}
$options['body'] = \http_build_query($options['form_params'], '', '&');
unset($options['form_params']);
// Ensure that we don't have the header in different case and set the new value.
$options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
$options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
}
if (isset($options['multipart'])) {
$options['body'] = new Psr7\MultipartStream($options['multipart']);
unset($options['multipart']);
}
if (isset($options['json'])) {
$options['body'] = Utils::jsonEncode($options['json']);
unset($options['json']);
// Ensure that we don't have the header in different case and set the new value.
$options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
$options['_conditional']['Content-Type'] = 'application/json';
}
if (!empty($options['decode_content'])
&& $options['decode_content'] !== true
) {
// Ensure that we don't have the header in different case and set the new value.
$options['_conditional'] = Psr7\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']);
$modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
}
if (isset($options['body'])) {
if (\is_array($options['body'])) {
throw $this->invalidBody();
}
$modify['body'] = Psr7\Utils::streamFor($options['body']);
unset($options['body']);
}
if (!empty($options['auth']) && \is_array($options['auth'])) {
$value = $options['auth'];
$type = isset($value[2]) ? \strtolower($value[2]) : 'basic';
switch ($type) {
case 'basic':
// Ensure that we don't have the header in different case and set the new value.
$modify['set_headers'] = Psr7\Utils::caselessRemove(['Authorization'], $modify['set_headers']);
$modify['set_headers']['Authorization'] = 'Basic '
.\base64_encode("$value[0]:$value[1]");
break;
case 'digest':
// @todo: Do not rely on curl
$options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_DIGEST;
$options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]";
break;
case 'ntlm':
$options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM;
$options['curl'][\CURLOPT_USERPWD] = "$value[0]:$value[1]";
break;
}
}
if (isset($options['query'])) {
$value = $options['query'];
if (\is_array($value)) {
$value = \http_build_query($value, '', '&', \PHP_QUERY_RFC3986);
}
if (!\is_string($value)) {
throw new InvalidArgumentException('query must be a string or array');
}
$modify['query'] = $value;
unset($options['query']);
}
// Ensure that sink is not an invalid value.
if (isset($options['sink'])) {
// TODO: Add more sink validation?
if (\is_bool($options['sink'])) {
throw new InvalidArgumentException('sink must not be a boolean');
}
}
if (isset($options['version'])) {
$modify['version'] = $options['version'];
}
$request = Psr7\Utils::modifyRequest($request, $modify);
if ($request->getBody() instanceof Psr7\MultipartStream) {
// Use a multipart/form-data POST if a Content-Type is not set.
// Ensure that we don't have the header in different case and set the new value.
$options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']);
$options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
.$request->getBody()->getBoundary();
}
// Merge in conditional headers if they are not present.
if (isset($options['_conditional'])) {
// Build up the changes so it's in a single clone of the message.
$modify = [];
foreach ($options['_conditional'] as $k => $v) {
if (!$request->hasHeader($k)) {
$modify['set_headers'][$k] = $v;
}
}
$request = Psr7\Utils::modifyRequest($request, $modify);
// Don't pass this internal value along to middleware/handlers.
unset($options['_conditional']);
}
return $request;
}
/**
* Return an InvalidArgumentException with pre-set message.
*/
private function invalidBody(): InvalidArgumentException
{
return new InvalidArgumentException('Passing in the "body" request '
.'option as an array to send a request is not supported. '
.'Please use the "form_params" request option to send a '
.'application/x-www-form-urlencoded request, or the "multipart" '
.'request option to send a multipart/form-data request.');
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace GuzzleHttp;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;
/**
* Client interface for sending HTTP requests.
*/
interface ClientInterface
{
/**
* The Guzzle major version.
*/
public const MAJOR_VERSION = 7;
/**
* Send an HTTP request.
*
* @param RequestInterface $request Request to send
* @param array $options Request options to apply to the given
* request and to the transfer.
*
* @throws GuzzleException
*/
public function send(RequestInterface $request, array $options = []): ResponseInterface;
/**
* Asynchronously send an HTTP request.
*
* @param RequestInterface $request Request to send
* @param array $options Request options to apply to the given
* request and to the transfer.
*/
public function sendAsync(RequestInterface $request, array $options = []): PromiseInterface;
/**
* Create and send an HTTP request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well.
*
* @param string $method HTTP method.
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*
* @throws GuzzleException
*/
public function request(string $method, $uri, array $options = []): ResponseInterface;
/**
* Create and send an asynchronous HTTP request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well. Use an array to provide a URL
* template and additional variables to use in the URL template expansion.
*
* @param string $method HTTP method
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*/
public function requestAsync(string $method, $uri, array $options = []): PromiseInterface;
/**
* Get a client configuration option.
*
* These options include default request options of the client, a "handler"
* (if utilized by the concrete client), and a "base_uri" if utilized by
* the concrete client.
*
* @param string|null $option The config option to retrieve.
*
* @return mixed
*
* @deprecated ClientInterface::getConfig will be removed in guzzlehttp/guzzle:8.0.
*/
public function getConfig(?string $option = null);
}

View File

@@ -0,0 +1,241 @@
<?php
namespace GuzzleHttp;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;
/**
* Client interface for sending HTTP requests.
*/
trait ClientTrait
{
/**
* Create and send an HTTP request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well.
*
* @param string $method HTTP method.
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*
* @throws GuzzleException
*/
abstract public function request(string $method, $uri, array $options = []): ResponseInterface;
/**
* Create and send an HTTP GET request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*
* @throws GuzzleException
*/
public function get($uri, array $options = []): ResponseInterface
{
return $this->request('GET', $uri, $options);
}
/**
* Create and send an HTTP HEAD request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*
* @throws GuzzleException
*/
public function head($uri, array $options = []): ResponseInterface
{
return $this->request('HEAD', $uri, $options);
}
/**
* Create and send an HTTP PUT request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*
* @throws GuzzleException
*/
public function put($uri, array $options = []): ResponseInterface
{
return $this->request('PUT', $uri, $options);
}
/**
* Create and send an HTTP POST request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*
* @throws GuzzleException
*/
public function post($uri, array $options = []): ResponseInterface
{
return $this->request('POST', $uri, $options);
}
/**
* Create and send an HTTP PATCH request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*
* @throws GuzzleException
*/
public function patch($uri, array $options = []): ResponseInterface
{
return $this->request('PATCH', $uri, $options);
}
/**
* Create and send an HTTP DELETE request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*
* @throws GuzzleException
*/
public function delete($uri, array $options = []): ResponseInterface
{
return $this->request('DELETE', $uri, $options);
}
/**
* Create and send an asynchronous HTTP request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well. Use an array to provide a URL
* template and additional variables to use in the URL template expansion.
*
* @param string $method HTTP method
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*/
abstract public function requestAsync(string $method, $uri, array $options = []): PromiseInterface;
/**
* Create and send an asynchronous HTTP GET request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well. Use an array to provide a URL
* template and additional variables to use in the URL template expansion.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*/
public function getAsync($uri, array $options = []): PromiseInterface
{
return $this->requestAsync('GET', $uri, $options);
}
/**
* Create and send an asynchronous HTTP HEAD request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well. Use an array to provide a URL
* template and additional variables to use in the URL template expansion.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*/
public function headAsync($uri, array $options = []): PromiseInterface
{
return $this->requestAsync('HEAD', $uri, $options);
}
/**
* Create and send an asynchronous HTTP PUT request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well. Use an array to provide a URL
* template and additional variables to use in the URL template expansion.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*/
public function putAsync($uri, array $options = []): PromiseInterface
{
return $this->requestAsync('PUT', $uri, $options);
}
/**
* Create and send an asynchronous HTTP POST request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well. Use an array to provide a URL
* template and additional variables to use in the URL template expansion.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*/
public function postAsync($uri, array $options = []): PromiseInterface
{
return $this->requestAsync('POST', $uri, $options);
}
/**
* Create and send an asynchronous HTTP PATCH request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well. Use an array to provide a URL
* template and additional variables to use in the URL template expansion.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*/
public function patchAsync($uri, array $options = []): PromiseInterface
{
return $this->requestAsync('PATCH', $uri, $options);
}
/**
* Create and send an asynchronous HTTP DELETE request.
*
* Use an absolute path to override the base path of the client, or a
* relative path to append to the base path of the client. The URL can
* contain the query string as well. Use an array to provide a URL
* template and additional variables to use in the URL template expansion.
*
* @param string|UriInterface $uri URI object or string.
* @param array $options Request options to apply.
*/
public function deleteAsync($uri, array $options = []): PromiseInterface
{
return $this->requestAsync('DELETE', $uri, $options);
}
}

View File

@@ -0,0 +1,307 @@
<?php
namespace GuzzleHttp\Cookie;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Cookie jar that stores cookies as an array
*/
class CookieJar implements CookieJarInterface
{
/**
* @var SetCookie[] Loaded cookie data
*/
private $cookies = [];
/**
* @var bool
*/
private $strictMode;
/**
* @param bool $strictMode Set to true to throw exceptions when invalid
* cookies are added to the cookie jar.
* @param array $cookieArray Array of SetCookie objects or a hash of
* arrays that can be used with the SetCookie
* constructor
*/
public function __construct(bool $strictMode = false, array $cookieArray = [])
{
$this->strictMode = $strictMode;
foreach ($cookieArray as $cookie) {
if (!($cookie instanceof SetCookie)) {
$cookie = new SetCookie($cookie);
}
$this->setCookie($cookie);
}
}
/**
* Create a new Cookie jar from an associative array and domain.
*
* @param array $cookies Cookies to create the jar from
* @param string $domain Domain to set the cookies to
*/
public static function fromArray(array $cookies, string $domain): self
{
$cookieJar = new self();
foreach ($cookies as $name => $value) {
$cookieJar->setCookie(new SetCookie([
'Domain' => $domain,
'Name' => $name,
'Value' => $value,
'Discard' => true,
]));
}
return $cookieJar;
}
/**
* Evaluate if this cookie should be persisted to storage
* that survives between requests.
*
* @param SetCookie $cookie Being evaluated.
* @param bool $allowSessionCookies If we should persist session cookies
*/
public static function shouldPersist(SetCookie $cookie, bool $allowSessionCookies = false): bool
{
if ($cookie->getExpires() || $allowSessionCookies) {
if (!$cookie->getDiscard()) {
return true;
}
}
return false;
}
/**
* Finds and returns the cookie based on the name
*
* @param string $name cookie name to search for
*
* @return SetCookie|null cookie that was found or null if not found
*/
public function getCookieByName(string $name): ?SetCookie
{
foreach ($this->cookies as $cookie) {
if ($cookie->getName() !== null && \strcasecmp($cookie->getName(), $name) === 0) {
return $cookie;
}
}
return null;
}
public function toArray(): array
{
return \array_map(static function (SetCookie $cookie): array {
return $cookie->toArray();
}, $this->getIterator()->getArrayCopy());
}
public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void
{
if (!$domain) {
$this->cookies = [];
return;
} elseif (!$path) {
$this->cookies = \array_filter(
$this->cookies,
static function (SetCookie $cookie) use ($domain): bool {
return !$cookie->matchesDomain($domain);
}
);
} elseif (!$name) {
$this->cookies = \array_filter(
$this->cookies,
static function (SetCookie $cookie) use ($path, $domain): bool {
return !($cookie->matchesPath($path)
&& $cookie->matchesDomain($domain));
}
);
} else {
$this->cookies = \array_filter(
$this->cookies,
static function (SetCookie $cookie) use ($path, $domain, $name) {
return !($cookie->getName() == $name
&& $cookie->matchesPath($path)
&& $cookie->matchesDomain($domain));
}
);
}
}
public function clearSessionCookies(): void
{
$this->cookies = \array_filter(
$this->cookies,
static function (SetCookie $cookie): bool {
return !$cookie->getDiscard() && $cookie->getExpires();
}
);
}
public function setCookie(SetCookie $cookie): bool
{
// If the name string is empty (but not 0), ignore the set-cookie
// string entirely.
$name = $cookie->getName();
if (!$name && $name !== '0') {
return false;
}
// Only allow cookies with set and valid domain, name, value
$result = $cookie->validate();
if ($result !== true) {
if ($this->strictMode) {
throw new \RuntimeException('Invalid cookie: '.$result);
}
$this->removeCookieIfEmpty($cookie);
return false;
}
// Resolve conflicts with previously set cookies
foreach ($this->cookies as $i => $c) {
// Two cookies are identical, when their path, and domain are
// identical.
if ($c->getPath() != $cookie->getPath()
|| $c->getDomain() != $cookie->getDomain()
|| $c->getName() != $cookie->getName()
) {
continue;
}
// The previously set cookie is a discard cookie and this one is
// not so allow the new cookie to be set
if (!$cookie->getDiscard() && $c->getDiscard()) {
unset($this->cookies[$i]);
continue;
}
// If the new cookie's expiration is further into the future, then
// replace the old cookie
if ($cookie->getExpires() > $c->getExpires()) {
unset($this->cookies[$i]);
continue;
}
// If the value has changed, we better change it
if ($cookie->getValue() !== $c->getValue()) {
unset($this->cookies[$i]);
continue;
}
// The cookie exists, so no need to continue
return false;
}
$this->cookies[] = $cookie;
return true;
}
public function count(): int
{
return \count($this->cookies);
}
/**
* @return \ArrayIterator<int, SetCookie>
*/
public function getIterator(): \ArrayIterator
{
return new \ArrayIterator(\array_values($this->cookies));
}
public function extractCookies(RequestInterface $request, ResponseInterface $response): void
{
if ($cookieHeader = $response->getHeader('Set-Cookie')) {
foreach ($cookieHeader as $cookie) {
$sc = SetCookie::fromString($cookie);
if (!$sc->getDomain()) {
$sc->setDomain($request->getUri()->getHost());
}
if (0 !== \strpos($sc->getPath(), '/')) {
$sc->setPath($this->getCookiePathFromRequest($request));
}
if (!$sc->matchesDomain($request->getUri()->getHost())) {
continue;
}
// Note: At this point `$sc->getDomain()` being a public suffix should
// be rejected, but we don't want to pull in the full PSL dependency.
$this->setCookie($sc);
}
}
}
/**
* Computes cookie path following RFC 6265 section 5.1.4
*
* @see https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4
*/
private function getCookiePathFromRequest(RequestInterface $request): string
{
$uriPath = $request->getUri()->getPath();
if ('' === $uriPath) {
return '/';
}
if (0 !== \strpos($uriPath, '/')) {
return '/';
}
if ('/' === $uriPath) {
return '/';
}
$lastSlashPos = \strrpos($uriPath, '/');
if (0 === $lastSlashPos || false === $lastSlashPos) {
return '/';
}
return \substr($uriPath, 0, $lastSlashPos);
}
public function withCookieHeader(RequestInterface $request): RequestInterface
{
$values = [];
$uri = $request->getUri();
$scheme = $uri->getScheme();
$host = $uri->getHost();
$path = $uri->getPath() ?: '/';
foreach ($this->cookies as $cookie) {
if ($cookie->matchesPath($path)
&& $cookie->matchesDomain($host)
&& !$cookie->isExpired()
&& (!$cookie->getSecure() || $scheme === 'https')
) {
$values[] = $cookie->getName().'='
.$cookie->getValue();
}
}
return $values
? $request->withHeader('Cookie', \implode('; ', $values))
: $request;
}
/**
* If a cookie already exists and the server asks to set it again with a
* null value, the cookie must be deleted.
*/
private function removeCookieIfEmpty(SetCookie $cookie): void
{
$cookieValue = $cookie->getValue();
if ($cookieValue === null || $cookieValue === '') {
$this->clear(
$cookie->getDomain(),
$cookie->getPath(),
$cookie->getName()
);
}
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace GuzzleHttp\Cookie;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Stores HTTP cookies.
*
* It extracts cookies from HTTP requests, and returns them in HTTP responses.
* CookieJarInterface instances automatically expire contained cookies when
* necessary. Subclasses are also responsible for storing and retrieving
* cookies from a file, database, etc.
*
* @see https://docs.python.org/2/library/cookielib.html Inspiration
*
* @extends \IteratorAggregate<SetCookie>
*/
interface CookieJarInterface extends \Countable, \IteratorAggregate
{
/**
* Create a request with added cookie headers.
*
* If no matching cookies are found in the cookie jar, then no Cookie
* header is added to the request and the same request is returned.
*
* @param RequestInterface $request Request object to modify.
*
* @return RequestInterface returns the modified request.
*/
public function withCookieHeader(RequestInterface $request): RequestInterface;
/**
* Extract cookies from an HTTP response and store them in the CookieJar.
*
* @param RequestInterface $request Request that was sent
* @param ResponseInterface $response Response that was received
*/
public function extractCookies(RequestInterface $request, ResponseInterface $response): void;
/**
* Sets a cookie in the cookie jar.
*
* @param SetCookie $cookie Cookie to set.
*
* @return bool Returns true on success or false on failure
*/
public function setCookie(SetCookie $cookie): bool;
/**
* Remove cookies currently held in the cookie jar.
*
* Invoking this method without arguments will empty the whole cookie jar.
* If given a $domain argument only cookies belonging to that domain will
* be removed. If given a $domain and $path argument, cookies belonging to
* the specified path within that domain are removed. If given all three
* arguments, then the cookie with the specified name, path and domain is
* removed.
*
* @param string|null $domain Clears cookies matching a domain
* @param string|null $path Clears cookies matching a domain and path
* @param string|null $name Clears cookies matching a domain, path, and name
*/
public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void;
/**
* Discard all sessions cookies.
*
* Removes cookies that don't have an expire field or a have a discard
* field set to true. To be called when the user agent shuts down according
* to RFC 2965.
*/
public function clearSessionCookies(): void;
/**
* Converts the cookie jar to an array.
*/
public function toArray(): array;
}

View File

@@ -0,0 +1,101 @@
<?php
namespace GuzzleHttp\Cookie;
use GuzzleHttp\Utils;
/**
* Persists non-session cookies using a JSON formatted file
*/
class FileCookieJar extends CookieJar
{
/**
* @var string filename
*/
private $filename;
/**
* @var bool Control whether to persist session cookies or not.
*/
private $storeSessionCookies;
/**
* Create a new FileCookieJar object
*
* @param string $cookieFile File to store the cookie data
* @param bool $storeSessionCookies Set to true to store session cookies
* in the cookie jar.
*
* @throws \RuntimeException if the file cannot be found or created
*/
public function __construct(string $cookieFile, bool $storeSessionCookies = false)
{
parent::__construct();
$this->filename = $cookieFile;
$this->storeSessionCookies = $storeSessionCookies;
if (\file_exists($cookieFile)) {
$this->load($cookieFile);
}
}
/**
* Saves the file when shutting down
*/
public function __destruct()
{
$this->save($this->filename);
}
/**
* Saves the cookies to a file.
*
* @param string $filename File to save
*
* @throws \RuntimeException if the file cannot be found or created
*/
public function save(string $filename): void
{
$json = [];
/** @var SetCookie $cookie */
foreach ($this as $cookie) {
if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
$json[] = $cookie->toArray();
}
}
$jsonStr = Utils::jsonEncode($json);
if (false === \file_put_contents($filename, $jsonStr, \LOCK_EX)) {
throw new \RuntimeException("Unable to save file {$filename}");
}
}
/**
* Load cookies from a JSON formatted file.
*
* Old cookies are kept unless overwritten by newly loaded ones.
*
* @param string $filename Cookie file to load.
*
* @throws \RuntimeException if the file cannot be loaded.
*/
public function load(string $filename): void
{
$json = \file_get_contents($filename);
if (false === $json) {
throw new \RuntimeException("Unable to load file {$filename}");
}
if ($json === '') {
return;
}
$data = Utils::jsonDecode($json, true);
if (\is_array($data)) {
foreach ($data as $cookie) {
$this->setCookie(new SetCookie($cookie));
}
} elseif (\is_scalar($data) && !empty($data)) {
throw new \RuntimeException("Invalid cookie file: {$filename}");
}
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace GuzzleHttp\Cookie;
/**
* Persists cookies in the client session
*/
class SessionCookieJar extends CookieJar
{
/**
* @var string session key
*/
private $sessionKey;
/**
* @var bool Control whether to persist session cookies or not.
*/
private $storeSessionCookies;
/**
* Create a new SessionCookieJar object
*
* @param string $sessionKey Session key name to store the cookie
* data in session
* @param bool $storeSessionCookies Set to true to store session cookies
* in the cookie jar.
*/
public function __construct(string $sessionKey, bool $storeSessionCookies = false)
{
parent::__construct();
$this->sessionKey = $sessionKey;
$this->storeSessionCookies = $storeSessionCookies;
$this->load();
}
/**
* Saves cookies to session when shutting down
*/
public function __destruct()
{
$this->save();
}
/**
* Save cookies to the client session
*/
public function save(): void
{
$json = [];
/** @var SetCookie $cookie */
foreach ($this as $cookie) {
if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
$json[] = $cookie->toArray();
}
}
$_SESSION[$this->sessionKey] = \json_encode($json);
}
/**
* Load the contents of the client session into the data array
*/
protected function load(): void
{
if (!isset($_SESSION[$this->sessionKey])) {
return;
}
$data = \json_decode($_SESSION[$this->sessionKey], true);
if (\is_array($data)) {
foreach ($data as $cookie) {
$this->setCookie(new SetCookie($cookie));
}
} elseif (\strlen($data)) {
throw new \RuntimeException('Invalid cookie data');
}
}
}

View File

@@ -0,0 +1,492 @@
<?php
namespace GuzzleHttp\Cookie;
/**
* Set-Cookie object
*/
class SetCookie
{
/**
* @var array
*/
private static $defaults = [
'Name' => null,
'Value' => null,
'Domain' => null,
'Path' => '/',
'Max-Age' => null,
'Expires' => null,
'Secure' => false,
'Discard' => false,
'HttpOnly' => false,
];
/**
* @var array Cookie data
*/
private $data;
/**
* Create a new SetCookie object from a string.
*
* @param string $cookie Set-Cookie header string
*/
public static function fromString(string $cookie): self
{
// Create the default return array
$data = self::$defaults;
// Explode the cookie string using a series of semicolons
$pieces = \array_filter(\array_map('trim', \explode(';', $cookie)));
// The name of the cookie (first kvp) must exist and include an equal sign.
if (!isset($pieces[0]) || \strpos($pieces[0], '=') === false) {
return new self($data);
}
// Add the cookie pieces into the parsed data array
foreach ($pieces as $part) {
$cookieParts = \explode('=', $part, 2);
$key = \trim($cookieParts[0]);
$value = isset($cookieParts[1])
? \trim($cookieParts[1], " \n\r\t\0\x0B")
: true;
// Only check for non-cookies when cookies have been found
if (!isset($data['Name'])) {
$data['Name'] = $key;
$data['Value'] = $value;
} else {
foreach (\array_keys(self::$defaults) as $search) {
if (!\strcasecmp($search, $key)) {
if ($search === 'Max-Age') {
if (is_numeric($value)) {
$data[$search] = (int) $value;
}
} elseif ($search === 'Secure' || $search === 'Discard' || $search === 'HttpOnly') {
if ($value) {
$data[$search] = true;
}
} else {
$data[$search] = $value;
}
continue 2;
}
}
$data[$key] = $value;
}
}
return new self($data);
}
/**
* @param array $data Array of cookie data provided by a Cookie parser
*/
public function __construct(array $data = [])
{
$this->data = self::$defaults;
if (isset($data['Name'])) {
$this->setName($data['Name']);
}
if (isset($data['Value'])) {
$this->setValue($data['Value']);
}
if (isset($data['Domain'])) {
$this->setDomain($data['Domain']);
}
if (isset($data['Path'])) {
$this->setPath($data['Path']);
}
if (isset($data['Max-Age'])) {
$this->setMaxAge($data['Max-Age']);
}
if (isset($data['Expires'])) {
$this->setExpires($data['Expires']);
}
if (isset($data['Secure'])) {
$this->setSecure($data['Secure']);
}
if (isset($data['Discard'])) {
$this->setDiscard($data['Discard']);
}
if (isset($data['HttpOnly'])) {
$this->setHttpOnly($data['HttpOnly']);
}
// Set the remaining values that don't have extra validation logic
foreach (array_diff(array_keys($data), array_keys(self::$defaults)) as $key) {
$this->data[$key] = $data[$key];
}
// Extract the Expires value and turn it into a UNIX timestamp if needed
if (!$this->getExpires() && $this->getMaxAge()) {
// Calculate the Expires date
$this->setExpires(\time() + $this->getMaxAge());
} elseif (null !== ($expires = $this->getExpires()) && !\is_numeric($expires)) {
$this->setExpires($expires);
}
}
public function __toString()
{
$str = $this->data['Name'].'='.($this->data['Value'] ?? '').'; ';
foreach ($this->data as $k => $v) {
if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) {
if ($k === 'Expires') {
$str .= 'Expires='.\gmdate('D, d M Y H:i:s \G\M\T', $v).'; ';
} else {
$str .= ($v === true ? $k : "{$k}={$v}").'; ';
}
}
}
return \rtrim($str, '; ');
}
public function toArray(): array
{
return $this->data;
}
/**
* Get the cookie name.
*
* @return string
*/
public function getName()
{
return $this->data['Name'];
}
/**
* Set the cookie name.
*
* @param string $name Cookie name
*/
public function setName($name): void
{
if (!is_string($name)) {
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
}
$this->data['Name'] = (string) $name;
}
/**
* Get the cookie value.
*
* @return string|null
*/
public function getValue()
{
return $this->data['Value'];
}
/**
* Set the cookie value.
*
* @param string $value Cookie value
*/
public function setValue($value): void
{
if (!is_string($value)) {
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
}
$this->data['Value'] = (string) $value;
}
/**
* Get the domain.
*
* @return string|null
*/
public function getDomain()
{
return $this->data['Domain'];
}
/**
* Set the domain of the cookie.
*
* @param string|null $domain
*/
public function setDomain($domain): void
{
if (!is_string($domain) && null !== $domain) {
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
}
$this->data['Domain'] = null === $domain ? null : (string) $domain;
}
/**
* Get the path.
*
* @return string
*/
public function getPath()
{
return $this->data['Path'];
}
/**
* Set the path of the cookie.
*
* @param string $path Path of the cookie
*/
public function setPath($path): void
{
if (!is_string($path)) {
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
}
$this->data['Path'] = (string) $path;
}
/**
* Maximum lifetime of the cookie in seconds.
*
* @return int|null
*/
public function getMaxAge()
{
return null === $this->data['Max-Age'] ? null : (int) $this->data['Max-Age'];
}
/**
* Set the max-age of the cookie.
*
* @param int|null $maxAge Max age of the cookie in seconds
*/
public function setMaxAge($maxAge): void
{
if (!is_int($maxAge) && null !== $maxAge) {
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
}
$this->data['Max-Age'] = $maxAge === null ? null : (int) $maxAge;
}
/**
* The UNIX timestamp when the cookie Expires.
*
* @return string|int|null
*/
public function getExpires()
{
return $this->data['Expires'];
}
/**
* Set the unix timestamp for which the cookie will expire.
*
* @param int|string|null $timestamp Unix timestamp or any English textual datetime description.
*/
public function setExpires($timestamp): void
{
if (!is_int($timestamp) && !is_string($timestamp) && null !== $timestamp) {
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int, string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
}
$this->data['Expires'] = null === $timestamp ? null : (\is_numeric($timestamp) ? (int) $timestamp : \strtotime((string) $timestamp));
}
/**
* Get whether or not this is a secure cookie.
*
* @return bool
*/
public function getSecure()
{
return $this->data['Secure'];
}
/**
* Set whether or not the cookie is secure.
*
* @param bool $secure Set to true or false if secure
*/
public function setSecure($secure): void
{
if (!is_bool($secure)) {
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
}
$this->data['Secure'] = (bool) $secure;
}
/**
* Get whether or not this is a session cookie.
*
* @return bool|null
*/
public function getDiscard()
{
return $this->data['Discard'];
}
/**
* Set whether or not this is a session cookie.
*
* @param bool $discard Set to true or false if this is a session cookie
*/
public function setDiscard($discard): void
{
if (!is_bool($discard)) {
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
}
$this->data['Discard'] = (bool) $discard;
}
/**
* Get whether or not this is an HTTP only cookie.
*
* @return bool
*/
public function getHttpOnly()
{
return $this->data['HttpOnly'];
}
/**
* Set whether or not this is an HTTP only cookie.
*
* @param bool $httpOnly Set to true or false if this is HTTP only
*/
public function setHttpOnly($httpOnly): void
{
if (!is_bool($httpOnly)) {
trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
}
$this->data['HttpOnly'] = (bool) $httpOnly;
}
/**
* Check if the cookie matches a path value.
*
* A request-path path-matches a given cookie-path if at least one of
* the following conditions holds:
*
* - The cookie-path and the request-path are identical.
* - The cookie-path is a prefix of the request-path, and the last
* character of the cookie-path is %x2F ("/").
* - The cookie-path is a prefix of the request-path, and the first
* character of the request-path that is not included in the cookie-
* path is a %x2F ("/") character.
*
* @param string $requestPath Path to check against
*/
public function matchesPath(string $requestPath): bool
{
$cookiePath = $this->getPath();
// Match on exact matches or when path is the default empty "/"
if ($cookiePath === '/' || $cookiePath == $requestPath) {
return true;
}
// Ensure that the cookie-path is a prefix of the request path.
if (0 !== \strpos($requestPath, $cookiePath)) {
return false;
}
// Match if the last character of the cookie-path is "/"
if (\substr($cookiePath, -1, 1) === '/') {
return true;
}
// Match if the first character not included in cookie path is "/"
return \substr($requestPath, \strlen($cookiePath), 1) === '/';
}
/**
* Check if the cookie matches a domain value.
*
* @param string $domain Domain to check against
*/
public function matchesDomain(string $domain): bool
{
$cookieDomain = $this->getDomain();
if (null === $cookieDomain) {
return true;
}
// Remove the leading '.' as per spec in RFC 6265.
// https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3
$cookieDomain = \ltrim(\strtolower($cookieDomain), '.');
$domain = \strtolower($domain);
// Domain not set or exact match.
if ('' === $cookieDomain || $domain === $cookieDomain) {
return true;
}
// Matching the subdomain according to RFC 6265.
// https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3
if (\filter_var($domain, \FILTER_VALIDATE_IP)) {
return false;
}
return (bool) \preg_match('/\.'.\preg_quote($cookieDomain, '/').'$/', $domain);
}
/**
* Check if the cookie is expired.
*/
public function isExpired(): bool
{
return $this->getExpires() !== null && \time() > $this->getExpires();
}
/**
* Check if the cookie is valid according to RFC 6265.
*
* @return bool|string Returns true if valid or an error message if invalid
*/
public function validate()
{
$name = $this->getName();
if ($name === '') {
return 'The cookie name must not be empty';
}
// Check if any of the invalid characters are present in the cookie name
if (\preg_match(
'/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/',
$name
)) {
return 'Cookie name must not contain invalid characters: ASCII '
.'Control characters (0-31;127), space, tab and the '
.'following characters: ()<>@,;:\"/?={}';
}
// Value must not be null. 0 and empty string are valid. Empty strings
// are technically against RFC 6265, but known to happen in the wild.
$value = $this->getValue();
if ($value === null) {
return 'The cookie value must not be empty';
}
// Domains must not be empty, but can be 0. "0" is not a valid internet
// domain, but may be used as server name in a private network.
$domain = $this->getDomain();
if ($domain === null || $domain === '') {
return 'The cookie domain must not be empty';
}
return true;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace GuzzleHttp\Exception;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Exception when an HTTP error occurs (4xx or 5xx error)
*/
class BadResponseException extends RequestException
{
public function __construct(
string $message,
RequestInterface $request,
ResponseInterface $response,
?\Throwable $previous = null,
array $handlerContext = []
) {
parent::__construct($message, $request, $response, $previous, $handlerContext);
}
/**
* Current exception and the ones that extend it will always have a response.
*/
public function hasResponse(): bool
{
return true;
}
/**
* This function narrows the return type from the parent class and does not allow it to be nullable.
*/
public function getResponse(): ResponseInterface
{
/** @var ResponseInterface */
return parent::getResponse();
}
}

Some files were not shown because too many files have changed in this diff Show More