diff --git a/composer.json b/composer.json index 1c119e9..28eccb9 100755 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "overtrue/pinyin": "^3.0", "phpoffice/phpspreadsheet": "^1.29.1", "overtrue/wechat": "^4.6", + "yansongda/pay": "^3.0", "ext-json": "*", "ext-curl": "*", "ext-pdo": "*", diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php index fe69fa6..2052022 100755 --- a/vendor/composer/InstalledVersions.php +++ b/vendor/composer/InstalledVersions.php @@ -1,831 +1,396 @@ + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ namespace Composer; use Composer\Autoload\ClassLoader; use Composer\Semver\VersionParser; - - - - - - - +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ class InstalledVersions { -private static $installed = array ( - 'root' => - array ( - 'pretty_version' => 'dev-main', - 'version' => 'dev-main', - 'aliases' => - array ( - ), - 'reference' => '5769442de60965346d4098544e88c3d20a2755fe', - 'name' => 'fastadminnet/fastadmin', - ), - 'versions' => - array ( - 'composer/pcre' => - array ( - 'pretty_version' => '3.3.2', - 'version' => '3.3.2.0', - 'aliases' => - array ( - ), - 'reference' => 'b2bed4734f0cc156ee1fe9c0da2550420d99a21e', - ), - 'easywechat-composer/easywechat-composer' => - array ( - 'pretty_version' => '1.4.1', - 'version' => '1.4.1.0', - 'aliases' => - array ( - ), - 'reference' => '3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd', - ), - 'ezyang/htmlpurifier' => - array ( - 'pretty_version' => 'v4.19.0', - 'version' => '4.19.0.0', - 'aliases' => - array ( - ), - 'reference' => 'b287d2a16aceffbf6e0295559b39662612b77fcf', - ), - 'fastadminnet/fastadmin' => - array ( - 'pretty_version' => 'dev-main', - 'version' => 'dev-main', - 'aliases' => - array ( - ), - 'reference' => '5769442de60965346d4098544e88c3d20a2755fe', - ), - 'fastadminnet/fastadmin-addons' => - array ( - 'pretty_version' => '1.4.2', - 'version' => '1.4.2.0', - 'aliases' => - array ( - ), - 'reference' => '14af178a62fb4cc897f954fa9d7d53798ad2cf37', - ), - 'fastadminnet/fastadmin-mailer' => - array ( - 'pretty_version' => 'v2.1.1', - 'version' => '2.1.1.0', - 'aliases' => - array ( - ), - 'reference' => 'bca635ac5f564ed6688d818d215021ffb0813746', - ), - 'guzzlehttp/guzzle' => - array ( - 'pretty_version' => '7.9.2', - 'version' => '7.9.2.0', - 'aliases' => - array ( - ), - 'reference' => 'd281ed313b989f213357e3be1a179f02196ac99b', - ), - 'guzzlehttp/promises' => - array ( - 'pretty_version' => '2.3.0', - 'version' => '2.3.0.0', - 'aliases' => - array ( - ), - 'reference' => '481557b130ef3790cf82b713667b43030dc9c957', - ), - 'guzzlehttp/psr7' => - array ( - 'pretty_version' => '2.8.0', - 'version' => '2.8.0.0', - 'aliases' => - array ( - ), - 'reference' => '21dc724a0583619cd1652f673303492272778051', - ), - 'maennchen/zipstream-php' => - array ( - 'pretty_version' => '2.4.0', - 'version' => '2.4.0.0', - 'aliases' => - array ( - ), - 'reference' => '3fa72e4c71a43f9e9118752a5c90e476a8dc9eb3', - ), - 'markbaker/complex' => - array ( - 'pretty_version' => '3.0.2', - 'version' => '3.0.2.0', - 'aliases' => - array ( - ), - 'reference' => '95c56caa1cf5c766ad6d65b6344b807c1e8405b9', - ), - 'markbaker/matrix' => - array ( - 'pretty_version' => '3.0.1', - 'version' => '3.0.1.0', - 'aliases' => - array ( - ), - 'reference' => '728434227fe21be27ff6d86621a1b13107a2562c', - ), - 'monolog/monolog' => - array ( - 'pretty_version' => '2.10.0', - 'version' => '2.10.0.0', - 'aliases' => - array ( - ), - 'reference' => '5cf826f2991858b54d5c3809bee745560a1042a7', - ), - 'myclabs/php-enum' => - array ( - 'pretty_version' => '1.8.4', - 'version' => '1.8.4.0', - 'aliases' => - array ( - ), - 'reference' => 'a867478eae49c9f59ece437ae7f9506bfaa27483', - ), - 'nelexa/zip' => - array ( - 'pretty_version' => '4.0.2', - 'version' => '4.0.2.0', - 'aliases' => - array ( - ), - 'reference' => '88a1b6549be813278ff2dd3b6b2ac188827634a7', - ), - 'overtrue/pinyin' => - array ( - 'pretty_version' => '3.0.6', - 'version' => '3.0.6.0', - 'aliases' => - array ( - ), - 'reference' => '3b781d267197b74752daa32814d3a2cf5d140779', - ), - 'overtrue/socialite' => - array ( - 'pretty_version' => '2.0.24', - 'version' => '2.0.24.0', - 'aliases' => - array ( - ), - 'reference' => 'ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec', - ), - 'overtrue/wechat' => - array ( - 'pretty_version' => '4.9.0', - 'version' => '4.9.0.0', - 'aliases' => - array ( - ), - 'reference' => '92791f5d957269c633b9aa175f842f6006f945b1', - ), - 'paragonie/constant_time_encoding' => - array ( - 'pretty_version' => 'v3.1.3', - 'version' => '3.1.3.0', - 'aliases' => - array ( - ), - 'reference' => 'd5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77', - ), - 'paragonie/random_compat' => - array ( - 'pretty_version' => 'v9.99.100', - 'version' => '9.99.100.0', - 'aliases' => - array ( - ), - 'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a', - ), - 'phpoffice/phpspreadsheet' => - array ( - 'pretty_version' => '1.30.1', - 'version' => '1.30.1.0', - 'aliases' => - array ( - ), - 'reference' => 'fa8257a579ec623473eabfe49731de5967306c4c', - ), - 'phpseclib/phpseclib' => - array ( - 'pretty_version' => '3.0.47', - 'version' => '3.0.47.0', - 'aliases' => - array ( - ), - 'reference' => '9d6ca36a6c2dd434765b1071b2644a1c683b385d', - ), - 'pimple/pimple' => - array ( - 'pretty_version' => 'v3.5.0', - 'version' => '3.5.0.0', - 'aliases' => - array ( - ), - 'reference' => 'a94b3a4db7fb774b3d78dad2315ddc07629e1bed', - ), - 'psr/cache' => - array ( - 'pretty_version' => '2.0.0', - 'version' => '2.0.0.0', - 'aliases' => - array ( - ), - 'reference' => '213f9dbc5b9bfbc4f8db86d2838dc968752ce13b', - ), - 'psr/cache-implementation' => - array ( - 'provided' => - array ( - 0 => '1.0|2.0', - ), - ), - 'psr/container' => - array ( - 'pretty_version' => '2.0.2', - 'version' => '2.0.2.0', - 'aliases' => - array ( - ), - 'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963', - ), - 'psr/event-dispatcher' => - array ( - 'pretty_version' => '1.0.0', - 'version' => '1.0.0.0', - 'aliases' => - array ( - ), - 'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0', - ), - 'psr/event-dispatcher-implementation' => - array ( - 'provided' => - array ( - 0 => '1.0', - ), - ), - 'psr/http-client' => - array ( - 'pretty_version' => '1.0.3', - 'version' => '1.0.3.0', - 'aliases' => - array ( - ), - 'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90', - ), - 'psr/http-client-implementation' => - array ( - 'provided' => - array ( - 0 => '1.0', - ), - ), - 'psr/http-factory' => - array ( - 'pretty_version' => '1.0.2', - 'version' => '1.0.2.0', - 'aliases' => - array ( - ), - 'reference' => 'e616d01114759c4c489f93b099585439f795fe35', - ), - 'psr/http-factory-implementation' => - array ( - 'provided' => - array ( - 0 => '1.0', - ), - ), - 'psr/http-message' => - array ( - 'pretty_version' => '1.1', - 'version' => '1.1.0.0', - 'aliases' => - array ( - ), - 'reference' => 'cb6ce4845ce34a8ad9e68117c10ee90a29919eba', - ), - 'psr/http-message-implementation' => - array ( - 'provided' => - array ( - 0 => '1.0', - ), - ), - 'psr/log' => - array ( - 'pretty_version' => '1.1.4', - 'version' => '1.1.4.0', - 'aliases' => - array ( - ), - 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', - ), - 'psr/log-implementation' => - array ( - 'provided' => - array ( - 0 => '1.0.0 || 2.0.0 || 3.0.0', - ), - ), - 'psr/simple-cache' => - array ( - 'pretty_version' => '1.0.1', - 'version' => '1.0.1.0', - 'aliases' => - array ( - ), - 'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b', - ), - 'psr/simple-cache-implementation' => - array ( - 'provided' => - array ( - 0 => '1.0|2.0', - ), - ), - 'ralouphie/getallheaders' => - array ( - 'pretty_version' => '3.0.3', - 'version' => '3.0.3.0', - 'aliases' => - array ( - ), - 'reference' => '120b605dfeb996808c31b6477290a714d356e822', - ), - 'symfony/cache' => - array ( - 'pretty_version' => 'v5.4.46', - 'version' => '5.4.46.0', - 'aliases' => - array ( - ), - 'reference' => '0fe08ee32cec2748fbfea10c52d3ee02049e0f6b', - ), - 'symfony/cache-contracts' => - array ( - 'pretty_version' => 'v2.5.4', - 'version' => '2.5.4.0', - 'aliases' => - array ( - ), - 'reference' => '517c3a3619dadfa6952c4651767fcadffb4df65e', - ), - 'symfony/cache-implementation' => - array ( - 'provided' => - array ( - 0 => '1.0|2.0', - ), - ), - 'symfony/deprecation-contracts' => - array ( - 'pretty_version' => 'v3.0.2', - 'version' => '3.0.2.0', - 'aliases' => - array ( - ), - 'reference' => '26954b3d62a6c5fd0ea8a2a00c0353a14978d05c', - ), - 'symfony/event-dispatcher' => - array ( - 'pretty_version' => 'v5.4.45', - 'version' => '5.4.45.0', - 'aliases' => - array ( - ), - 'reference' => '72982eb416f61003e9bb6e91f8b3213600dcf9e9', - ), - 'symfony/event-dispatcher-contracts' => - array ( - 'pretty_version' => 'v3.0.2', - 'version' => '3.0.2.0', - 'aliases' => - array ( - ), - 'reference' => '7bc61cc2db649b4637d331240c5346dcc7708051', - ), - 'symfony/event-dispatcher-implementation' => - array ( - 'provided' => - array ( - 0 => '2.0', - ), - ), - 'symfony/finder' => - array ( - 'pretty_version' => 'v6.0.19', - 'version' => '6.0.19.0', - 'aliases' => - array ( - ), - 'reference' => '5cc9cac6586fc0c28cd173780ca696e419fefa11', - ), - 'symfony/http-foundation' => - array ( - 'pretty_version' => 'v5.4.48', - 'version' => '5.4.48.0', - 'aliases' => - array ( - ), - 'reference' => '3f38b8af283b830e1363acd79e5bc3412d055341', - ), - 'symfony/polyfill-mbstring' => - array ( - 'pretty_version' => 'v1.32.0', - 'version' => '1.32.0.0', - 'aliases' => - array ( - ), - 'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493', - ), - 'symfony/polyfill-php73' => - array ( - 'pretty_version' => 'v1.32.0', - 'version' => '1.32.0.0', - 'aliases' => - array ( - ), - 'reference' => '0f68c03565dcaaf25a890667542e8bd75fe7e5bb', - ), - 'symfony/polyfill-php80' => - array ( - 'pretty_version' => 'v1.32.0', - 'version' => '1.32.0.0', - 'aliases' => - array ( - ), - 'reference' => '0cc9dd0f17f61d8131e7df6b84bd344899fe2608', - ), - 'symfony/psr-http-message-bridge' => - array ( - 'pretty_version' => 'v2.3.1', - 'version' => '2.3.1.0', - 'aliases' => - array ( - ), - 'reference' => '581ca6067eb62640de5ff08ee1ba6850a0ee472e', - ), - 'symfony/service-contracts' => - array ( - 'pretty_version' => 'v3.0.2', - 'version' => '3.0.2.0', - 'aliases' => - array ( - ), - 'reference' => 'd78d39c1599bd1188b8e26bb341da52c3c6d8a66', - ), - 'symfony/var-exporter' => - array ( - 'pretty_version' => 'v6.0.19', - 'version' => '6.0.19.0', - 'aliases' => - array ( - ), - 'reference' => 'df56f53818c2d5d9f683f4ad2e365ba73a3b69d2', - ), - 'topthink/framework' => - array ( - 'pretty_version' => 'dev-master', - 'version' => 'dev-master', - 'aliases' => - array ( - 0 => '9999999-dev', - ), - 'reference' => '9a2e7c2a1b6302afb61035c99c85bf0cfe0c52ec', - ), - 'topthink/think-captcha' => - array ( - 'pretty_version' => 'v1.0.9', - 'version' => '1.0.9.0', - 'aliases' => - array ( - ), - 'reference' => '9be9dd7e61c7fa3c478c4b92910d7230b94d0d23', - ), - 'topthink/think-helper' => - array ( - 'pretty_version' => 'v1.0.7', - 'version' => '1.0.7.0', - 'aliases' => - array ( - ), - 'reference' => '5f92178606c8ce131d36b37a57c58eb71e55f019', - ), - 'topthink/think-installer' => - array ( - 'pretty_version' => 'v1.0.14', - 'version' => '1.0.14.0', - 'aliases' => - array ( - ), - 'reference' => 'eae1740ac264a55c06134b6685dfb9f837d004d1', - ), - 'topthink/think-queue' => - array ( - 'pretty_version' => 'v1.1.6', - 'version' => '1.1.6.0', - 'aliases' => - array ( - ), - 'reference' => '250650eb0e8ea5af4cfdc7ae46f3f4e0a24ac245', - ), - 'workerman/channel' => - array ( - 'pretty_version' => 'v1.2.3', - 'version' => '1.2.3.0', - 'aliases' => - array ( - ), - 'reference' => '5edb0008eae35bf2da7218d911042abd23aa4370', - ), - 'workerman/phpsocket.io' => - array ( - 'pretty_version' => 'v2.2.0', - 'version' => '2.2.0.0', - 'aliases' => - array ( - ), - 'reference' => '0ba306b380e016f447f9860db95fcc1c7553fb91', - ), - 'workerman/workerman' => - array ( - 'pretty_version' => 'v4.2.1', - 'version' => '4.2.1.0', - 'aliases' => - array ( - ), - 'reference' => 'cafb5a43d93d7d30a16b32a57948581cca993562', - ), - ), -); -private static $canGetVendors; -private static $installedByVendor = array(); + /** + * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to + * @internal + */ + private static $selfDir = null; + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + /** + * @var bool + */ + private static $installedIsLocalDir; + /** + * @var bool|null + */ + private static $canGetVendors; + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + if (1 === \count($packages)) { + return $packages[0]; + } -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))); -} - - - - - - - - - -public static function isInstalled($packageName) -{ -foreach (self::getInstalled() as $installed) { -if (isset($installed['versions'][$packageName])) { -return true; -} -} - -return false; -} - - - - - - - - - - - - - - -public static function satisfies(VersionParser $parser, $packageName, $constraint) -{ -$constraint = $parser->parseConstraints($constraint); -$provided = $parser->parseConstraints(self::getVersionRanges($packageName)); - -return $provided->matches($constraint); -} - - - - - - - - - - -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'); -} - - - - - -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'); -} - - - - - -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'); -} - - - - - -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'); -} - - - - - -public static function getRootPackage() -{ -$installed = self::getInstalled(); - -return $installed[0]['root']; -} - - - - - - - - -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); - -return self::$installed; -} - - - - - - - -public static function getAllRawData() -{ -return self::getInstalled(); -} - - - - - - - - - - - - - - - - - - - -public static function reload($data) -{ -self::$installed = $data; -self::$installedByVendor = array(); -} - - - - - -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')) { -$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; -} -} -} - -$installed[] = self::$installed; - -return $installed; -} + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + + // when using reload, we disable the duplicate protection to ensure that self::$installed data is + // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, + // so we have to assume it does not, and that may result in duplicate data being returned when listing + // all installed packages for example + self::$installedIsLocalDir = false; + } + + /** + * @return string + */ + private static function getSelfDir() + { + if (self::$selfDir === null) { + self::$selfDir = strtr(__DIR__, '\\', '/'); + } + + return self::$selfDir; + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + $copiedLocalDir = false; + + if (self::$canGetVendors) { + $selfDir = self::getSelfDir(); + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + $vendorDir = strtr($vendorDir, '\\', '/'); + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + self::$installedByVendor[$vendorDir] = $required; + $installed[] = $required; + if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { + self::$installed = $required; + self::$installedIsLocalDir = true; + } + } + if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { + $copiedLocalDir = true; + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array() && !$copiedLocalDir) { + $installed[] = self::$installed; + } + + return $installed; + } } diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 9c3f371..f37ff5e 100755 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -199,30 +199,24 @@ }, { "name": "fastadminnet/fastadmin-addons", - "version": "1.4.2", - "version_normalized": "1.4.2.0", + "version": "1.4.3", + "version_normalized": "1.4.3.0", "source": { "type": "git", "url": "https://github.com/fastadminnet/fastadmin-addons.git", - "reference": "14af178a62fb4cc897f954fa9d7d53798ad2cf37" + "reference": "b7e371254f97fae7e9232984a746ffa58b64504e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fastadminnet/fastadmin-addons/zipball/14af178a62fb4cc897f954fa9d7d53798ad2cf37", - "reference": "14af178a62fb4cc897f954fa9d7d53798ad2cf37", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "url": "https://api.github.com/repos/fastadminnet/fastadmin-addons/zipball/b7e371254f97fae7e9232984a746ffa58b64504e", + "reference": "b7e371254f97fae7e9232984a746ffa58b64504e", + "shasum": "" }, "require": { "nelexa/zip": "^3.3 || ^4.0", - "php": ">=7.0.0" + "php": ">=7.1.0" }, - "time": "2025-06-03T03:09:28+00:00", + "time": "2025-06-17T03:35:34+00:00", "type": "library", "extra": { "think-config": { @@ -256,7 +250,7 @@ "homepage": "https://github.com/fastadminnet/fastadmin-addons", "support": { "issues": "https://github.com/fastadminnet/fastadmin-addons/issues", - "source": "https://github.com/fastadminnet/fastadmin-addons/tree/v1.4.2" + "source": "https://github.com/fastadminnet/fastadmin-addons/tree/v1.4.3" }, "install-path": "../fastadminnet/fastadmin-addons" }, @@ -320,29 +314,23 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.9.2", - "version_normalized": "7.9.2.0", + "version": "7.10.0", + "version_normalized": "7.10.0.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "d281ed313b989f213357e3be1a179f02196ac99b" + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", - "reference": "d281ed313b989f213357e3be1a179f02196ac99b", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.3", - "guzzlehttp/psr7": "^2.7.0", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -363,7 +351,7 @@ "ext-intl": "Required for Internationalized Domain Name (IDN) support", "psr/log": "Required for using the Log middleware" }, - "time": "2024-07-24T11:22:20+00:00", + "time": "2025-08-23T22:36:01+00:00", "type": "library", "extra": { "bamarni-bin": { @@ -435,7 +423,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.9.2" + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" }, "funding": [ { @@ -660,41 +648,39 @@ }, { "name": "maennchen/zipstream-php", - "version": "2.4.0", - "version_normalized": "2.4.0.0", + "version": "3.1.2", + "version_normalized": "3.1.2.0", "source": { "type": "git", "url": "https://github.com/maennchen/ZipStream-PHP.git", - "reference": "3fa72e4c71a43f9e9118752a5c90e476a8dc9eb3" + "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/3fa72e4c71a43f9e9118752a5c90e476a8dc9eb3", - "reference": "3fa72e4c71a43f9e9118752a5c90e476a8dc9eb3", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f", + "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f", + "shasum": "" }, "require": { "ext-mbstring": "*", - "myclabs/php-enum": "^1.5", - "php": "^8.0", - "psr/http-message": "^1.0" + "ext-zlib": "*", + "php-64bit": "^8.2" }, "require-dev": { + "brianium/paratest": "^7.7", "ext-zip": "*", - "friendsofphp/php-cs-fixer": "^3.9", - "guzzlehttp/guzzle": "^6.5.3 || ^7.2.0", + "friendsofphp/php-cs-fixer": "^3.16", + "guzzlehttp/guzzle": "^7.5", "mikey179/vfsstream": "^1.6", - "php-coveralls/php-coveralls": "^2.4", - "phpunit/phpunit": "^8.5.8 || ^9.4.2", - "vimeo/psalm": "^5.0" + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^11.0", + "vimeo/psalm": "^6.0" }, - "time": "2022-12-08T12:29:14+00:00", + "suggest": { + "guzzlehttp/psr7": "^2.4", + "psr/http-message": "^2.0" + }, + "time": "2025-01-27T12:07:53+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -731,16 +717,12 @@ ], "support": { "issues": "https://github.com/maennchen/ZipStream-PHP/issues", - "source": "https://github.com/maennchen/ZipStream-PHP/tree/2.4.0" + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2" }, "funding": [ { "url": "https://github.com/maennchen", "type": "github" - }, - { - "url": "https://opencollective.com/zipstream", - "type": "open_collective" } ], "install-path": "../maennchen/zipstream-php" @@ -963,78 +945,6 @@ ], "install-path": "../monolog/monolog" }, - { - "name": "myclabs/php-enum", - "version": "1.8.4", - "version_normalized": "1.8.4.0", - "source": { - "type": "git", - "url": "https://github.com/myclabs/php-enum.git", - "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/php-enum/zipball/a867478eae49c9f59ece437ae7f9506bfaa27483", - "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] - }, - "require": { - "ext-json": "*", - "php": "^7.3 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.5", - "squizlabs/php_codesniffer": "1.*", - "vimeo/psalm": "^4.6.2" - }, - "time": "2022-08-04T09:53:51+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "MyCLabs\\Enum\\": "src/" - }, - "classmap": [ - "stubs/Stringable.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP Enum contributors", - "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" - } - ], - "description": "PHP Enum implementation", - "homepage": "http://github.com/myclabs/php-enum", - "keywords": [ - "enum" - ], - "support": { - "issues": "https://github.com/myclabs/php-enum/issues", - "source": "https://github.com/myclabs/php-enum/tree/1.8.4" - }, - "funding": [ - { - "url": "https://github.com/mnapoli", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", - "type": "tidelift" - } - ], - "install-path": "../myclabs/php-enum" - }, { "name": "nelexa/zip", "version": "4.0.2", @@ -1435,24 +1345,18 @@ }, { "name": "phpoffice/phpspreadsheet", - "version": "1.30.1", - "version_normalized": "1.30.1.0", + "version": "1.30.0", + "version_normalized": "1.30.0.0", "source": { "type": "git", "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", - "reference": "fa8257a579ec623473eabfe49731de5967306c4c" + "reference": "2f39286e0136673778b7a142b3f0d141e43d1714" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fa8257a579ec623473eabfe49731de5967306c4c", - "reference": "fa8257a579ec623473eabfe49731de5967306c4c", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/2f39286e0136673778b7a142b3f0d141e43d1714", + "reference": "2f39286e0136673778b7a142b3f0d141e43d1714", + "shasum": "" }, "require": { "composer/pcre": "^1||^2||^3", @@ -1473,7 +1377,7 @@ "maennchen/zipstream-php": "^2.1 || ^3.0", "markbaker/complex": "^3.0", "markbaker/matrix": "^3.0", - "php": ">=7.4.0 <8.5.0", + "php": "^7.4 || ^8.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" @@ -1498,7 +1402,7 @@ "mpdf/mpdf": "Option for rendering PDF with PDF Writer", "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" }, - "time": "2025-10-26T16:01:04+00:00", + "time": "2025-08-10T06:28:02+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1544,7 +1448,7 @@ ], "support": { "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", - "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.1" + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.0" }, "install-path": "../phpoffice/phpspreadsheet" }, @@ -1935,30 +1839,24 @@ }, { "name": "psr/http-factory", - "version": "1.0.2", - "version_normalized": "1.0.2.0", + "version": "1.1.0", + "version_normalized": "1.1.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "e616d01114759c4c489f93b099585439f795fe35" + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", - "reference": "e616d01114759c4c489f93b099585439f795fe35", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" }, "require": { - "php": ">=7.0.0", + "php": ">=7.1", "psr/http-message": "^1.0 || ^2.0" }, - "time": "2023-04-10T20:10:41+00:00", + "time": "2024-04-15T12:06:14+00:00", "type": "library", "extra": { "branch-alias": { @@ -1981,7 +1879,7 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interfaces for PSR-7 HTTP message factories", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ "factory", "http", @@ -1993,39 +1891,33 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + "source": "https://github.com/php-fig/http-factory" }, "install-path": "../psr/http-factory" }, { "name": "psr/http-message", - "version": "1.1", - "version_normalized": "1.1.0.0", + "version": "2.0", + "version_normalized": "2.0.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", - "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "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:50:52+00:00", + "time": "2023-04-04T09:54:51+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "2.0.x-dev" } }, "installation-source": "dist", @@ -2041,7 +1933,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP messages", @@ -2055,7 +1947,7 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/1.1" + "source": "https://github.com/php-fig/http-message/tree/2.0" }, "install-path": "../psr/http-message" }, @@ -2397,29 +2289,23 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.0.2", - "version_normalized": "3.0.2.0", + "version": "v3.6.0", + "version_normalized": "3.6.0.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=8.1" }, - "time": "2022-01-02T09:55:41+00:00", + "time": "2024-09-25T14:21:43+00:00", "type": "library", "extra": { "thanks": { @@ -2427,7 +2313,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.6-dev" } }, "installation-source": "dist", @@ -2453,7 +2339,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.2" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -2561,33 +2447,24 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.0.2", - "version_normalized": "3.0.2.0", + "version": "v3.6.0", + "version_normalized": "3.6.0.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "7bc61cc2db649b4637d331240c5346dcc7708051" + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7bc61cc2db649b4637d331240c5346dcc7708051", - "reference": "7bc61cc2db649b4637d331240c5346dcc7708051", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "shasum": "" }, "require": { - "php": ">=8.0.2", + "php": ">=8.1", "psr/event-dispatcher": "^1" }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, - "time": "2022-01-02T09:55:41+00:00", + "time": "2024-09-25T14:21:43+00:00", "type": "library", "extra": { "thanks": { @@ -2595,7 +2472,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.6-dev" } }, "installation-source": "dist", @@ -2629,7 +2506,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.0.2" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" }, "funding": [ { @@ -2649,29 +2526,26 @@ }, { "name": "symfony/finder", - "version": "v6.0.19", - "version_normalized": "6.0.19.0", + "version": "v7.3.2", + "version_normalized": "7.3.2.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "5cc9cac6586fc0c28cd173780ca696e419fefa11" + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/5cc9cac6586fc0c28cd173780ca696e419fefa11", - "reference": "5cc9cac6586fc0c28cd173780ca696e419fefa11", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", + "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=8.2" }, - "time": "2023-01-20T17:44:14+00:00", + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "time": "2025-07-15T13:41:35+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2699,7 +2573,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.0.19" + "source": "https://github.com/symfony/finder/tree/v7.3.2" }, "funding": [ { @@ -2710,6 +2584,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -2798,8 +2676,8 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.32.0", - "version_normalized": "1.32.0.0", + "version": "v1.33.0", + "version_normalized": "1.33.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -2809,13 +2687,7 @@ "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "shasum": "" }, "require": { "ext-iconv": "*", @@ -2868,7 +2740,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -2879,6 +2751,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -2888,8 +2764,8 @@ }, { "name": "symfony/polyfill-php73", - "version": "v1.32.0", - "version_normalized": "1.32.0.0", + "version": "v1.33.0", + "version_normalized": "1.33.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", @@ -2899,13 +2775,7 @@ "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "shasum": "" }, "require": { "php": ">=7.2" @@ -2953,7 +2823,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0" }, "funding": [ { @@ -2964,6 +2834,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -2973,8 +2847,8 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.32.0", - "version_normalized": "1.32.0.0", + "version": "v1.33.0", + "version_normalized": "1.33.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", @@ -2984,13 +2858,7 @@ "type": "zip", "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "shasum": "" }, "require": { "php": ">=7.2" @@ -3042,7 +2910,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -3053,6 +2921,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -3154,36 +3026,28 @@ }, { "name": "symfony/service-contracts", - "version": "v3.0.2", - "version_normalized": "3.0.2.0", + "version": "v3.6.0", + "version_normalized": "3.6.0.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "d78d39c1599bd1188b8e26bb341da52c3c6d8a66" + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d78d39c1599bd1188b8e26bb341da52c3c6d8a66", - "reference": "d78d39c1599bd1188b8e26bb341da52c3c6d8a66", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "shasum": "" }, "require": { - "php": ">=8.0.2", - "psr/container": "^2.0" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "ext-psr": "<1.1|>=2" }, - "suggest": { - "symfony/service-implementation": "" - }, - "time": "2022-05-30T19:17:58+00:00", + "time": "2025-04-25T09:37:31+00:00", "type": "library", "extra": { "thanks": { @@ -3191,14 +3055,17 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.6-dev" } }, "installation-source": "dist", "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3225,7 +3092,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.0.2" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" }, "funding": [ { @@ -3245,32 +3112,29 @@ }, { "name": "symfony/var-exporter", - "version": "v6.0.19", - "version_normalized": "6.0.19.0", + "version": "v6.4.26", + "version_normalized": "6.4.26.0", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "df56f53818c2d5d9f683f4ad2e365ba73a3b69d2" + "reference": "466fcac5fa2e871f83d31173f80e9c2684743bfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/df56f53818c2d5d9f683f4ad2e365ba73a3b69d2", - "reference": "df56f53818c2d5d9f683f4ad2e365ba73a3b69d2", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/466fcac5fa2e871f83d31173f80e9c2684743bfc", + "reference": "466fcac5fa2e871f83d31173f80e9c2684743bfc", + "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { - "symfony/var-dumper": "^5.4|^6.0" + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, - "time": "2023-01-13T08:34:10+00:00", + "time": "2025-09-11T09:57:09+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -3303,10 +3167,12 @@ "export", "hydrate", "instantiate", + "lazy-loading", + "proxy", "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.0.19" + "source": "https://github.com/symfony/var-exporter/tree/v6.4.26" }, "funding": [ { @@ -3317,6 +3183,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -3602,24 +3472,18 @@ }, { "name": "workerman/phpsocket.io", - "version": "v2.2.0", - "version_normalized": "2.2.0.0", + "version": "v2.2.2", + "version_normalized": "2.2.2.0", "source": { "type": "git", "url": "https://github.com/walkor/phpsocket.io.git", - "reference": "0ba306b380e016f447f9860db95fcc1c7553fb91" + "reference": "5f96eace9f2bcec82555a97ac9867b732024d3b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/walkor/phpsocket.io/zipball/0ba306b380e016f447f9860db95fcc1c7553fb91", - "reference": "0ba306b380e016f447f9860db95fcc1c7553fb91", - "shasum": "", - "mirrors": [ - { - "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", - "preferred": true - } - ] + "url": "https://api.github.com/repos/walkor/phpsocket.io/zipball/5f96eace9f2bcec82555a97ac9867b732024d3b6", + "reference": "5f96eace9f2bcec82555a97ac9867b732024d3b6", + "shasum": "" }, "require": { "ext-json": "*", @@ -3629,7 +3493,7 @@ "require-dev": { "squizlabs/php_codesniffer": "^3.7" }, - "time": "2025-03-24T17:14:03+00:00", + "time": "2025-04-01T08:36:37+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -3655,7 +3519,7 @@ ], "support": { "issues": "https://github.com/walkor/phpsocket.io/issues", - "source": "https://github.com/walkor/phpsocket.io/tree/v2.2.0" + "source": "https://github.com/walkor/phpsocket.io/tree/v2.2.2" }, "funding": [ { @@ -3740,6 +3604,223 @@ } ], "install-path": "../workerman/workerman" + }, + { + "name": "yansongda/artful", + "version": "v1.1.3", + "version_normalized": "1.1.3.0", + "source": { + "type": "git", + "url": "https://github.com/yansongda/artful.git", + "reference": "ddc203ef34ab369a5a31df057a0fda697d3ed855" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yansongda/artful/zipball/ddc203ef34ab369a5a31df057a0fda697d3ed855", + "reference": "ddc203ef34ab369a5a31df057a0fda697d3ed855", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^2.6", + "php": ">=8.0", + "psr/container": "^1.1 || ^2.0", + "psr/event-dispatcher": "^1.0", + "psr/http-client": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "yansongda/supports": "~4.0.10" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.44", + "guzzlehttp/guzzle": "^7.0", + "hyperf/pimple": "^2.2", + "mockery/mockery": "^1.4", + "monolog/monolog": "^2.2", + "phpstan/phpstan": "^1.0.0 || ^2.0.0", + "phpunit/phpunit": "^9.0", + "symfony/event-dispatcher": "^5.2.0", + "symfony/http-foundation": "^5.2.0", + "symfony/psr-http-message-bridge": "^2.1", + "symfony/var-dumper": "^5.1" + }, + "suggest": { + "hyperf/pimple": "其它/无框架下使用 SDK,请安装,任选其一", + "illuminate/container": "其它/无框架下使用 SDK,请安装,任选其一" + }, + "time": "2025-07-24T09:39:17+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Yansongda\\Artful\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "yansongda", + "email": "me@yansongda.cn" + } + ], + "description": "Artful 是一个简单易用的 API 请求框架 PHP Api RequesT Framwork U Like。", + "keywords": [ + "api", + "artful", + "framework", + "request" + ], + "support": { + "homepage": "https://artful.yansongda.cn", + "issues": "https://github.com/yansongda/artful/issues", + "source": "https://github.com/yansongda/artful" + }, + "install-path": "../yansongda/artful" + }, + { + "name": "yansongda/pay", + "version": "v3.7.18", + "version_normalized": "3.7.18.0", + "source": { + "type": "git", + "url": "https://github.com/yansongda/pay.git", + "reference": "813c01e7abed94d2c5ac1a3abdfb87316d78c276" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yansongda/pay/zipball/813c01e7abed94d2c5ac1a3abdfb87316d78c276", + "reference": "813c01e7abed94d2c5ac1a3abdfb87316d78c276", + "shasum": "" + }, + "require": { + "ext-bcmath": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-openssl": "*", + "ext-simplexml": "*", + "php": ">=8.0", + "yansongda/artful": "~1.1.3", + "yansongda/supports": "~4.0.10" + }, + "conflict": { + "hyperf/framework": "<3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.44", + "guzzlehttp/guzzle": "^7.0", + "hyperf/pimple": "^2.2", + "jetbrains/phpstorm-attributes": "^1.1", + "mockery/mockery": "^1.4", + "monolog/monolog": "^2.2", + "phpstan/phpstan": "^1.0.0 || ^2.0.0", + "phpunit/phpunit": "^9.0", + "symfony/event-dispatcher": "^5.2.0", + "symfony/http-foundation": "^5.2.0", + "symfony/psr-http-message-bridge": "^2.1", + "symfony/var-dumper": "^5.1" + }, + "time": "2025-08-13T14:28:14+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Yansongda\\Pay\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "yansongda", + "email": "me@yansongda.cn" + } + ], + "description": "可能是我用过的最优雅的 Alipay 和 WeChat 的支付 SDK 扩展包了", + "keywords": [ + "alipay", + "pay", + "wechat" + ], + "support": { + "homepage": "https://pay.yansongda.cn", + "issues": "https://github.com/yansongda/pay/issues", + "source": "https://github.com/yansongda/pay" + }, + "install-path": "../yansongda/pay" + }, + { + "name": "yansongda/supports", + "version": "v4.0.12", + "version_normalized": "4.0.12.0", + "source": { + "type": "git", + "url": "https://github.com/yansongda/supports.git", + "reference": "308de376d20cb1cd4f959644793e0582ccd1ef6d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yansongda/supports/zipball/308de376d20cb1cd4f959644793e0582ccd1ef6d", + "reference": "308de376d20cb1cd4f959644793e0582ccd1ef6d", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-mbstring": "*", + "php": ">=8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.0", + "mockery/mockery": "^1.4", + "phpstan/phpstan": "^1.1.0", + "phpunit/phpunit": "^9.0" + }, + "suggest": { + "monolog/monolog": "Use logger", + "symfony/console": "Use stdout logger" + }, + "time": "2025-01-08T08:55:20+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Yansongda\\Supports\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "yansongda", + "email": "me@yansongda.cn" + } + ], + "description": "common components", + "keywords": [ + "array", + "collection", + "config", + "support" + ], + "support": { + "issues": "https://github.com/yansongda/supports/issues", + "source": "https://github.com/yansongda/supports" + }, + "install-path": "../yansongda/supports" } ], "dev": true, diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 669647c..a32e762 100755 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -1,556 +1,565 @@ - - array ( - 'pretty_version' => 'dev-main', - 'version' => 'dev-main', - 'aliases' => - array ( + array( + 'name' => 'fastadminnet/fastadmin', + 'pretty_version' => 'dev-main', + 'version' => 'dev-main', + 'reference' => '34d47f8d9b30ecf836fed3387e6c38b7010e9c8c', + 'type' => 'project', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, ), - 'reference' => '5769442de60965346d4098544e88c3d20a2755fe', - 'name' => 'fastadminnet/fastadmin', - ), - 'versions' => - array ( - 'composer/pcre' => - array ( - 'pretty_version' => '3.3.2', - 'version' => '3.3.2.0', - 'aliases' => - array ( - ), - 'reference' => 'b2bed4734f0cc156ee1fe9c0da2550420d99a21e', + 'versions' => array( + 'composer/pcre' => array( + 'pretty_version' => '3.3.2', + 'version' => '3.3.2.0', + 'reference' => 'b2bed4734f0cc156ee1fe9c0da2550420d99a21e', + 'type' => 'library', + 'install_path' => __DIR__ . '/./pcre', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'easywechat-composer/easywechat-composer' => array( + 'pretty_version' => '1.4.1', + 'version' => '1.4.1.0', + 'reference' => '3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd', + 'type' => 'composer-plugin', + 'install_path' => __DIR__ . '/../easywechat-composer/easywechat-composer', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'ezyang/htmlpurifier' => array( + 'pretty_version' => 'v4.19.0', + 'version' => '4.19.0.0', + 'reference' => 'b287d2a16aceffbf6e0295559b39662612b77fcf', + 'type' => 'library', + 'install_path' => __DIR__ . '/../ezyang/htmlpurifier', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'fastadminnet/fastadmin' => array( + 'pretty_version' => 'dev-main', + 'version' => 'dev-main', + 'reference' => '34d47f8d9b30ecf836fed3387e6c38b7010e9c8c', + 'type' => 'project', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'fastadminnet/fastadmin-addons' => array( + 'pretty_version' => '1.4.3', + 'version' => '1.4.3.0', + 'reference' => 'b7e371254f97fae7e9232984a746ffa58b64504e', + 'type' => 'library', + 'install_path' => __DIR__ . '/../fastadminnet/fastadmin-addons', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'fastadminnet/fastadmin-mailer' => array( + 'pretty_version' => 'v2.1.1', + 'version' => '2.1.1.0', + 'reference' => 'bca635ac5f564ed6688d818d215021ffb0813746', + 'type' => 'library', + 'install_path' => __DIR__ . '/../fastadminnet/fastadmin-mailer', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'guzzlehttp/guzzle' => array( + 'pretty_version' => '7.10.0', + 'version' => '7.10.0.0', + 'reference' => 'b51ac707cfa420b7bfd4e4d5e510ba8008e822b4', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/guzzle', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'guzzlehttp/promises' => array( + 'pretty_version' => '2.3.0', + 'version' => '2.3.0.0', + 'reference' => '481557b130ef3790cf82b713667b43030dc9c957', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/promises', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'guzzlehttp/psr7' => array( + 'pretty_version' => '2.8.0', + 'version' => '2.8.0.0', + 'reference' => '21dc724a0583619cd1652f673303492272778051', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/psr7', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'maennchen/zipstream-php' => array( + 'pretty_version' => '3.1.2', + 'version' => '3.1.2.0', + 'reference' => 'aeadcf5c412332eb426c0f9b4485f6accba2a99f', + 'type' => 'library', + 'install_path' => __DIR__ . '/../maennchen/zipstream-php', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'markbaker/complex' => array( + 'pretty_version' => '3.0.2', + 'version' => '3.0.2.0', + 'reference' => '95c56caa1cf5c766ad6d65b6344b807c1e8405b9', + 'type' => 'library', + 'install_path' => __DIR__ . '/../markbaker/complex', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'markbaker/matrix' => array( + 'pretty_version' => '3.0.1', + 'version' => '3.0.1.0', + 'reference' => '728434227fe21be27ff6d86621a1b13107a2562c', + 'type' => 'library', + 'install_path' => __DIR__ . '/../markbaker/matrix', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'monolog/monolog' => array( + 'pretty_version' => '2.10.0', + 'version' => '2.10.0.0', + 'reference' => '5cf826f2991858b54d5c3809bee745560a1042a7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../monolog/monolog', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'nelexa/zip' => array( + 'pretty_version' => '4.0.2', + 'version' => '4.0.2.0', + 'reference' => '88a1b6549be813278ff2dd3b6b2ac188827634a7', + 'type' => 'library', + 'install_path' => __DIR__ . '/../nelexa/zip', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'overtrue/pinyin' => array( + 'pretty_version' => '3.0.6', + 'version' => '3.0.6.0', + 'reference' => '3b781d267197b74752daa32814d3a2cf5d140779', + 'type' => 'library', + 'install_path' => __DIR__ . '/../overtrue/pinyin', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'overtrue/socialite' => array( + 'pretty_version' => '2.0.24', + 'version' => '2.0.24.0', + 'reference' => 'ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec', + 'type' => 'library', + 'install_path' => __DIR__ . '/../overtrue/socialite', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'overtrue/wechat' => array( + 'pretty_version' => '4.9.0', + 'version' => '4.9.0.0', + 'reference' => '92791f5d957269c633b9aa175f842f6006f945b1', + 'type' => 'library', + 'install_path' => __DIR__ . '/../overtrue/wechat', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'paragonie/constant_time_encoding' => array( + 'pretty_version' => 'v3.1.3', + 'version' => '3.1.3.0', + 'reference' => 'd5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77', + 'type' => 'library', + 'install_path' => __DIR__ . '/../paragonie/constant_time_encoding', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'paragonie/random_compat' => array( + 'pretty_version' => 'v9.99.100', + 'version' => '9.99.100.0', + 'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../paragonie/random_compat', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'phpoffice/phpspreadsheet' => array( + 'pretty_version' => '1.30.0', + 'version' => '1.30.0.0', + 'reference' => '2f39286e0136673778b7a142b3f0d141e43d1714', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpoffice/phpspreadsheet', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'phpseclib/phpseclib' => array( + 'pretty_version' => '3.0.47', + 'version' => '3.0.47.0', + 'reference' => '9d6ca36a6c2dd434765b1071b2644a1c683b385d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpseclib/phpseclib', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'pimple/pimple' => array( + 'pretty_version' => 'v3.5.0', + 'version' => '3.5.0.0', + 'reference' => 'a94b3a4db7fb774b3d78dad2315ddc07629e1bed', + 'type' => 'library', + 'install_path' => __DIR__ . '/../pimple/pimple', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/cache' => array( + 'pretty_version' => '2.0.0', + 'version' => '2.0.0.0', + 'reference' => '213f9dbc5b9bfbc4f8db86d2838dc968752ce13b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/cache', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/cache-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0|2.0', + ), + ), + 'psr/container' => array( + 'pretty_version' => '2.0.2', + 'version' => '2.0.2.0', + 'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/container', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/event-dispatcher' => array( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/event-dispatcher', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/event-dispatcher-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + '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', + ), + ), + 'psr/log' => array( + 'pretty_version' => '1.1.4', + 'version' => '1.1.4.0', + 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/log', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/log-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0.0 || 2.0.0 || 3.0.0', + ), + ), + 'psr/simple-cache' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/simple-cache', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/simple-cache-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0|2.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, + ), + 'symfony/cache' => array( + 'pretty_version' => 'v5.4.46', + 'version' => '5.4.46.0', + 'reference' => '0fe08ee32cec2748fbfea10c52d3ee02049e0f6b', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/cache', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/cache-contracts' => array( + 'pretty_version' => 'v2.5.4', + 'version' => '2.5.4.0', + 'reference' => '517c3a3619dadfa6952c4651767fcadffb4df65e', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/cache-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/cache-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0|2.0', + ), + ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v3.6.0', + 'version' => '3.6.0.0', + 'reference' => '63afe740e99a13ba87ec199bb07bbdee937a5b62', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/event-dispatcher' => array( + 'pretty_version' => 'v5.4.45', + 'version' => '5.4.45.0', + 'reference' => '72982eb416f61003e9bb6e91f8b3213600dcf9e9', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/event-dispatcher', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/event-dispatcher-contracts' => array( + 'pretty_version' => 'v3.6.0', + 'version' => '3.6.0.0', + 'reference' => '59eb412e93815df44f05f342958efa9f46b1e586', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/event-dispatcher-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '2.0', + ), + ), + 'symfony/finder' => array( + 'pretty_version' => 'v7.3.2', + 'version' => '7.3.2.0', + 'reference' => '2a6614966ba1074fa93dae0bc804227422df4dfe', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/finder', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/http-foundation' => array( + 'pretty_version' => 'v5.4.48', + 'version' => '5.4.48.0', + 'reference' => '3f38b8af283b830e1363acd79e5bc3412d055341', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/http-foundation', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-mbstring' => array( + 'pretty_version' => 'v1.33.0', + 'version' => '1.33.0.0', + 'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-php73' => array( + 'pretty_version' => 'v1.33.0', + 'version' => '1.33.0.0', + 'reference' => '0f68c03565dcaaf25a890667542e8bd75fe7e5bb', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php73', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-php80' => array( + 'pretty_version' => 'v1.33.0', + 'version' => '1.33.0.0', + 'reference' => '0cc9dd0f17f61d8131e7df6b84bd344899fe2608', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php80', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/psr-http-message-bridge' => array( + 'pretty_version' => 'v2.3.1', + 'version' => '2.3.1.0', + 'reference' => '581ca6067eb62640de5ff08ee1ba6850a0ee472e', + 'type' => 'symfony-bridge', + 'install_path' => __DIR__ . '/../symfony/psr-http-message-bridge', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/service-contracts' => array( + 'pretty_version' => 'v3.6.0', + 'version' => '3.6.0.0', + 'reference' => 'f021b05a130d35510bd6b25fe9053c2a8a15d5d4', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/service-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/var-exporter' => array( + 'pretty_version' => 'v6.4.26', + 'version' => '6.4.26.0', + 'reference' => '466fcac5fa2e871f83d31173f80e9c2684743bfc', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/var-exporter', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'topthink/framework' => array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => '9a2e7c2a1b6302afb61035c99c85bf0cfe0c52ec', + 'type' => 'think-framework', + 'install_path' => __DIR__ . '/../../thinkphp', + 'aliases' => array( + 0 => '9999999-dev', + ), + 'dev_requirement' => false, + ), + 'topthink/think-captcha' => array( + 'pretty_version' => 'v1.0.9', + 'version' => '1.0.9.0', + 'reference' => '9be9dd7e61c7fa3c478c4b92910d7230b94d0d23', + 'type' => 'library', + 'install_path' => __DIR__ . '/../topthink/think-captcha', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'topthink/think-helper' => array( + 'pretty_version' => 'v1.0.7', + 'version' => '1.0.7.0', + 'reference' => '5f92178606c8ce131d36b37a57c58eb71e55f019', + 'type' => 'library', + 'install_path' => __DIR__ . '/../topthink/think-helper', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'topthink/think-installer' => array( + 'pretty_version' => 'v1.0.14', + 'version' => '1.0.14.0', + 'reference' => 'eae1740ac264a55c06134b6685dfb9f837d004d1', + 'type' => 'composer-plugin', + 'install_path' => __DIR__ . '/../topthink/think-installer', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'topthink/think-queue' => array( + 'pretty_version' => 'v1.1.6', + 'version' => '1.1.6.0', + 'reference' => '250650eb0e8ea5af4cfdc7ae46f3f4e0a24ac245', + 'type' => 'think-extend', + 'install_path' => __DIR__ . '/../topthink/think-queue', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'workerman/channel' => array( + 'pretty_version' => 'v1.2.3', + 'version' => '1.2.3.0', + 'reference' => '5edb0008eae35bf2da7218d911042abd23aa4370', + 'type' => 'library', + 'install_path' => __DIR__ . '/../workerman/channel', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'workerman/phpsocket.io' => array( + 'pretty_version' => 'v2.2.2', + 'version' => '2.2.2.0', + 'reference' => '5f96eace9f2bcec82555a97ac9867b732024d3b6', + 'type' => 'library', + 'install_path' => __DIR__ . '/../workerman/phpsocket.io', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'workerman/workerman' => array( + 'pretty_version' => 'v4.2.1', + 'version' => '4.2.1.0', + 'reference' => 'cafb5a43d93d7d30a16b32a57948581cca993562', + 'type' => 'library', + 'install_path' => __DIR__ . '/../workerman/workerman', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'yansongda/artful' => array( + 'pretty_version' => 'v1.1.3', + 'version' => '1.1.3.0', + 'reference' => 'ddc203ef34ab369a5a31df057a0fda697d3ed855', + 'type' => 'library', + 'install_path' => __DIR__ . '/../yansongda/artful', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'yansongda/pay' => array( + 'pretty_version' => 'v3.7.18', + 'version' => '3.7.18.0', + 'reference' => '813c01e7abed94d2c5ac1a3abdfb87316d78c276', + 'type' => 'library', + 'install_path' => __DIR__ . '/../yansongda/pay', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'yansongda/supports' => array( + 'pretty_version' => 'v4.0.12', + 'version' => '4.0.12.0', + 'reference' => '308de376d20cb1cd4f959644793e0582ccd1ef6d', + 'type' => 'library', + 'install_path' => __DIR__ . '/../yansongda/supports', + 'aliases' => array(), + 'dev_requirement' => false, + ), ), - 'easywechat-composer/easywechat-composer' => - array ( - 'pretty_version' => '1.4.1', - 'version' => '1.4.1.0', - 'aliases' => - array ( - ), - 'reference' => '3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd', - ), - 'ezyang/htmlpurifier' => - array ( - 'pretty_version' => 'v4.19.0', - 'version' => '4.19.0.0', - 'aliases' => - array ( - ), - 'reference' => 'b287d2a16aceffbf6e0295559b39662612b77fcf', - ), - 'fastadminnet/fastadmin' => - array ( - 'pretty_version' => 'dev-main', - 'version' => 'dev-main', - 'aliases' => - array ( - ), - 'reference' => '5769442de60965346d4098544e88c3d20a2755fe', - ), - 'fastadminnet/fastadmin-addons' => - array ( - 'pretty_version' => '1.4.2', - 'version' => '1.4.2.0', - 'aliases' => - array ( - ), - 'reference' => '14af178a62fb4cc897f954fa9d7d53798ad2cf37', - ), - 'fastadminnet/fastadmin-mailer' => - array ( - 'pretty_version' => 'v2.1.1', - 'version' => '2.1.1.0', - 'aliases' => - array ( - ), - 'reference' => 'bca635ac5f564ed6688d818d215021ffb0813746', - ), - 'guzzlehttp/guzzle' => - array ( - 'pretty_version' => '7.9.2', - 'version' => '7.9.2.0', - 'aliases' => - array ( - ), - 'reference' => 'd281ed313b989f213357e3be1a179f02196ac99b', - ), - 'guzzlehttp/promises' => - array ( - 'pretty_version' => '2.3.0', - 'version' => '2.3.0.0', - 'aliases' => - array ( - ), - 'reference' => '481557b130ef3790cf82b713667b43030dc9c957', - ), - 'guzzlehttp/psr7' => - array ( - 'pretty_version' => '2.8.0', - 'version' => '2.8.0.0', - 'aliases' => - array ( - ), - 'reference' => '21dc724a0583619cd1652f673303492272778051', - ), - 'maennchen/zipstream-php' => - array ( - 'pretty_version' => '2.4.0', - 'version' => '2.4.0.0', - 'aliases' => - array ( - ), - 'reference' => '3fa72e4c71a43f9e9118752a5c90e476a8dc9eb3', - ), - 'markbaker/complex' => - array ( - 'pretty_version' => '3.0.2', - 'version' => '3.0.2.0', - 'aliases' => - array ( - ), - 'reference' => '95c56caa1cf5c766ad6d65b6344b807c1e8405b9', - ), - 'markbaker/matrix' => - array ( - 'pretty_version' => '3.0.1', - 'version' => '3.0.1.0', - 'aliases' => - array ( - ), - 'reference' => '728434227fe21be27ff6d86621a1b13107a2562c', - ), - 'monolog/monolog' => - array ( - 'pretty_version' => '2.10.0', - 'version' => '2.10.0.0', - 'aliases' => - array ( - ), - 'reference' => '5cf826f2991858b54d5c3809bee745560a1042a7', - ), - 'myclabs/php-enum' => - array ( - 'pretty_version' => '1.8.4', - 'version' => '1.8.4.0', - 'aliases' => - array ( - ), - 'reference' => 'a867478eae49c9f59ece437ae7f9506bfaa27483', - ), - 'nelexa/zip' => - array ( - 'pretty_version' => '4.0.2', - 'version' => '4.0.2.0', - 'aliases' => - array ( - ), - 'reference' => '88a1b6549be813278ff2dd3b6b2ac188827634a7', - ), - 'overtrue/pinyin' => - array ( - 'pretty_version' => '3.0.6', - 'version' => '3.0.6.0', - 'aliases' => - array ( - ), - 'reference' => '3b781d267197b74752daa32814d3a2cf5d140779', - ), - 'overtrue/socialite' => - array ( - 'pretty_version' => '2.0.24', - 'version' => '2.0.24.0', - 'aliases' => - array ( - ), - 'reference' => 'ee7e7b000ec7d64f2b8aba1f6a2eec5cdf3f8bec', - ), - 'overtrue/wechat' => - array ( - 'pretty_version' => '4.9.0', - 'version' => '4.9.0.0', - 'aliases' => - array ( - ), - 'reference' => '92791f5d957269c633b9aa175f842f6006f945b1', - ), - 'paragonie/constant_time_encoding' => - array ( - 'pretty_version' => 'v3.1.3', - 'version' => '3.1.3.0', - 'aliases' => - array ( - ), - 'reference' => 'd5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77', - ), - 'paragonie/random_compat' => - array ( - 'pretty_version' => 'v9.99.100', - 'version' => '9.99.100.0', - 'aliases' => - array ( - ), - 'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a', - ), - 'phpoffice/phpspreadsheet' => - array ( - 'pretty_version' => '1.30.1', - 'version' => '1.30.1.0', - 'aliases' => - array ( - ), - 'reference' => 'fa8257a579ec623473eabfe49731de5967306c4c', - ), - 'phpseclib/phpseclib' => - array ( - 'pretty_version' => '3.0.47', - 'version' => '3.0.47.0', - 'aliases' => - array ( - ), - 'reference' => '9d6ca36a6c2dd434765b1071b2644a1c683b385d', - ), - 'pimple/pimple' => - array ( - 'pretty_version' => 'v3.5.0', - 'version' => '3.5.0.0', - 'aliases' => - array ( - ), - 'reference' => 'a94b3a4db7fb774b3d78dad2315ddc07629e1bed', - ), - 'psr/cache' => - array ( - 'pretty_version' => '2.0.0', - 'version' => '2.0.0.0', - 'aliases' => - array ( - ), - 'reference' => '213f9dbc5b9bfbc4f8db86d2838dc968752ce13b', - ), - 'psr/cache-implementation' => - array ( - 'provided' => - array ( - 0 => '1.0|2.0', - ), - ), - 'psr/container' => - array ( - 'pretty_version' => '2.0.2', - 'version' => '2.0.2.0', - 'aliases' => - array ( - ), - 'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963', - ), - 'psr/event-dispatcher' => - array ( - 'pretty_version' => '1.0.0', - 'version' => '1.0.0.0', - 'aliases' => - array ( - ), - 'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0', - ), - 'psr/event-dispatcher-implementation' => - array ( - 'provided' => - array ( - 0 => '1.0', - ), - ), - 'psr/http-client' => - array ( - 'pretty_version' => '1.0.3', - 'version' => '1.0.3.0', - 'aliases' => - array ( - ), - 'reference' => 'bb5906edc1c324c9a05aa0873d40117941e5fa90', - ), - 'psr/http-client-implementation' => - array ( - 'provided' => - array ( - 0 => '1.0', - ), - ), - 'psr/http-factory' => - array ( - 'pretty_version' => '1.0.2', - 'version' => '1.0.2.0', - 'aliases' => - array ( - ), - 'reference' => 'e616d01114759c4c489f93b099585439f795fe35', - ), - 'psr/http-factory-implementation' => - array ( - 'provided' => - array ( - 0 => '1.0', - ), - ), - 'psr/http-message' => - array ( - 'pretty_version' => '1.1', - 'version' => '1.1.0.0', - 'aliases' => - array ( - ), - 'reference' => 'cb6ce4845ce34a8ad9e68117c10ee90a29919eba', - ), - 'psr/http-message-implementation' => - array ( - 'provided' => - array ( - 0 => '1.0', - ), - ), - 'psr/log' => - array ( - 'pretty_version' => '1.1.4', - 'version' => '1.1.4.0', - 'aliases' => - array ( - ), - 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', - ), - 'psr/log-implementation' => - array ( - 'provided' => - array ( - 0 => '1.0.0 || 2.0.0 || 3.0.0', - ), - ), - 'psr/simple-cache' => - array ( - 'pretty_version' => '1.0.1', - 'version' => '1.0.1.0', - 'aliases' => - array ( - ), - 'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b', - ), - 'psr/simple-cache-implementation' => - array ( - 'provided' => - array ( - 0 => '1.0|2.0', - ), - ), - 'ralouphie/getallheaders' => - array ( - 'pretty_version' => '3.0.3', - 'version' => '3.0.3.0', - 'aliases' => - array ( - ), - 'reference' => '120b605dfeb996808c31b6477290a714d356e822', - ), - 'symfony/cache' => - array ( - 'pretty_version' => 'v5.4.46', - 'version' => '5.4.46.0', - 'aliases' => - array ( - ), - 'reference' => '0fe08ee32cec2748fbfea10c52d3ee02049e0f6b', - ), - 'symfony/cache-contracts' => - array ( - 'pretty_version' => 'v2.5.4', - 'version' => '2.5.4.0', - 'aliases' => - array ( - ), - 'reference' => '517c3a3619dadfa6952c4651767fcadffb4df65e', - ), - 'symfony/cache-implementation' => - array ( - 'provided' => - array ( - 0 => '1.0|2.0', - ), - ), - 'symfony/deprecation-contracts' => - array ( - 'pretty_version' => 'v3.0.2', - 'version' => '3.0.2.0', - 'aliases' => - array ( - ), - 'reference' => '26954b3d62a6c5fd0ea8a2a00c0353a14978d05c', - ), - 'symfony/event-dispatcher' => - array ( - 'pretty_version' => 'v5.4.45', - 'version' => '5.4.45.0', - 'aliases' => - array ( - ), - 'reference' => '72982eb416f61003e9bb6e91f8b3213600dcf9e9', - ), - 'symfony/event-dispatcher-contracts' => - array ( - 'pretty_version' => 'v3.0.2', - 'version' => '3.0.2.0', - 'aliases' => - array ( - ), - 'reference' => '7bc61cc2db649b4637d331240c5346dcc7708051', - ), - 'symfony/event-dispatcher-implementation' => - array ( - 'provided' => - array ( - 0 => '2.0', - ), - ), - 'symfony/finder' => - array ( - 'pretty_version' => 'v6.0.19', - 'version' => '6.0.19.0', - 'aliases' => - array ( - ), - 'reference' => '5cc9cac6586fc0c28cd173780ca696e419fefa11', - ), - 'symfony/http-foundation' => - array ( - 'pretty_version' => 'v5.4.48', - 'version' => '5.4.48.0', - 'aliases' => - array ( - ), - 'reference' => '3f38b8af283b830e1363acd79e5bc3412d055341', - ), - 'symfony/polyfill-mbstring' => - array ( - 'pretty_version' => 'v1.32.0', - 'version' => '1.32.0.0', - 'aliases' => - array ( - ), - 'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493', - ), - 'symfony/polyfill-php73' => - array ( - 'pretty_version' => 'v1.32.0', - 'version' => '1.32.0.0', - 'aliases' => - array ( - ), - 'reference' => '0f68c03565dcaaf25a890667542e8bd75fe7e5bb', - ), - 'symfony/polyfill-php80' => - array ( - 'pretty_version' => 'v1.32.0', - 'version' => '1.32.0.0', - 'aliases' => - array ( - ), - 'reference' => '0cc9dd0f17f61d8131e7df6b84bd344899fe2608', - ), - 'symfony/psr-http-message-bridge' => - array ( - 'pretty_version' => 'v2.3.1', - 'version' => '2.3.1.0', - 'aliases' => - array ( - ), - 'reference' => '581ca6067eb62640de5ff08ee1ba6850a0ee472e', - ), - 'symfony/service-contracts' => - array ( - 'pretty_version' => 'v3.0.2', - 'version' => '3.0.2.0', - 'aliases' => - array ( - ), - 'reference' => 'd78d39c1599bd1188b8e26bb341da52c3c6d8a66', - ), - 'symfony/var-exporter' => - array ( - 'pretty_version' => 'v6.0.19', - 'version' => '6.0.19.0', - 'aliases' => - array ( - ), - 'reference' => 'df56f53818c2d5d9f683f4ad2e365ba73a3b69d2', - ), - 'topthink/framework' => - array ( - 'pretty_version' => 'dev-master', - 'version' => 'dev-master', - 'aliases' => - array ( - 0 => '9999999-dev', - ), - 'reference' => '9a2e7c2a1b6302afb61035c99c85bf0cfe0c52ec', - ), - 'topthink/think-captcha' => - array ( - 'pretty_version' => 'v1.0.9', - 'version' => '1.0.9.0', - 'aliases' => - array ( - ), - 'reference' => '9be9dd7e61c7fa3c478c4b92910d7230b94d0d23', - ), - 'topthink/think-helper' => - array ( - 'pretty_version' => 'v1.0.7', - 'version' => '1.0.7.0', - 'aliases' => - array ( - ), - 'reference' => '5f92178606c8ce131d36b37a57c58eb71e55f019', - ), - 'topthink/think-installer' => - array ( - 'pretty_version' => 'v1.0.14', - 'version' => '1.0.14.0', - 'aliases' => - array ( - ), - 'reference' => 'eae1740ac264a55c06134b6685dfb9f837d004d1', - ), - 'topthink/think-queue' => - array ( - 'pretty_version' => 'v1.1.6', - 'version' => '1.1.6.0', - 'aliases' => - array ( - ), - 'reference' => '250650eb0e8ea5af4cfdc7ae46f3f4e0a24ac245', - ), - 'workerman/channel' => - array ( - 'pretty_version' => 'v1.2.3', - 'version' => '1.2.3.0', - 'aliases' => - array ( - ), - 'reference' => '5edb0008eae35bf2da7218d911042abd23aa4370', - ), - 'workerman/phpsocket.io' => - array ( - 'pretty_version' => 'v2.2.0', - 'version' => '2.2.0.0', - 'aliases' => - array ( - ), - 'reference' => '0ba306b380e016f447f9860db95fcc1c7553fb91', - ), - 'workerman/workerman' => - array ( - 'pretty_version' => 'v4.2.1', - 'version' => '4.2.1.0', - 'aliases' => - array ( - ), - 'reference' => 'cafb5a43d93d7d30a16b32a57948581cca993562', - ), - ), ); diff --git a/vendor/fastadminnet/fastadmin-addons/composer.json b/vendor/fastadminnet/fastadmin-addons/composer.json index ad98272..050b7c9 100644 --- a/vendor/fastadminnet/fastadmin-addons/composer.json +++ b/vendor/fastadminnet/fastadmin-addons/composer.json @@ -3,7 +3,7 @@ "description": "addons package for fastadmin", "homepage": "https://github.com/fastadminnet/fastadmin-addons", "license": "Apache-2.0", - "version": "1.4.2", + "version": "1.4.3", "authors": [ { "name": "Karson", @@ -18,7 +18,7 @@ "issues": "https://github.com/fastadminnet/fastadmin-addons/issues" }, "require": { - "php": ">=7.0.0", + "php": ">=7.1.0", "nelexa/zip": "^3.3 || ^4.0" }, "autoload": { diff --git a/vendor/fastadminnet/fastadmin-addons/src/addons/Controller.php b/vendor/fastadminnet/fastadmin-addons/src/addons/Controller.php index fea2c4f..457ff5c 100644 --- a/vendor/fastadminnet/fastadmin-addons/src/addons/Controller.php +++ b/vendor/fastadminnet/fastadmin-addons/src/addons/Controller.php @@ -63,7 +63,7 @@ class Controller extends \think\Controller * @param Request $request Request对象 * @access public */ - public function __construct(Request $request = null) + public function __construct(?Request $request = null) { if (is_null($request)) { $request = Request::instance(); diff --git a/vendor/guzzlehttp/guzzle/CHANGELOG.md b/vendor/guzzlehttp/guzzle/CHANGELOG.md index e0b6216..5fe721e 100644 --- a/vendor/guzzlehttp/guzzle/CHANGELOG.md +++ b/vendor/guzzlehttp/guzzle/CHANGELOG.md @@ -2,6 +2,25 @@ Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version. +## 7.10.0 - 2025-08-23 + +### Added + +- Support for PHP 8.5 + +### Changed + +- Adjusted `guzzlehttp/promises` version constraint to `^2.3` +- Adjusted `guzzlehttp/psr7` version constraint to `^2.8` + + +## 7.9.3 - 2025-03-27 + +### Changed + +- Remove explicit content-length header for GET requests +- Improve compatibility with bad servers for boolean cookie values + ## 7.9.2 - 2024-07-24 diff --git a/vendor/guzzlehttp/guzzle/composer.json b/vendor/guzzlehttp/guzzle/composer.json index cbede14..0db75a9 100644 --- a/vendor/guzzlehttp/guzzle/composer.json +++ b/vendor/guzzlehttp/guzzle/composer.json @@ -81,8 +81,8 @@ "require": { "php": "^7.2.5 || ^8.0", "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.3", - "guzzlehttp/psr7": "^2.7.0", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" }, diff --git a/vendor/guzzlehttp/guzzle/package-lock.json b/vendor/guzzlehttp/guzzle/package-lock.json new file mode 100644 index 0000000..0e14dc1 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "guzzle", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php index c9806da..47c4d10 100644 --- a/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php +++ b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php @@ -62,6 +62,10 @@ class SetCookie if (is_numeric($value)) { $data[$search] = (int) $value; } + } elseif ($search === 'Secure' || $search === 'Discard' || $search === 'HttpOnly') { + if ($value) { + $data[$search] = true; + } } else { $data[$search] = $value; } diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php index fe36137..3c1fa9c 100644 --- a/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php +++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php @@ -125,7 +125,9 @@ class CurlFactory implements CurlFactoryInterface unset($easy->handle); if (\count($this->handles) >= $this->maxHandles) { - \curl_close($resource); + if (PHP_VERSION_ID < 80000) { + \curl_close($resource); + } } else { // Remove all callback functions as they can hold onto references // and are not cleaned up by curl_reset. Using curl_setopt_array @@ -729,7 +731,10 @@ class CurlFactory implements CurlFactoryInterface public function __destruct() { foreach ($this->handles as $id => $handle) { - \curl_close($handle); + if (PHP_VERSION_ID < 80000) { + \curl_close($handle); + } + unset($this->handles[$id]); } } diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php index 73a6abe..21abbed 100644 --- a/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php +++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php @@ -240,7 +240,10 @@ class CurlMultiHandler $handle = $this->handles[$id]['easy']->handle; unset($this->delays[$id], $this->handles[$id]); \curl_multi_remove_handle($this->_mh, $handle); - \curl_close($handle); + + if (PHP_VERSION_ID < 80000) { + \curl_close($handle); + } return true; } diff --git a/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php index f045b52..9df70cf 100644 --- a/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php +++ b/vendor/guzzlehttp/guzzle/src/Handler/Proxy.php @@ -17,10 +17,10 @@ class Proxy * Sends synchronous requests to a specific handler while sending all other * requests to another handler. * - * @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $default Handler used for normal responses - * @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $sync Handler used for synchronous responses. + * @param callable(RequestInterface, array): PromiseInterface $default Handler used for normal responses + * @param callable(RequestInterface, array): PromiseInterface $sync Handler used for synchronous responses. * - * @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the composed handler. + * @return callable(RequestInterface, array): PromiseInterface Returns the composed handler. */ public static function wrapSync(callable $default, callable $sync): callable { @@ -37,10 +37,10 @@ class Proxy * performance benefits of curl while still supporting true streaming * through the StreamHandler. * - * @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $default Handler used for non-streaming responses - * @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $streaming Handler used for streaming responses + * @param callable(RequestInterface, array): PromiseInterface $default Handler used for non-streaming responses + * @param callable(RequestInterface, array): PromiseInterface $streaming Handler used for streaming responses * - * @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the composed handler. + * @return callable(RequestInterface, array): PromiseInterface Returns the composed handler. */ public static function wrapStreaming(callable $default, callable $streaming): callable { diff --git a/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php index 1d89a8f..f24921f 100644 --- a/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php +++ b/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php @@ -53,8 +53,14 @@ class StreamHandler $request = $request->withoutHeader('Expect'); // Append a content-length header if body size is zero to match - // cURL's behavior. - if (0 === $request->getBody()->getSize()) { + // the behavior of `CurlHandler` + if ( + ( + 0 === \strcasecmp('PUT', $request->getMethod()) + || 0 === \strcasecmp('POST', $request->getMethod()) + ) + && 0 === $request->getBody()->getSize() + ) { $request = $request->withHeader('Content-Length', '0'); } @@ -327,8 +333,15 @@ class StreamHandler ); return $this->createResource( - function () use ($uri, &$http_response_header, $contextResource, $context, $options, $request) { + function () use ($uri, $contextResource, $context, $options, $request) { $resource = @\fopen((string) $uri, 'r', false, $contextResource); + + // See https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_the_http_response_header_predefined_variable + if (function_exists('http_get_last_response_headers')) { + /** @var array|null */ + $http_response_header = \http_get_last_response_headers(); + } + $this->lastHeaders = $http_response_header ?? []; if (false === $resource) { diff --git a/vendor/guzzlehttp/guzzle/src/Middleware.php b/vendor/guzzlehttp/guzzle/src/Middleware.php index 6edbb3f..9901da4 100644 --- a/vendor/guzzlehttp/guzzle/src/Middleware.php +++ b/vendor/guzzlehttp/guzzle/src/Middleware.php @@ -187,12 +187,12 @@ final class Middleware * Middleware that logs requests, responses, and errors using a message * formatter. * - * @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests. - * * @param LoggerInterface $logger Logs messages. * @param MessageFormatterInterface|MessageFormatter $formatter Formatter used to create message strings. * @param string $logLevel Level at which to log requests. * + * @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests. + * * @return callable Returns a function that accepts the next handler. */ public static function log(LoggerInterface $logger, $formatter, string $logLevel = 'info'): callable diff --git a/vendor/guzzlehttp/guzzle/src/Pool.php b/vendor/guzzlehttp/guzzle/src/Pool.php index 6277c61..ddc304b 100644 --- a/vendor/guzzlehttp/guzzle/src/Pool.php +++ b/vendor/guzzlehttp/guzzle/src/Pool.php @@ -86,7 +86,7 @@ class Pool implements PromisorInterface * @param ClientInterface $client Client used to send the requests * @param array|\Iterator $requests Requests to send concurrently. * @param array $options Passes through the options available in - * {@see \GuzzleHttp\Pool::__construct} + * {@see Pool::__construct} * * @return array Returns an array containing the response or an exception * in the same order that the requests were sent. diff --git a/vendor/guzzlehttp/guzzle/src/Utils.php b/vendor/guzzlehttp/guzzle/src/Utils.php index df52927..c6a5893 100644 --- a/vendor/guzzlehttp/guzzle/src/Utils.php +++ b/vendor/guzzlehttp/guzzle/src/Utils.php @@ -79,7 +79,7 @@ final class Utils * * The returned handler is not wrapped by any default middlewares. * - * @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the best handler for the given system. + * @return callable(\Psr\Http\Message\RequestInterface, array): Promise\PromiseInterface Returns the best handler for the given system. * * @throws \RuntimeException if no viable Handler is available. */ diff --git a/vendor/guzzlehttp/guzzle/src/functions.php b/vendor/guzzlehttp/guzzle/src/functions.php index 5edc66a..9ab4b96 100644 --- a/vendor/guzzlehttp/guzzle/src/functions.php +++ b/vendor/guzzlehttp/guzzle/src/functions.php @@ -50,7 +50,7 @@ function debug_resource($value = null) * * The returned handler is not wrapped by any default middlewares. * - * @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the best handler for the given system. + * @return callable(\Psr\Http\Message\RequestInterface, array): Promise\PromiseInterface Returns the best handler for the given system. * * @throws \RuntimeException if no viable Handler is available. * diff --git a/vendor/maennchen/zipstream-php/.gitattributes b/vendor/maennchen/zipstream-php/.gitattributes new file mode 100644 index 0000000..e058ebd --- /dev/null +++ b/vendor/maennchen/zipstream-php/.gitattributes @@ -0,0 +1,6 @@ +.gitignore text eol=lf +.gitattributes text eol=lf +*.md text eol=lf +*.php text eol=lf +*.yml text eol=lf +*.xml text eol=lf diff --git a/vendor/maennchen/zipstream-php/.github/CODE_OF_CONDUCT.md b/vendor/maennchen/zipstream-php/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..9d75b87 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of + any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, + without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +jonatan@maennchen.ch. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][mozilla coc]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][faq]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[mozilla coc]: https://github.com/mozilla/diversity +[faq]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/vendor/maennchen/zipstream-php/.github/CONTRIBUTING.md b/vendor/maennchen/zipstream-php/.github/CONTRIBUTING.md new file mode 100644 index 0000000..d8caee0 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/CONTRIBUTING.md @@ -0,0 +1,139 @@ +# Contributing to ZipStream-PHP + +## Welcome! + +We look forward to your contributions! Here are some examples how you can +contribute: + +- [Report a bug](https://github.com/maennchen/ZipStream-PHP/issues/new?labels=bug&template=BUG.md) +- [Propose a new feature](https://github.com/maennchen/ZipStream-PHP/issues/new?labels=enhancement&template=FEATURE.md) +- [Send a pull request](https://github.com/maennchen/ZipStream-PHP/pulls) + +## We have a Code of Conduct + +Please note that this project is released with a +[Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this +project you agree to abide by its terms. + +## Any contributions you make will be under the MIT License + +When you submit code changes, your submissions are understood to be under the +same [MIT License](https://github.com/maennchen/ZipStream-PHP/blob/main/LICENSE) +that covers the project. By contributing to this project, you agree that your +contributions will be licensed under its MIT License. + +## Write bug reports with detail, background, and sample code + +In your bug report, please provide the following: + +- A quick summary and/or background +- Steps to reproduce + - Be specific! + - Give sample code if you can. +- What you expected would happen +- What actually happens +- Notes (possibly including why you think this might be happening, or stuff you +- tried that didn't work) + +Please do not report a bug for a version of ZIPStream-PHP that is no longer +supported (`< 3.0.0`). Please do not report a bug if you are using a version of +PHP that is not supported by the version of ZipStream-PHP you are using. + +Please post code and output as text +([using proper markup](https://guides.github.com/features/mastering-markdown/)). +Do not post screenshots of code or output. + +Please include the output of `composer info | sort`. + +## Workflow for Pull Requests + +1. Fork the repository. +2. Create your branch from `main` if you plan to implement new functionality or + change existing code significantly; create your branch from the oldest branch + that is affected by the bug if you plan to fix a bug. +3. Implement your change and add tests for it. +4. Ensure the test suite passes. +5. Ensure the code complies with our coding guidelines (see below). +6. Send that pull request! + +Please make sure you have +[set up your user name and email address](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup) +for use with Git. Strings such as `silly nick name ` look really +stupid in the commit history of a project. + +We encourage you to +[sign your Git commits with your GPG key](https://docs.github.com/en/github/authenticating-to-github/signing-commits). + +Pull requests for new features must be based on the `main` branch. + +We are trying to keep backwards compatibility breaks in ZipStream-PHP to a +minimum. Please take this into account when proposing changes. + +Due to time constraints, we are not always able to respond as quickly as we +would like. Please do not take delays personal and feel free to remind us if you +feel that we forgot to respond. + +## Coding Guidelines + +This project comes with a configuration file (located at `/psalm.yml` in the +repository) that you can use to perform static analysis (with a focus on type +checking): + +```bash +$ .composer run test:lint +``` + +This project comes with a configuration file (located at +`/.php-cs-fixer.dist.php` in the repository) that you can use to (re)format your +source code for compliance with this project's coding guidelines: + +```bash +$ composer run format +``` + +Please understand that we will not accept a pull request when its changes +violate this project's coding guidelines. + +## Using ZipStream-PHP from a Git checkout + +The following commands can be used to perform the initial checkout of +ZipStream-PHP: + +```bash +$ git clone git@github.com:maennchen/ZipStream-PHP.git + +$ cd ZipStream-PHP +``` + +Install ZipStream-PHP's dependencies using [Composer](https://getcomposer.org/): + +```bash +$ composer install +$ composer run install:tools # Install phpDocumentor using phive +``` + +## Running ZipStream-PHP's test suite + +After following the steps shown above, ZipStream-PHP's test suite is run like +this: + +```bash +$ composer run test:unit +``` + +There's some slow tests in the test suite that test the handling of big files in +the archives. To skip them use the following command instead: + +```bash +$ composer run test:unit:fast +``` + +## Generating ZipStream-PHP Documentation + +To generate the documentation for the library, run: + +```bash +$ composer run docs:generate +``` + +The guide documentation pages can be found in the `/guides/` directory. diff --git a/vendor/maennchen/zipstream-php/.github/FUNDING.yml b/vendor/maennchen/zipstream-php/.github/FUNDING.yml new file mode 100644 index 0000000..5a46127 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/FUNDING.yml @@ -0,0 +1 @@ +github: maennchen diff --git a/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE/BUG.yml b/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE/BUG.yml new file mode 100644 index 0000000..0eb8cc7 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE/BUG.yml @@ -0,0 +1,71 @@ +name: 🐞 Bug Report +description: Something is broken? +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + - Create a discussion instead if you are looking for support: + https://github.com/maennchen/ZipStream-PHP/discussions + - type: input + id: version + attributes: + label: ZipStream-PHP version + placeholder: x.y.z + validations: + required: true + - type: input + id: php-version + attributes: + label: PHP version + placeholder: x.y.z + validations: + required: true + - type: checkboxes + id: constraints + attributes: + label: Constraints for Bug Report + options: + - label: | + I'm using a version of ZipStream that is currently supported: + https://github.com/maennchen/ZipStream-PHP#version-support + required: true + - label: | + I'm using a version of PHP that has active support: + https://www.php.net/supported-versions.php + required: true + - label: | + I'm using a version of PHP that is compatible with your used + ZipStream version. + required: true + - label: | + I'm using the latest release of the used ZipStream major version. + required: true + - type: textarea + id: summary + attributes: + label: Summary + description: Provide a summary describing the problem you are experiencing. + validations: + required: true + - type: textarea + id: current-behaviour + attributes: + label: Current behavior + description: What is the current (buggy) behavior? + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: How to reproduce + description: Provide steps to reproduce the bug. + validations: + required: true + - type: textarea + id: expected-behaviour + attributes: + label: Expected behavior + description: What was the expected (correct) behavior? + validations: + required: true diff --git a/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE/FEATURE.yml b/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE/FEATURE.yml new file mode 100644 index 0000000..e5dec63 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE/FEATURE.yml @@ -0,0 +1,11 @@ +name: 🎉 Feature Request +description: You have a neat idea that should be implemented? +labels: ["enhancement"] +body: + - type: textarea + id: description + attributes: + label: Description + description: Provide a summary of the feature you would like to see implemented. + validations: + required: true diff --git a/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE.md b/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..6892c57 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,6 @@ +Please go the the `Preview` tab and select the appropriate sub-template: + +* [🐞 Failing Test](?expand=1&template=FAILING_TEST.md) +* [🐞 Bug Fix](?expand=1&template=FIX.md) +* [⚙ Improvement](?expand=1&template=IMPROVEMENT.md) +* [🎉 New Feature](?expand=1&template=NEW_FEATURE.md) diff --git a/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/FAILING_TEST.md b/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/FAILING_TEST.md new file mode 100644 index 0000000..24603cb --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/FAILING_TEST.md @@ -0,0 +1,13 @@ + + + diff --git a/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/FIX.md b/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/FIX.md new file mode 100644 index 0000000..77f65a0 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/FIX.md @@ -0,0 +1,13 @@ + + + diff --git a/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/IMPROVEMENT.md b/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/IMPROVEMENT.md new file mode 100644 index 0000000..3ac8e31 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/IMPROVEMENT.md @@ -0,0 +1,9 @@ + + + diff --git a/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/NEW_FEATURE.md b/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/NEW_FEATURE.md new file mode 100644 index 0000000..ca53939 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/PULL_REQUEST_TEMPLATE/NEW_FEATURE.md @@ -0,0 +1,9 @@ + + + diff --git a/vendor/maennchen/zipstream-php/.github/SECURITY.md b/vendor/maennchen/zipstream-php/.github/SECURITY.md new file mode 100644 index 0000000..3046c31 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/SECURITY.md @@ -0,0 +1,22 @@ +# Security Policy + +[![OpenSSF Vulnerability Disclosure](https://img.shields.io/badge/OpenSSF-Vulnerability_Disclosure-green)](https://github.com/ossf/oss-vulnerability-guide/blob/main/finder-guide.md) +[![GitHub Report](https://img.shields.io/badge/GitHub-Security_Advisories-blue)](https://github.com/maennchen/ZipStream-PHP/security/advisories/new) +[![Email Report](https://img.shields.io/badge/Email-jonatan%40maennchen.ch-blue)](mailto:jonatan@maennchen.ch) + +This repository follows the +[OpenSSF Vulnerability Disclosure guide](https://github.com/ossf/oss-vulnerability-guide/tree/main). +You can learn more about it in the +[Finders Guide](https://github.com/ossf/oss-vulnerability-guide/blob/main/finder-guide.md). + +Please report vulnerabilities via the +[GitHub Security Vulnerability Reporting](https://github.com/maennchen/ZipStream-PHP/security/advisories/new) +or via email to [`jonatan@maennchen.ch`](mailto:jonatan@maennchen.ch) if this does +not work for you. + +Our vulnerability management team will respond within 3 working days of your +report. If the issue is confirmed as a vulnerability, we will open a Security +Advisory. This project follows a 90 day disclosure timeline. + +If you have questions about reporting security issues, email the vulnerability +management team: [`jonatan@maennchen.ch`](mailto:jonatan@maennchen.ch) diff --git a/vendor/maennchen/zipstream-php/.github/dependabot.yml b/vendor/maennchen/zipstream-php/.github/dependabot.yml new file mode 100644 index 0000000..9d20742 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: + - package-ecosystem: "composer" + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + github-actions: + applies-to: version-updates + patterns: + - "*" diff --git a/vendor/maennchen/zipstream-php/.github/scorecard.yml b/vendor/maennchen/zipstream-php/.github/scorecard.yml new file mode 100644 index 0000000..219fc0b --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/scorecard.yml @@ -0,0 +1,14 @@ +annotations: + - checks: + - fuzzing + reasons: + - reason: not-applicable # PHP is memory safe + - checks: + - packaging + reasons: + - reason: not-supported # Using Composer + - checks: + - signed-releases + reasons: + - reason: not-applicable # Releases are distributed via Composer + diff --git a/vendor/maennchen/zipstream-php/.github/workflows/branch_main.yml b/vendor/maennchen/zipstream-php/.github/workflows/branch_main.yml new file mode 100644 index 0000000..15ff278 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/workflows/branch_main.yml @@ -0,0 +1,24 @@ +on: + push: + branches: + - "main" + +name: "Main Branch" + +permissions: + contents: read + +jobs: + test: + name: "Test" + + permissions: + contents: read + security-events: write + + uses: ./.github/workflows/part_test.yml + + docs: + name: "Docs" + + uses: ./.github/workflows/part_docs.yml diff --git a/vendor/maennchen/zipstream-php/.github/workflows/part_dependabot.yml b/vendor/maennchen/zipstream-php/.github/workflows/part_dependabot.yml new file mode 100644 index 0000000..20a13a2 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/workflows/part_dependabot.yml @@ -0,0 +1,30 @@ +on: + workflow_call: {} + +name: "Dependabot" + +permissions: + contents: read + +jobs: + automerge_dependabot: + name: "Automerge PRs" + + runs-on: ubuntu-latest + + permissions: + pull-requests: write + contents: write + + steps: + - name: Harden Runner + uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 + with: + egress-policy: audit + + - uses: fastify/github-action-merge-dependabot@c3bde0759d4f24db16f7b250b2122bc2df57e817 # v3.11.0 + with: + github-token: ${{ github.token }} + use-github-auto-merge: true + # Major Updates need to be merged manually + target: minor diff --git a/vendor/maennchen/zipstream-php/.github/workflows/part_docs.yml b/vendor/maennchen/zipstream-php/.github/workflows/part_docs.yml new file mode 100644 index 0000000..9b779eb --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/workflows/part_docs.yml @@ -0,0 +1,51 @@ +on: + workflow_call: {} + +name: "Documentation" + +permissions: + contents: read + +jobs: + generate: + name: "Generate" + + runs-on: ubuntu-latest + + steps: + - name: Harden Runner + uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 + with: + egress-policy: audit + + - name: Checkout Code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: SetUp PHP + id: setup-php + uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2 + with: + php-version: "8.3" + tools: phive + - name: Cache Tools + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + id: cache + with: + path: ~/.phive + key: tools-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-${{ hashFiles('**/phars.xml') }} + restore-keys: | + tools-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}- + tools-${{ steps.setup-php.outputs.php-version }}- + tools- + - name: Install Tools + run: composer run install:tools + - name: Generate Docs + run: composer run docs:generate + - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + with: + name: docs + path: docs + - name: Package for GitHub Pages + uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 + with: + path: docs + diff --git a/vendor/maennchen/zipstream-php/.github/workflows/part_release.yml b/vendor/maennchen/zipstream-php/.github/workflows/part_release.yml new file mode 100644 index 0000000..112d72a --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/workflows/part_release.yml @@ -0,0 +1,94 @@ +on: + workflow_call: + inputs: + releaseName: + required: true + type: string + stable: + required: false + type: boolean + default: false + +name: "Release" + +permissions: + contents: read + +jobs: + create: + name: Create Release + + runs-on: ubuntu-latest + + permissions: + contents: write + + steps: + - name: Harden Runner + uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 + with: + egress-policy: audit + + - name: Create prerelease + if: ${{ !inputs.stable }} + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + gh release create \ + --repo ${{ github.repository }} \ + --title ${{ inputs.releaseName }} \ + --prerelease \ + --generate-notes \ + ${{ inputs.releaseName }} + + - name: Create release + if: ${{ inputs.stable }} + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + gh release create \ + --repo ${{ github.repository }} \ + --title ${{ inputs.releaseName }} \ + --generate-notes \ + ${{ inputs.releaseName }} + + upload_release: + name: "Upload" + + needs: ["create"] + + runs-on: ubuntu-latest + + permissions: + id-token: write + contents: write + attestations: write + + steps: + - name: Harden Runner + uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 + with: + egress-policy: audit + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: docs + path: docs + - run: | + tar -czvf docs.tar.gz docs + - name: "Attest Documentation" + id: attestation + uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 + with: + subject-path: "docs.tar.gz" + - name: Copy Attestation + run: cp "$ATTESTATION" docs.tar.gz.sigstore + env: + ATTESTATION: "${{ steps.attestation.outputs.bundle-path }}" + - name: Upload + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + gh release upload --clobber "${{ github.ref_name }}" \ + docs.tar.gz docs.tar.gz.sigstore diff --git a/vendor/maennchen/zipstream-php/.github/workflows/part_test.yml b/vendor/maennchen/zipstream-php/.github/workflows/part_test.yml new file mode 100644 index 0000000..d4f8180 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/workflows/part_test.yml @@ -0,0 +1,181 @@ +on: + workflow_call: + +name: "Test" + +permissions: + contents: read + +jobs: + phpunit: + name: PHPUnit (PHP ${{ matrix.php }} on ${{ matrix.os }}) + + runs-on: ${{ matrix.os }} + + continue-on-error: ${{ matrix.experimental }} + + strategy: + fail-fast: false + matrix: + php: ["8.2", "8.3", "8.4"] + os: [ubuntu-latest] + experimental: [false] + include: + - php: nightly + os: ubuntu-latest + experimental: true + - php: "8.4" + os: windows-latest + experimental: false + - php: "8.4" + os: macos-latest + experimental: false + + steps: + - name: Harden Runner + uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 + with: + egress-policy: audit + + - name: Checkout Code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: SetUp PHP + id: setup-php + uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2 + with: + php-version: "${{ matrix.php }}" + tools: phpunit + coverage: xdebug + extensions: xdebug,zip + - name: Get composer cache directory + id: composer-cache-common + if: "${{ runner.os != 'Windows' }}" + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + - name: Get composer cache directory + id: composer-cache-windows + if: "${{ runner.os == 'Windows' }}" + run: echo "dir=$(composer config cache-files-dir)" >> $env:GITHUB_OUTPUT + - name: Cache Deps + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + id: cache + with: + path: ${{ steps.composer-cache-common.outputs.dir }}${{ steps.composer-cache-windows.outputs.dir }} + key: deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-composer- + deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}- + deps-${{ steps.setup-php.outputs.php-version }}- + deps- + - name: Install Deps + if: matrix.php != 'nightly' + run: composer install --prefer-dist + - name: Install Deps (ignore PHP requirement) + if: matrix.php == 'nightly' + run: composer install --prefer-dist --ignore-platform-req=php+ + - name: Run PHPUnit + run: composer run test:unit:cov + - name: Upload coverage results to Coveralls + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_PARALLEL: true + COVERALLS_FLAG_NAME: ${{ runner.os }}-${{ steps.setup-php.outputs.php-version }} + run: composer run coverage:report + continue-on-error: ${{ matrix.experimental }} + + mark_coverage_done: + needs: ["phpunit"] + + runs-on: ubuntu-latest + + steps: + - name: Harden Runner + uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 + with: + egress-policy: audit + + - name: Coveralls Finished + uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # v2.3.6 + with: + github-token: ${{ secrets.github_token }} + parallel-finished: true + + psalm: + name: Run Psalm + + runs-on: "ubuntu-latest" + + permissions: + security-events: write + + steps: + - name: Harden Runner + uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 + with: + egress-policy: audit + + - name: Checkout Code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: SetUp PHP + id: setup-php + uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2 + with: + php-version: "8.3" + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + - name: Cache Deps + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + id: cache + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-composer- + deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}- + deps-${{ steps.setup-php.outputs.php-version }}- + deps- + - name: Install Deps + run: composer install --prefer-dist + - name: Run Psalm + run: composer run test:lint -- --report=results.sarif + - name: "Upload SARIF" + uses: github/codeql-action/upload-sarif@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3 + with: + sarif_file: results.sarif + + php-cs: + name: Run PHP-CS + + runs-on: "ubuntu-latest" + + steps: + - name: Harden Runner + uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 + with: + egress-policy: audit + + - name: Checkout Code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: SetUp PHP + id: setup-php + uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2 + with: + php-version: "8.3" + - name: Get composer cache directory + id: composer-cache + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + - name: Cache Deps + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + id: cache + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}-composer- + deps-${{ runner.os }}-${{ steps.setup-php.outputs.php-version }}- + deps-${{ steps.setup-php.outputs.php-version }}- + deps- + - name: Install Deps + run: composer install --prefer-dist + - name: Run PHP-CS + run: composer run test:formatted diff --git a/vendor/maennchen/zipstream-php/.github/workflows/pr.yml b/vendor/maennchen/zipstream-php/.github/workflows/pr.yml new file mode 100644 index 0000000..d21f398 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/workflows/pr.yml @@ -0,0 +1,50 @@ +on: + pull_request: + branches: + - "*" + workflow_dispatch: {} + +name: "Pull Request" + +permissions: + contents: read + +jobs: + test: + name: "Test" + + permissions: + contents: read + security-events: write + + uses: ./.github/workflows/part_test.yml + + docs: + name: "Docs" + + uses: ./.github/workflows/part_docs.yml + + dependabot: + name: "Dependabot" + + if: ${{ github.actor == 'dependabot[bot]'}} + + permissions: + pull-requests: write + contents: write + + uses: ./.github/workflows/part_dependabot.yml + + dependency-review: + name: Dependency Review + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 + with: + egress-policy: audit + + - name: 'Checkout Repository' + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: 'Dependency Review' + uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0 diff --git a/vendor/maennchen/zipstream-php/.github/workflows/scorecard.yml b/vendor/maennchen/zipstream-php/.github/workflows/scorecard.yml new file mode 100644 index 0000000..c1d08a2 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/workflows/scorecard.yml @@ -0,0 +1,78 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '28 11 * * 3' + push: + branches: [ "main" ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + # Uncomment the permissions below if installing in a private repository. + # contents: read + # actions: read + + steps: + - name: Harden Runner + uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 + with: + egress-policy: audit + + - name: "Checkout code" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecard on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard (optional). + # Commenting out will disable upload of results to your repo's Code Scanning dashboard + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3.28.5 + with: + sarif_file: results.sarif diff --git a/vendor/maennchen/zipstream-php/.github/workflows/tag-beta.yml b/vendor/maennchen/zipstream-php/.github/workflows/tag-beta.yml new file mode 100644 index 0000000..b339945 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/workflows/tag-beta.yml @@ -0,0 +1,29 @@ +on: + push: + tags: + - "[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+" + +name: "Beta Tag" + +permissions: + contents: read + +jobs: + docs: + name: "Docs" + + uses: ./.github/workflows/part_docs.yml + + release: + name: "Release" + + needs: ["docs"] + + permissions: + id-token: write + contents: write + attestations: write + + uses: ./.github/workflows/part_release.yml + with: + releaseName: "${{ github.ref_name }}" diff --git a/vendor/maennchen/zipstream-php/.github/workflows/tag-stable.yml b/vendor/maennchen/zipstream-php/.github/workflows/tag-stable.yml new file mode 100644 index 0000000..dfc1438 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/workflows/tag-stable.yml @@ -0,0 +1,55 @@ +on: + push: + tags: + - "[0-9]+.[0-9]+.[0-9]+" + +name: "Stable Tag" + +permissions: + contents: read + +jobs: + docs: + name: "Docs" + + uses: ./.github/workflows/part_docs.yml + + release: + name: "Release" + + needs: ["docs"] + + permissions: + id-token: write + contents: write + attestations: write + + uses: ./.github/workflows/part_release.yml + with: + releaseName: "${{ github.ref_name }}" + stable: true + + deploy_pages: + name: "Deploy to GitHub Pages" + + needs: ["release", "docs"] + + runs-on: ubuntu-latest + + permissions: + pages: write + id-token: write + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Harden Runner + uses: step-security/harden-runner@cb605e52c26070c328afc4562f0b4ada7618a84e # v2.10.4 + with: + egress-policy: audit + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 diff --git a/vendor/maennchen/zipstream-php/.gitignore b/vendor/maennchen/zipstream-php/.gitignore new file mode 100644 index 0000000..e52a498 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.gitignore @@ -0,0 +1,12 @@ +/composer.lock +/cov +/coverage.clover.xml +/docs +.idea +/.php-cs-fixer.cache +/.phpdoc/cache +/.phpunit.result.cache +/phpunit.xml +/.phpunit.cache +/tools +/vendor diff --git a/vendor/maennchen/zipstream-php/.phive/phars.xml b/vendor/maennchen/zipstream-php/.phive/phars.xml index 569106a..c958402 100644 --- a/vendor/maennchen/zipstream-php/.phive/phars.xml +++ b/vendor/maennchen/zipstream-php/.phive/phars.xml @@ -1,4 +1,4 @@ - + diff --git a/vendor/maennchen/zipstream-php/.php-cs-fixer.dist.php b/vendor/maennchen/zipstream-php/.php-cs-fixer.dist.php index 3ba86a4..9d47c38 100644 --- a/vendor/maennchen/zipstream-php/.php-cs-fixer.dist.php +++ b/vendor/maennchen/zipstream-php/.php-cs-fixer.dist.php @@ -13,6 +13,7 @@ declare(strict_types=1); use PhpCsFixer\Config; use PhpCsFixer\Finder; +use PhpCsFixer\Runner; $finder = Finder::create() ->exclude('.github') @@ -26,7 +27,8 @@ $config = new Config(); return $config->setRules([ '@PER' => true, '@PER:risky' => true, - '@PHP81Migration' => true, + '@PHP83Migration' => true, + '@PHP84Migration' => true, '@PHPUnit84Migration:risky' => true, 'array_syntax' => ['syntax' => 'short'], 'class_attributes_separation' => true, @@ -50,7 +52,6 @@ return $config->setRules([ 'semicolon_after_instruction' => true, 'short_scalar_cast' => true, 'simplified_null_return' => true, - 'single_blank_line_before_namespace' => true, 'single_class_element_per_statement' => true, 'single_line_comment_style' => true, 'single_quote' => true, @@ -68,4 +69,5 @@ return $config->setRules([ ], ]) ->setFinder($finder) - ->setRiskyAllowed(true); \ No newline at end of file + ->setRiskyAllowed(true) + ->setParallelConfig(Runner\Parallel\ParallelConfigFactory::detect()); diff --git a/vendor/maennchen/zipstream-php/.phpdoc/template/base.html.twig b/vendor/maennchen/zipstream-php/.phpdoc/template/base.html.twig index b7507fb..2a70c0a 100644 --- a/vendor/maennchen/zipstream-php/.phpdoc/template/base.html.twig +++ b/vendor/maennchen/zipstream-php/.phpdoc/template/base.html.twig @@ -9,7 +9,7 @@ "social": [ { "iconClass": "fab fa-github", "url": "https://github.com/maennchen/ZipStream-PHP"}, { "iconClass": "fas fa-envelope-open-text", "url": "https://github.com/maennchen/ZipStream-PHP/discussions"}, - { "iconClass": "fas fa-money-bill", "url": "https://opencollective.com/zipstream"}, + { "iconClass": "fas fa-money-bill", "url": "https://github.com/sponsors/maennchen"}, ] } %} \ No newline at end of file diff --git a/vendor/maennchen/zipstream-php/.tool-versions b/vendor/maennchen/zipstream-php/.tool-versions index 54f6ff0..150c1ee 100644 --- a/vendor/maennchen/zipstream-php/.tool-versions +++ b/vendor/maennchen/zipstream-php/.tool-versions @@ -1 +1 @@ -php 8.2.0 +php 8.4.3 diff --git a/vendor/maennchen/zipstream-php/README.md b/vendor/maennchen/zipstream-php/README.md index 155a265..1e6d679 100644 --- a/vendor/maennchen/zipstream-php/README.md +++ b/vendor/maennchen/zipstream-php/README.md @@ -4,7 +4,8 @@ [![Coverage Status](https://coveralls.io/repos/github/maennchen/ZipStream-PHP/badge.svg?branch=main)](https://coveralls.io/github/maennchen/ZipStream-PHP?branch=main) [![Latest Stable Version](https://poser.pugx.org/maennchen/zipstream-php/v/stable)](https://packagist.org/packages/maennchen/zipstream-php) [![Total Downloads](https://poser.pugx.org/maennchen/zipstream-php/downloads)](https://packagist.org/packages/maennchen/zipstream-php) -[![Financial Contributors on Open Collective](https://opencollective.com/zipstream/all/badge.svg?label=financial+contributors)](https://opencollective.com/zipstream) [![License](https://img.shields.io/github/license/maennchen/zipstream-php.svg)](LICENSE) +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9524/badge)](https://www.bestpractices.dev/projects/9524) +[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/maennchen/ZipStream-PHP/badge)](https://scorecard.dev/viewer/?uri=github.com/maennchen/ZipStream-PHP) ## Unstable Branch @@ -14,13 +15,17 @@ version. ## Overview -A fast and simple streaming zip file downloader for PHP. Using this library will save you from having to write the Zip to disk. You can directly send it to the user, which is much faster. It can work with S3 buckets or any PSR7 Stream. +A fast and simple streaming zip file downloader for PHP. Using this library will +save you from having to write the Zip to disk. You can directly send it to the +user, which is much faster. It can work with S3 buckets or any PSR7 Stream. Please see the [LICENSE](LICENSE) file for licensing and warranty information. ## Installation -Simply add a dependency on maennchen/zipstream-php to your project's composer.json file if you use Composer to manage the dependencies of your project. Use following command to add the package to your project's dependencies: +Simply add a dependency on maennchen/zipstream-php to your project's +`composer.json` file if you use Composer to manage the dependencies of your +project. Use following command to add the package to your project's dependencies: ```bash composer require maennchen/zipstream-php @@ -29,51 +34,120 @@ composer require maennchen/zipstream-php ## Usage For detailed instructions, please check the -[Documentation](https://maennchen.dev/ZipStream-PHP/). - -Here's a simple example: +[Documentation](https://maennchen.github.io/ZipStream-PHP/). ```php // Autoload the dependencies require 'vendor/autoload.php'; -// enable output of HTTP headers -$options = new ZipStream\Option\Archive(); -$options->setSendHttpHeaders(true); - // create a new zipstream object -$zip = new ZipStream\ZipStream('example.zip', $options); +$zip = new ZipStream\ZipStream( + outputName: 'example.zip', + + // enable output of HTTP headers + sendHttpHeaders: true, +); // create a file named 'hello.txt' -$zip->addFile('hello.txt', 'This is the contents of hello.txt'); +$zip->addFile( + fileName: 'hello.txt', + data: 'This is the contents of hello.txt', +); // add a file named 'some_image.jpg' from a local file 'path/to/image.jpg' -$zip->addFileFromPath('some_image.jpg', 'path/to/image.jpg'); +$zip->addFileFromPath( + fileName: 'some_image.jpg', + path: 'path/to/image.jpg', +); // finish the zip stream $zip->finish(); ``` +## Upgrade to version 3.1.2 + +- Minimum PHP Version: `8.2` + +## Upgrade to version 3.0.0 + +### General + +- Minimum PHP Version: `8.1` +- Only 64bit Architecture is supported. +- The class `ZipStream\Option\Method` has been replaced with the enum + `ZipStream\CompressionMethod`. +- Most clases have been flagged as `@internal` and should not be used from the + outside. + If you're using internal resources to extend this library, please open an + issue so that a clean interface can be added & published. + The externally available classes & enums are: + - `ZipStream\CompressionMethod` + - `ZipStream\Exception*` + - `ZipStream\ZipStream` + +### Archive Options + +- The class `ZipStream\Option\Archive` has been replaced in favor of named + arguments in the `ZipStream\ZipStream` constuctor. +- The archive options `largeFileSize` & `largeFileMethod` has been removed. If + you want different `compressionMethods` based on the file size, you'll have to + implement this yourself. +- The archive option `httpHeaderCallback` changed the type from `callable` to + `Closure`. +- The archive option `zeroHeader` has been replaced with the option + `defaultEnableZeroHeader` and can be overridden for every file. Its default + value changed from `false` to `true`. +- The archive option `statFiles` was removed since the library no longer checks + filesizes this way. +- The archive option `deflateLevel` has been replaced with the option + `defaultDeflateLevel` and can be overridden for every file. +- The first argument (`name`) of the `ZipStream\ZipStream` constuctor has been + replaced with the named argument `outputName`. +- Headers are now also sent if the `outputName` is empty. If you do not want to + automatically send http headers, set `sendHttpHeaders` to `false`. + +### File Options + +- The class `ZipStream\Option\File` has been replaced in favor of named + arguments in the `ZipStream\ZipStream->addFile*` functions. +- The file option `method` has been renamed to `compressionMethod`. +- The file option `time` has been renamed to `lastModificationDateTime`. +- The file option `size` has been renamed to `maxSize`. + ## Upgrade to version 2.0.0 -- Only the self opened streams will be closed (#139) - If you were relying on ZipStream to close streams that the library didn't open, - you'll need to close them yourself now. +https://github.com/maennchen/ZipStream-PHP/tree/2.0.0#upgrade-to-version-200 ## Upgrade to version 1.0.0 -- All options parameters to all function have been moved from an `array` to structured option objects. See [the wiki](https://github.com/maennchen/ZipStream-PHP/wiki/Available-options) for examples. -- The whole library has been refactored. The minimal PHP requirement has been raised to PHP 7.1. - -## Usage with Symfony and S3 - -You can find example code on [the wiki](https://github.com/maennchen/ZipStream-PHP/wiki/Symfony-example). +https://github.com/maennchen/ZipStream-PHP/tree/2.0.0#upgrade-to-version-100 ## Contributing ZipStream-PHP is a collaborative project. Please take a look at the [.github/CONTRIBUTING.md](.github/CONTRIBUTING.md) file. +## Version Support + +Versions are supported according to the table below. + +Please do not open any pull requests contradicting the current version support +status. + +Careful: Always check the `README` on `main` for up-to-date information. + +| Version | New Features | Bugfixes | Security | +|---------|--------------|----------|----------| +| *3* | ✓ | ✓ | ✓ | +| *2* | ✗ | ✗ | ✓ | +| *1* | ✗ | ✗ | ✗ | +| *0* | ✗ | ✗ | ✗ | + +This library aligns itself with the PHP core support. New features and bugfixes +will only target PHP versions according to their current status. + +See: https://www.php.net/supported-versions.php + ## About the Authors - Paul Duncan - https://pablotron.org/ @@ -81,34 +155,3 @@ ZipStream-PHP is a collaborative project. Please take a look at the - Jesse G. Donat - https://donatstudios.com - Nicolas CARPi - https://www.deltablot.com - Nik Barham - https://www.brokencube.co.uk - -## Contributors - -### Code Contributors - -This project exists thanks to all the people who contribute. -[[Contribute](.github/CONTRIBUTING.md)]. - - -### Financial Contributors - -Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/zipstream/contribute)] - -#### Individuals - - - -#### Organizations - -Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/zipstream/contribute)] - - - - - - - - - - - diff --git a/vendor/maennchen/zipstream-php/composer.json b/vendor/maennchen/zipstream-php/composer.json index 2746da1..6ecd503 100644 --- a/vendor/maennchen/zipstream-php/composer.json +++ b/vendor/maennchen/zipstream-php/composer.json @@ -22,28 +22,42 @@ } ], "require": { - "php": "^8.0", + "php-64bit": "^8.2", "ext-mbstring": "*", - "psr/http-message": "^1.0", - "myclabs/php-enum": "^1.5" + "ext-zlib": "*" }, "require-dev": { - "phpunit/phpunit": "^8.5.8 || ^9.4.2", - "guzzlehttp/guzzle": "^6.5.3 || ^7.2.0", + "phpunit/phpunit": "^11.0", + "guzzlehttp/guzzle": "^7.5", "ext-zip": "*", "mikey179/vfsstream": "^1.6", - "vimeo/psalm": "^5.0", - "php-coveralls/php-coveralls": "^2.4", - "friendsofphp/php-cs-fixer": "^3.9" + "php-coveralls/php-coveralls": "^2.5", + "friendsofphp/php-cs-fixer": "^3.16", + "vimeo/psalm": "^6.0", + "brianium/paratest": "^7.7" + }, + "suggest": { + "psr/http-message": "^2.0", + "guzzlehttp/psr7": "^2.4" }, "scripts": { - "format": "PHP_CS_FIXER_IGNORE_ENV=true php-cs-fixer fix", - "test": "composer run test:unit && composer run test:formatted && composer run test:lint", - "test:unit": "phpunit --coverage-clover=coverage.clover.xml --coverage-html cov", - "test:formatted": "composer run format -- --dry-run --stop-on-violation --using-cache=no", - "test:lint": "psalm --stats --show-info --find-unused-psalm-suppress", + "format": "php-cs-fixer fix", + "test": [ + "@test:unit", + "@test:formatted", + "@test:lint" + ], + "test:unit:setup-cov": "@putenv XDEBUG_MODE=coverage", + "test:unit": "paratest --functional", + "test:unit:cov": ["@test:unit:setup-cov", "@test:unit --coverage-clover=coverage.clover.xml --coverage-html cov"], + "test:unit:slow": "@test:unit --group slow", + "test:unit:slow:cov": ["@test:unit:setup-cov", "@test:unit --coverage-clover=coverage.clover.xml --coverage-html cov --group slow"], + "test:unit:fast": "@test:unit --exclude-group slow", + "test:unit:fast:cov": ["@test:unit:setup-cov", "@test:unit --coverage-clover=coverage.clover.xml --coverage-html cov --exclude-group slow"], + "test:formatted": "@format --dry-run --stop-on-violation --using-cache=no", + "test:lint": "psalm --stats --show-info=true --find-unused-psalm-suppress", "coverage:report": "php-coveralls --coverage_clover=coverage.clover.xml --json_path=coveralls-upload.json --insecure", - "install:tools": "phive install --trust-gpg-keys 0x67F861C3D889C656", + "install:tools": "phive install --trust-gpg-keys 0x67F861C3D889C656 --trust-gpg-keys 0x8AC0BAA79732DD42", "docs:generate": "tools/phpdocumentor --sourcecode" }, "autoload": { @@ -51,6 +65,9 @@ "ZipStream\\": "src/" } }, + "autoload-dev": { + "psr-4": { "ZipStream\\Test\\": "test/" } + }, "archive": { "exclude": [ "/composer.lock", diff --git a/vendor/maennchen/zipstream-php/guides/ContentLength.rst b/vendor/maennchen/zipstream-php/guides/ContentLength.rst index e51e692..21fea34 100644 --- a/vendor/maennchen/zipstream-php/guides/ContentLength.rst +++ b/vendor/maennchen/zipstream-php/guides/ContentLength.rst @@ -1,79 +1,47 @@ Adding Content-Length header ============= -Adding a ``Content-Length`` header for ``ZipStream`` is not trivial since the -size is not known beforehand. +Adding a ``Content-Length`` header for ``ZipStream`` can be achieved by +using the options ``SIMULATION_STRICT`` or ``SIMULATION_LAX`` in the +``operationMode`` parameter. -The following workaround adds an approximated header: +In the ``SIMULATION_STRICT`` mode, ``ZipStream`` will not allow to calculate the +size based on reading the whole file. ``SIMULATION_LAX`` will read the whole +file if neccessary. + +``SIMULATION_STRICT`` is therefore useful to make sure that the size can be +calculated efficiently. .. code-block:: php + use ZipStream\OperationMode; + use ZipStream\ZipStream; - class Zip - { - /** @var string */ - private $name; + $zip = new ZipStream( + operationMode: OperationMode::SIMULATE_STRICT, // or SIMULATE_LAX + defaultEnableZeroHeader: false, + sendHttpHeaders: true, + outputStream: $stream, + ); - private $files = []; + // Normally add files + $zip->addFile('sample.txt', 'Sample String Data'); - public function __construct($name) - { - $this->name = $name; + // Use addFileFromCallback and exactSize if you want to defer opening of + // the file resource + $zip->addFileFromCallback( + 'sample.txt', + exactSize: 18, + callback: function () { + return fopen('...'); } + ); - public function addFile($name, $data) - { - $this->files[] = ['type' => 'addFile', 'name' => $name, 'data' => $data]; - } + // Read resulting file size + $size = $zip->finish(); + + // Tell it to the browser + header('Content-Length: '. $size); + + // Execute the Simulation and stream the actual zip to the client + $zip->executeSimulation(); - public function addFileFromPath($name, $path) - { - $this->files[] = ['type' => 'addFileFromPath', 'name' => $name, 'path' => $path]; - } - - public function getEstimate() - { - $estimate = 22; - foreach ($this->files as $file) { - $estimate += 76 + 2 * strlen($file['name']); - if ($file['type'] === 'addFile') { - $estimate += strlen($file['data']); - } - if ($file['type'] === 'addFileFromPath') { - $estimate += filesize($file['path']); - } - } - return $estimate; - } - - public function finish() - { - header('Content-Length: ' . $this->getEstimate()); - $options = new \ZipStream\Option\Archive(); - $options->setSendHttpHeaders(true); - $options->setEnableZip64(false); - $options->setDeflateLevel(-1); - $zip = new \ZipStream\ZipStream($this->name, $options); - - $fileOptions = new \ZipStream\Option\File(); - $fileOptions->setMethod(\ZipStream\Option\Method::STORE()); - foreach ($this->files as $file) { - if ($file['type'] === 'addFile') { - $zip->addFile($file['name'], $file['data'], $fileOptions); - } - if ($file['type'] === 'addFileFromPath') { - $zip->addFileFromPath($file['name'], $file['path'], $fileOptions); - } - } - $zip->finish(); - exit; - } - } - -It only works with the following constraints: - -- All file content is known beforehand. -- Content Deflation is disabled - -Thanks to -`partiellkorrekt `_ -for this workaround. \ No newline at end of file diff --git a/vendor/maennchen/zipstream-php/guides/FlySystem.rst b/vendor/maennchen/zipstream-php/guides/FlySystem.rst index 0243f24..4e6c6fb 100644 --- a/vendor/maennchen/zipstream-php/guides/FlySystem.rst +++ b/vendor/maennchen/zipstream-php/guides/FlySystem.rst @@ -14,20 +14,21 @@ default one, and pass it to Flysystem ``putStream`` method. // the content is lost when closing the stream / opening another one $tempStream = fopen('php://memory', 'w+'); - // Init Options - $zipStreamOptions = new Archive(); - $zipStreamOptions->setOutputStream($tempStream); - // Create Zip Archive - $zipStream = new ZipStream('test.zip', $zipStreamOptions); + $zipStream = new ZipStream( + outputStream: $tempStream, + outputName: 'test.zip', + ); $zipStream->addFile('test.txt', 'text'); $zipStream->finish(); - // Store File (see Flysystem documentation, and all its framework integration) - $adapter = new Local(__DIR__.'/path/to/folder'); // Can be any adapter (AWS, Google, Ftp, etc.) + // Store File + // (see Flysystem documentation, and all its framework integration) + // Can be any adapter (AWS, Google, Ftp, etc.) + $adapter = new Local(__DIR__.'/path/to/folder'); $filesystem = new Filesystem($adapter); - $filesystem->putStream('test.zip', $tempStream) + $filesystem->writeStream('test.zip', $tempStream) // Close Stream - fclose($tempStream); \ No newline at end of file + fclose($tempStream); diff --git a/vendor/maennchen/zipstream-php/guides/Options.rst b/vendor/maennchen/zipstream-php/guides/Options.rst index eabaa6f..5e92e94 100644 --- a/vendor/maennchen/zipstream-php/guides/Options.rst +++ b/vendor/maennchen/zipstream-php/guides/Options.rst @@ -2,60 +2,65 @@ Available options =============== Here is the full list of options available to you. You can also have a look at -``src/Option/Archive.php`` file. - -First, an instance of ``ZipStream\Option\Archive`` needs to be created, and -after that you use setters methods to modify the values. +``src/ZipStream.php`` file. .. code-block:: php + use ZipStream\ZipStream; - use ZipStream\Option\Archive as ArchiveOptions; require_once 'vendor/autoload.php'; - $opt = new ArchiveOptions(); + $zip = new ZipStream( + // Define output stream + // (argument is eiter a resource or implementing + // `Psr\Http\Message\StreamInterface`) + // + // Setup with `psr/http-message` & `guzzlehttp/psr7` dependencies + // required when using `Psr\Http\Message\StreamInterface`. + outputStream: $filePointer, - // Define output stream (argument is of type resource) - $opt->setOutputStream($fd); + // Set the deflate level (default is 6; use -1 to disable it) + defaultDeflateLevel: 6, - // Set the deflate level (default is 6; use -1 to disable it) - $opt->setDeflateLevel(6); + // Add a comment to the zip file + comment: 'This is a comment.', - // Add a comment to the zip file - $opt->setComment('This is a comment.'); + // Send http headers (default is true) + sendHttpHeaders: false, - // Size, in bytes, of the largest file to try and load into memory (used by addFileFromPath()). Large files may also be compressed differently; see the 'largeFileMethod' option. - $opt->setLargeFileSize(30000000); + // HTTP Content-Disposition. + // Defaults to 'attachment', where FILENAME is the specified filename. + // Note that this does nothing if you are not sending HTTP headers. + contentDisposition: 'attachment', - // How to handle large files. Legal values are STORE (the default), or DEFLATE. Store sends the file raw and is significantly faster, while DEFLATE compresses the file and is much, much slower. Note that deflate must compress the file twice and is extremely slow. - $opt->setLargeFileMethod(ZipStream\Option\Method::STORE()); - $opt->setLargeFileMethod(ZipStream\Option\Method::DEFLATE()); + // Output Name for HTTP Content-Disposition + // Defaults to no name + outputName: "example.zip", - // Send http headers (default is false) - $opt->setSendHttpHeaders(false); + // HTTP Content-Type. + // Defaults to 'application/x-zip'. + // Note that this does nothing if you are not sending HTTP headers. + contentType: 'application/x-zip', - // HTTP Content-Disposition. Defaults to 'attachment', where FILENAME is the specified filename. Note that this does nothing if you are not sending HTTP headers. - $opt->setContentDisposition('attachment'); + // Set the function called for setting headers. + // Default is the `header()` of PHP + httpHeaderCallback: header(...), - // Set the content type (does nothing if you are not sending HTTP headers) - $opt->setContentType('application/x-zip'); + // Enable streaming files with single read where general purpose bit 3 + // indicates local file header contain zero values in crc and size + // fields, these appear only after file contents in data descriptor + // block. + // Set to true if your input stream is remote + // (used with addFileFromStream()). + // Default is false. + defaultEnableZeroHeader: false, - // Set the function called for setting headers. Default is the `header()` of PHP - $opt->setHttpHeaderCallback('header'); + // Enable zip64 extension, allowing very large archives + // (> 4Gb or file count > 64k) + // Default is true + enableZip64: true, - // Enable streaming files with single read where general purpose bit 3 indicates local file header contain zero values in crc and size fields, these appear only after file contents in data descriptor block. Default is false. Set to true if your input stream is remote (used with addFileFromStream()). - $opt->setZeroHeader(false); - - // Enable reading file stat for determining file size. When a 32-bit system reads file size that is over 2 GB, invalid value appears in file size due to integer overflow. Should be disabled on 32-bit systems with method addFileFromPath if any file may exceed 2 GB. In this case file will be read in blocks and correct size will be determined from content. Default is true. - $opt->setStatFiles(true); - - // Enable zip64 extension, allowing very large archives (> 4Gb or file count > 64k) - // default is true - $opt->setEnableZip64(true); - - // Flush output buffer after every write - // default is false - $opt->setFlushOutput(true); - - // Now that everything is set you can pass the options to the ZipStream instance - $zip = new ZipStream('example.zip', $opt); + // Flush output buffer after every write + // Default is false + flushOutput: true, + ); diff --git a/vendor/maennchen/zipstream-php/guides/PSR7Streams.rst b/vendor/maennchen/zipstream-php/guides/PSR7Streams.rst index 4b4ca4b..22af71d 100644 --- a/vendor/maennchen/zipstream-php/guides/PSR7Streams.rst +++ b/vendor/maennchen/zipstream-php/guides/PSR7Streams.rst @@ -12,7 +12,10 @@ Example --------------- .. code-block:: php - + $stream = $response->getBody(); // add a file named 'streamfile.txt' from the content of the stream - $zip->addFileFromPsr7Stream('streamfile.txt', $stream); + $zip->addFileFromPsr7Stream( + fileName: 'streamfile.txt', + stream: $stream, + ); diff --git a/vendor/maennchen/zipstream-php/guides/StreamOutput.rst b/vendor/maennchen/zipstream-php/guides/StreamOutput.rst index 1a0495f..9f3165b 100644 --- a/vendor/maennchen/zipstream-php/guides/StreamOutput.rst +++ b/vendor/maennchen/zipstream-php/guides/StreamOutput.rst @@ -5,9 +5,9 @@ Stream to S3 Bucket --------------- .. code-block:: php + use Aws\S3\S3Client; use Aws\Credentials\CredentialProvider; - use ZipStream\Option\Archive; use ZipStream\ZipStream; $bucket = 'your bucket name'; @@ -21,13 +21,19 @@ Stream to S3 Bucket $zipFile = fopen("s3://$bucket/example.zip", 'w'); - $options = new Archive(); - $options->setEnableZip64(false); - $options->setOutputStream($zipFile); + $zip = new ZipStream( + enableZip64: false, + outputStream: $zipFile, + ); - $zip = new ZipStream(null, $options); - $zip->addFile('file1.txt', 'File1 data'); - $zip->addFile('file2.txt', 'File2 data'); + $zip->addFile( + fileName: 'file1.txt', + data: 'File1 data', + ); + $zip->addFile( + fileName: 'file2.txt', + data: 'File2 data', + ); $zip->finish(); - fclose($zipFile); \ No newline at end of file + fclose($zipFile); diff --git a/vendor/maennchen/zipstream-php/guides/Symfony.rst b/vendor/maennchen/zipstream-php/guides/Symfony.rst index 18f9059..902552c 100644 --- a/vendor/maennchen/zipstream-php/guides/Symfony.rst +++ b/vendor/maennchen/zipstream-php/guides/Symfony.rst @@ -31,7 +31,7 @@ stored in an AWS S3 bucket by key: */ public function zipStreamAction() { - //sample test file on s3 + // sample test file on s3 $s3keys = array( "ziptestfolder/file1.txt" ); @@ -39,18 +39,18 @@ stored in an AWS S3 bucket by key: $s3Client = $this->get('app.amazon.s3'); //s3client service $s3Client->registerStreamWrapper(); //required - //using StreamedResponse to wrap ZipStream functionality for files on AWS s3. + // using StreamedResponse to wrap ZipStream functionality + // for files on AWS s3. $response = new StreamedResponse(function() use($s3keys, $s3Client) { // Define suitable options for ZipStream Archive. - $options = new \ZipStream\Option\Archive(); - $options->setContentType('application/octet-stream'); // this is needed to prevent issues with truncated zip files - $options->setZeroHeader(true); - $options->setComment('test zip file.'); - //initialise zipstream with output zip filename and options. - $zip = new ZipStream\ZipStream('test.zip', $options); + $zip = new ZipStream\ZipStream( + outputName: 'test.zip', + defaultEnableZeroHeader: true, + contentType: 'application/octet-stream', + ); //loop keys - useful for multiple files foreach ($s3keys as $key) { @@ -58,15 +58,19 @@ stored in an AWS S3 bucket by key: //file using the same name. $fileName = basename($key); - //concatenate s3path. - $bucket = 'bucketname'; //replace with your bucket name or get from parameters file. + // concatenate s3path. + // replace with your bucket name or get from parameters file. + $bucket = 'bucketname'; $s3path = "s3://" . $bucket . "/" . $key; //addFileFromStream if ($streamRead = fopen($s3path, 'r')) { - $zip->addFileFromStream($fileName, $streamRead); + $zip->addFileFromStream( + fileName: $fileName, + stream: $streamRead, + ); } else { - die('Could not open stream for reading'); + die('Could not open stream for reading'); } } @@ -123,4 +127,4 @@ You need to add correct permissions 's3' => ['ACL' => 'public-read'], ]); - fopen($path, 'w', null, $outputContext); \ No newline at end of file + fopen($path, 'w', null, $outputContext); diff --git a/vendor/maennchen/zipstream-php/guides/index.rst b/vendor/maennchen/zipstream-php/guides/index.rst index 67f504b..48f465a 100644 --- a/vendor/maennchen/zipstream-php/guides/index.rst +++ b/vendor/maennchen/zipstream-php/guides/index.rst @@ -22,11 +22,20 @@ Installation Simply add a dependency on ``maennchen/zipstream-php`` to your project's ``composer.json`` file if you use Composer to manage the dependencies of your -project. Use following command to add the package to your project's dependencies: +project. Use following command to add the package to your project's +dependencies: .. code-block:: sh composer require maennchen/zipstream-php +If you want to use``addFileFromPsr7Stream``` +(``Psr\Http\Message\StreamInterface``) or use a stream instead of a +``resource`` as ``outputStream``, the following dependencies must be installed +as well: + +.. code-block:: sh + composer require psr/http-message guzzlehttp/psr7 + If ``composer install`` yields the following error, your installation is missing the `mbstring extension `_, either `install it `_ @@ -52,25 +61,42 @@ Here's a simple example: // Autoload the dependencies require 'vendor/autoload.php'; - // enable output of HTTP headers - $options = new ZipStream\Option\Archive(); - $options->setSendHttpHeaders(true); - // create a new zipstream object - $zip = new ZipStream\ZipStream('example.zip', $options); + $zip = new ZipStream\ZipStream( + outputName: 'example.zip', + + // enable output of HTTP headers + sendHttpHeaders: true, + ); // create a file named 'hello.txt' - $zip->addFile('hello.txt', 'This is the contents of hello.txt'); + $zip->addFile( + fileName: 'hello.txt', + data: 'This is the contents of hello.txt', + ); // add a file named 'some_image.jpg' from a local file 'path/to/image.jpg' - $zip->addFileFromPath('some_image.jpg', 'path/to/image.jpg'); + $zip->addFileFromPath( + fileName: 'some_image.jpg', + path: 'path/to/image.jpg', + ); // add a file named 'goodbye.txt' from an open stream resource - $fp = tmpfile(); - fwrite($fp, 'The quick brown fox jumped over the lazy dog.'); - rewind($fp); - $zip->addFileFromStream('goodbye.txt', $fp); - fclose($fp); + $filePointer = tmpfile(); + fwrite($filePointer, 'The quick brown fox jumped over the lazy dog.'); + rewind($filePointer); + $zip->addFileFromStream( + fileName: 'goodbye.txt', + stream: $filePointer, + ); + fclose($filePointer); + + // add a file named 'streamfile.txt' from the body of a `guzzle` response + // Setup with `psr/http-message` & `guzzlehttp/psr7` dependencies required. + $zip->addFileFromPsr7Stream( + fileName: 'streamfile.txt', + stream: $response->getBody(), + ); // finish the zip stream $zip->finish(); @@ -87,7 +113,7 @@ The native Mac OS archive extraction tool prior to macOS 10.15 might not open archives in some conditions. A workaround is to disable the Zip64 feature with the option ``enableZip64: false``. This limits the archive to 4 Gb and 64k files but will allow users on macOS 10.14 and below to open them without issue. -See `#116 `_. +See `#116 `_. The linux ``unzip`` utility might not handle properly unicode characters. It is recommended to extract with another tool like diff --git a/vendor/maennchen/zipstream-php/phpunit.xml.dist b/vendor/maennchen/zipstream-php/phpunit.xml.dist index 8a2f318..1b02a3a 100644 --- a/vendor/maennchen/zipstream-php/phpunit.xml.dist +++ b/vendor/maennchen/zipstream-php/phpunit.xml.dist @@ -1,14 +1,15 @@ - - - - src - - + + test + + + src + + diff --git a/vendor/maennchen/zipstream-php/psalm.xml b/vendor/maennchen/zipstream-php/psalm.xml index 4e4c4f6..56af0e6 100644 --- a/vendor/maennchen/zipstream-php/psalm.xml +++ b/vendor/maennchen/zipstream-php/psalm.xml @@ -1,53 +1,25 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + diff --git a/vendor/maennchen/zipstream-php/src/Bigint.php b/vendor/maennchen/zipstream-php/src/Bigint.php deleted file mode 100644 index f2565e9..0000000 --- a/vendor/maennchen/zipstream-php/src/Bigint.php +++ /dev/null @@ -1,174 +0,0 @@ -fillBytes($value, 0, 8); - } - - /** - * Get an instance - * - * @param int $value - * @return Bigint - */ - public static function init(int $value = 0): self - { - return new self($value); - } - - /** - * Fill bytes from low to high - * - * @param int $low - * @param int $high - * @return Bigint - */ - public static function fromLowHigh(int $low, int $high): self - { - $bigint = new self(); - $bigint->fillBytes($low, 0, 4); - $bigint->fillBytes($high, 4, 4); - return $bigint; - } - - /** - * Get high 32 - * - * @return int - */ - public function getHigh32(): int - { - return $this->getValue(4, 4); - } - - /** - * Get value from bytes array - * - * @param int $end - * @param int $length - * @return int - */ - public function getValue(int $end = 0, int $length = 8): int - { - $result = 0; - for ($i = $end + $length - 1; $i >= $end; $i--) { - $result <<= 8; - $result |= $this->bytes[$i]; - } - return $result; - } - - /** - * Get low FF - * - * @param bool $force - * @return float - */ - public function getLowFF(bool $force = false): float - { - if ($force || $this->isOver32()) { - return (float)0xFFFFFFFF; - } - return (float)$this->getLow32(); - } - - /** - * Check if is over 32 - * - * @psalm-suppress ArgumentTypeCoercion - * @param bool $force - * @return bool - */ - public function isOver32(bool $force = false): bool - { - // value 0xFFFFFFFF already needs a Zip64 header - return $force || - max(array_slice($this->bytes, 4, 4)) > 0 || - min(array_slice($this->bytes, 0, 4)) === 0xFF; - } - - /** - * Get low 32 - * - * @return int - */ - public function getLow32(): int - { - return $this->getValue(0, 4); - } - - /** - * Get hexadecimal - * - * @return string - */ - public function getHex64(): string - { - $result = '0x'; - for ($i = 7; $i >= 0; $i--) { - $result .= sprintf('%02X', $this->bytes[$i]); - } - return $result; - } - - /** - * Add - * - * @param Bigint $other - * @return Bigint - */ - public function add(self $other): self - { - $result = clone $this; - $overflow = false; - for ($i = 0; $i < 8; $i++) { - $result->bytes[$i] += $other->bytes[$i]; - if ($overflow) { - $result->bytes[$i]++; - $overflow = false; - } - if ($result->bytes[$i] & 0x100) { - $overflow = true; - $result->bytes[$i] &= 0xFF; - } - } - if ($overflow) { - throw new OverflowException(); - } - return $result; - } - - /** - * Fill the bytes field with int - * - * @param int $value - * @param int $start - * @param int $count - * @return void - */ - protected function fillBytes(int $value, int $start, int $count): void - { - for ($i = 0; $i < $count; $i++) { - $this->bytes[$start + $i] = $i >= PHP_INT_SIZE ? 0 : $value & 0xFF; - $value >>= 8; - } - } -} diff --git a/vendor/maennchen/zipstream-php/src/CentralDirectoryFileHeader.php b/vendor/maennchen/zipstream-php/src/CentralDirectoryFileHeader.php new file mode 100644 index 0000000..ffcfc6e --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/CentralDirectoryFileHeader.php @@ -0,0 +1,52 @@ +value), + new PackField(format: 'V', value: Time::dateTimeToDosTime($lastModificationDateTime)), + new PackField(format: 'V', value: $crc32), + new PackField(format: 'V', value: $compressedSize), + new PackField(format: 'V', value: $uncompressedSize), + new PackField(format: 'v', value: strlen($fileName)), + new PackField(format: 'v', value: strlen($extraField)), + new PackField(format: 'v', value: strlen($fileComment)), + new PackField(format: 'v', value: $diskNumberStart), + new PackField(format: 'v', value: $internalFileAttributes), + new PackField(format: 'V', value: $externalFileAttributes), + new PackField(format: 'V', value: $relativeOffsetOfLocalHeader), + ) . $fileName . $extraField . $fileComment; + } +} diff --git a/vendor/maennchen/zipstream-php/src/CompressionMethod.php b/vendor/maennchen/zipstream-php/src/CompressionMethod.php new file mode 100644 index 0000000..51e4363 --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/CompressionMethod.php @@ -0,0 +1,106 @@ +format(DateTimeInterface::ATOM) . " can't be represented as DOS time / date."); + } +} diff --git a/vendor/maennchen/zipstream-php/src/Exception/EncodingException.php b/vendor/maennchen/zipstream-php/src/Exception/EncodingException.php deleted file mode 100644 index 5b0267d..0000000 --- a/vendor/maennchen/zipstream-php/src/Exception/EncodingException.php +++ /dev/null @@ -1,14 +0,0 @@ -resource = $resource; + parent::__construct('Function ' . $function . 'failed on resource.'); + } +} diff --git a/vendor/maennchen/zipstream-php/src/Exception/SimulationFileUnknownException.php b/vendor/maennchen/zipstream-php/src/Exception/SimulationFileUnknownException.php new file mode 100644 index 0000000..717c1aa --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Exception/SimulationFileUnknownException.php @@ -0,0 +1,19 @@ +fileName = self::filterFilename($fileName); + $this->checkEncoding(); - /** - * @var Bigint - */ - public $len; + if ($this->enableZeroHeader) { + $this->generalPurposeBitFlag |= GeneralPurposeBitFlag::ZERO_HEADER; + } - /** - * @var Bigint - */ - public $zlen; - - /** @var int */ - public $crc; - - /** - * @var Bigint - */ - public $hlen; - - /** - * @var Bigint - */ - public $ofs; - - /** - * @var int - */ - public $bits; - - /** - * @var Version - */ - public $version; - - /** - * @var ZipStream - */ - public $zip; - - /** - * @var resource - */ - private $deflate; - - /** - * @var HashContext - */ - private $hash; - - /** - * @var Method - */ - private $method; - - /** - * @var Bigint - */ - private $totalLength; - - public function __construct(ZipStream $zip, string $name, ?FileOptions $opt = null) - { - $this->zip = $zip; - - $this->name = $name; - $this->opt = $opt ?: new FileOptions(); - $this->method = $this->opt->getMethod(); - $this->version = Version::STORE(); - $this->ofs = new Bigint(); + $this->version = $this->compressionMethod === CompressionMethod::DEFLATE ? Version::DEFLATE : Version::STORE; } - public function processPath(string $path): void + public function cloneSimulationExecution(): self { - if (!is_readable($path)) { - if (!file_exists($path)) { - throw new FileNotFoundException($path); - } - throw new FileNotReadableException($path); - } - if ($this->zip->isLargeFile($path) === false) { - $data = file_get_contents($path); - $this->processData($data); + return new self( + $this->fileName, + $this->dataCallback, + OperationMode::NORMAL, + $this->startOffset, + $this->compressionMethod, + $this->comment, + $this->lastModificationDateTime, + $this->deflateLevel, + $this->maxSize, + $this->exactSize, + $this->enableZip64, + $this->enableZeroHeader, + $this->send, + $this->recordSentBytes, + ); + } + + public function process(): string + { + $forecastSize = $this->forecastSize(); + + if ($this->enableZeroHeader) { + // No calculation required + } elseif ($this->isSimulation() && $forecastSize !== null) { + $this->uncompressedSize = $forecastSize; + $this->compressedSize = $forecastSize; } else { - $this->method = $this->zip->opt->getLargeFileMethod(); - - $stream = new Stream(fopen($path, 'rb')); - $this->processStream($stream); - $stream->close(); + $this->readStream(send: false); + if (rewind($this->unpackStream()) === false) { + throw new ResourceActionException('rewind', $this->unpackStream()); + } } + + $this->addFileHeader(); + + $detectedSize = $forecastSize ?? ($this->compressedSize > 0 ? $this->compressedSize : null); + + if ( + $this->isSimulation() && + $detectedSize !== null + ) { + $this->uncompressedSize = $detectedSize; + $this->compressedSize = $detectedSize; + ($this->recordSentBytes)($detectedSize); + } else { + $this->readStream(send: true); + } + + $this->addFileFooter(); + return $this->getCdrFile(); } - public function processData(string $data): void + /** + * @return resource + */ + private function unpackStream() { - $this->len = new Bigint(strlen($data)); - $this->crc = crc32($data); - - // compress data if needed - if ($this->method->equals(Method::DEFLATE())) { - $data = gzdeflate($data); + if ($this->stream) { + return $this->stream; } - $this->zlen = new Bigint(strlen($data)); - $this->addFileHeader(); - $this->zip->send($data); - $this->addFileFooter(); + if ($this->operationMode === OperationMode::SIMULATE_STRICT) { + throw new SimulationFileUnknownException(); + } + + $this->stream = ($this->dataCallback)(); + + if (!$this->enableZeroHeader && !stream_get_meta_data($this->stream)['seekable']) { + throw new StreamNotSeekableException(); + } + if (!( + str_contains(stream_get_meta_data($this->stream)['mode'], 'r') + || str_contains(stream_get_meta_data($this->stream)['mode'], 'w+') + || str_contains(stream_get_meta_data($this->stream)['mode'], 'a+') + || str_contains(stream_get_meta_data($this->stream)['mode'], 'x+') + || str_contains(stream_get_meta_data($this->stream)['mode'], 'c+') + )) { + throw new StreamNotReadableException(); + } + + return $this->stream; + } + + private function forecastSize(): ?int + { + if ($this->compressionMethod !== CompressionMethod::STORE) { + return null; + } + if ($this->exactSize !== null) { + return $this->exactSize; + } + $fstat = fstat($this->unpackStream()); + if (!$fstat || !array_key_exists('size', $fstat) || $fstat['size'] < 1) { + return null; + } + + if ($this->maxSize !== null && $this->maxSize < $fstat['size']) { + return $this->maxSize; + } + + return $fstat['size']; } /** * Create and send zip header for this file. - * - * @return void - * @throws \ZipStream\Exception\EncodingException */ - public function addFileHeader(): void + private function addFileHeader(): void { - $name = static::filterFilename($this->name); + $forceEnableZip64 = $this->enableZeroHeader && $this->enableZip64; - // calculate name length - $nameLength = strlen($name); + $footer = $this->buildZip64ExtraBlock($forceEnableZip64); - // create dos timestamp - $time = static::dosTime($this->opt->getTime()->getTimestamp()); + $zip64Enabled = $footer !== ''; - $comment = $this->opt->getComment(); - - if (!mb_check_encoding($name, 'ASCII') || - !mb_check_encoding($comment, 'ASCII')) { - // Sets Bit 11: Language encoding flag (EFS). If this bit is set, - // the filename and comment fields for this file - // MUST be encoded using UTF-8. (see APPENDIX D) - if (mb_check_encoding($name, 'UTF-8') && - mb_check_encoding($comment, 'UTF-8')) { - $this->bits |= self::BIT_EFS_UTF8; - } + if ($zip64Enabled) { + $this->version = Version::ZIP64; } - if ($this->method->equals(Method::DEFLATE())) { - $this->version = Version::DEFLATE(); + if ($this->generalPurposeBitFlag & GeneralPurposeBitFlag::EFS) { + // Put the tricky entry to + // force Linux unzip to lookup EFS flag. + $footer .= Zs\ExtendedInformationExtraField::generate(); } - $force = (bool)($this->bits & self::BIT_ZERO_HEADER) && - $this->zip->opt->isEnableZip64(); + $data = LocalFileHeader::generate( + versionNeededToExtract: $this->version->value, + generalPurposeBitFlag: $this->generalPurposeBitFlag, + compressionMethod: $this->compressionMethod, + lastModificationDateTime: $this->lastModificationDateTime, + crc32UncompressedData: $this->crc, + compressedSize: $zip64Enabled + ? 0xFFFFFFFF + : $this->compressedSize, + uncompressedSize: $zip64Enabled + ? 0xFFFFFFFF + : $this->uncompressedSize, + fileName: $this->fileName, + extraField: $footer, + ); - $footer = $this->buildZip64ExtraBlock($force); - // If this file will start over 4GB limit in ZIP file, - // CDR record will have to use Zip64 extension to describe offset - // to keep consistency we use the same value here - if ($this->zip->ofs->isOver32()) { - $this->version = Version::ZIP64(); - } - - $fields = [ - ['V', ZipStream::FILE_HEADER_SIGNATURE], - ['v', $this->version->getValue()], // Version needed to Extract - ['v', $this->bits], // General purpose bit flags - data descriptor flag set - ['v', $this->method->getValue()], // Compression method - ['V', $time], // Timestamp (DOS Format) - ['V', $this->crc], // CRC32 of data (0 -> moved to data descriptor footer) - ['V', $this->zlen->getLowFF($force)], // Length of compressed data (forced to 0xFFFFFFFF for zero header) - ['V', $this->len->getLowFF($force)], // Length of original data (forced to 0xFFFFFFFF for zero header) - ['v', $nameLength], // Length of filename - ['v', strlen($footer)], // Extra data (see above) - ]; - - // pack fields and calculate "total" length - $header = ZipStream::packFields($fields); - - // print header and filename - $data = $header . $name . $footer; - $this->zip->send($data); - - // save header length - $this->hlen = Bigint::init(strlen($data)); + ($this->send)($data); } /** * Strip characters that are not legal in Windows filenames * to prevent compatibility issues - * - * @param string $filename Unprocessed filename - * @return string */ - public static function filterFilename(string $filename): string - { + private static function filterFilename( + /** + * Unprocessed filename + */ + string $fileName + ): string { // strip leading slashes from file name // (fixes bug in windows archive viewer) - $filename = preg_replace('/^\\/+/', '', $filename); + $fileName = ltrim($fileName, '/'); - return str_replace(['\\', ':', '*', '?', '"', '<', '>', '|'], '_', $filename); + return str_replace(['\\', ':', '*', '?', '"', '<', '>', '|'], '_', $fileName); } - /** - * Create and send data descriptor footer for this file. - * - * @return void - */ - public function addFileFooter(): void + private function checkEncoding(): void { - if ($this->bits & self::BIT_ZERO_HEADER) { - // compressed and uncompressed size - $sizeFormat = 'V'; - if ($this->zip->opt->isEnableZip64()) { - $sizeFormat = 'P'; + // Sets Bit 11: Language encoding flag (EFS). If this bit is set, + // the filename and comment fields for this file + // MUST be encoded using UTF-8. (see APPENDIX D) + if (mb_check_encoding($this->fileName, 'UTF-8') && + mb_check_encoding($this->comment, 'UTF-8')) { + $this->generalPurposeBitFlag |= GeneralPurposeBitFlag::EFS; + } + } + + private function buildZip64ExtraBlock(bool $force = false): string + { + $outputZip64ExtraBlock = false; + + $originalSize = null; + if ($force || $this->uncompressedSize > 0xFFFFFFFF) { + $outputZip64ExtraBlock = true; + $originalSize = $this->uncompressedSize; + } + + $compressedSize = null; + if ($force || $this->compressedSize > 0xFFFFFFFF) { + $outputZip64ExtraBlock = true; + $compressedSize = $this->compressedSize; + } + + // If this file will start over 4GB limit in ZIP file, + // CDR record will have to use Zip64 extension to describe offset + // to keep consistency we use the same value here + $relativeHeaderOffset = null; + if ($this->startOffset > 0xFFFFFFFF) { + $outputZip64ExtraBlock = true; + $relativeHeaderOffset = $this->startOffset; + } + + if (!$outputZip64ExtraBlock) { + return ''; + } + + if (!$this->enableZip64) { + throw new OverflowException(); + } + + return Zip64\ExtendedInformationExtraField::generate( + originalSize: $originalSize, + compressedSize: $compressedSize, + relativeHeaderOffset: $relativeHeaderOffset, + diskStartNumber: null, + ); + } + + private function addFileFooter(): void + { + if (($this->compressedSize > 0xFFFFFFFF || $this->uncompressedSize > 0xFFFFFFFF) && $this->version !== Version::ZIP64) { + throw new OverflowException(); + } + + if (!$this->enableZeroHeader) { + return; + } + + if ($this->version === Version::ZIP64) { + $footer = Zip64\DataDescriptor::generate( + crc32UncompressedData: $this->crc, + compressedSize: $this->compressedSize, + uncompressedSize: $this->uncompressedSize, + ); + } else { + $footer = DataDescriptor::generate( + crc32UncompressedData: $this->crc, + compressedSize: $this->compressedSize, + uncompressedSize: $this->uncompressedSize, + ); + } + + ($this->send)($footer); + } + + private function readStream(bool $send): void + { + $this->compressedSize = 0; + $this->uncompressedSize = 0; + $hash = hash_init('crc32b'); + + $deflate = $this->compressionInit(); + + while ( + !feof($this->unpackStream()) && + ($this->maxSize === null || $this->uncompressedSize < $this->maxSize) && + ($this->exactSize === null || $this->uncompressedSize < $this->exactSize) + ) { + $readLength = min( + ($this->maxSize ?? PHP_INT_MAX) - $this->uncompressedSize, + ($this->exactSize ?? PHP_INT_MAX) - $this->uncompressedSize, + self::CHUNKED_READ_BLOCK_SIZE + ); + + $data = fread($this->unpackStream(), $readLength); + + if ($data === false) { + throw new ResourceActionException('fread', $this->unpackStream()); } - $fields = [ - ['V', ZipStream::DATA_DESCRIPTOR_SIGNATURE], - ['V', $this->crc], // CRC32 - [$sizeFormat, $this->zlen], // Length of compressed data - [$sizeFormat, $this->len], // Length of original data - ]; - $footer = ZipStream::packFields($fields); - $this->zip->send($footer); - } else { - $footer = ''; + hash_update($hash, $data); + + $this->uncompressedSize += strlen($data); + + if ($deflate) { + $data = deflate_add( + $deflate, + $data, + feof($this->unpackStream()) ? ZLIB_FINISH : ZLIB_NO_FLUSH + ); + + if ($data === false) { + throw new RuntimeException('deflate_add failed'); + } + } + + $this->compressedSize += strlen($data); + + if ($send) { + ($this->send)($data); + } } - $this->totalLength = $this->hlen->add($this->zlen)->add(Bigint::init(strlen($footer))); - $this->zip->addToCdr($this); + + if ($this->exactSize !== null && $this->uncompressedSize !== $this->exactSize) { + throw new FileSizeIncorrectException(expectedSize: $this->exactSize, actualSize: $this->uncompressedSize); + } + + $this->crc = hexdec(hash_final($hash)); } - public function processStream(StreamInterface $stream): void + private function compressionInit(): ?DeflateContext { - $this->zlen = new Bigint(); - $this->len = new Bigint(); + switch ($this->compressionMethod) { + case CompressionMethod::STORE: + // Noting to do + return null; + case CompressionMethod::DEFLATE: + $deflateContext = deflate_init( + ZLIB_ENCODING_RAW, + ['level' => $this->deflateLevel] + ); - if ($this->zip->opt->isZeroHeader()) { - $this->processStreamWithZeroHeader($stream); - } else { - $this->processStreamWithComputedHeader($stream); + if (!$deflateContext) { + // @codeCoverageIgnoreStart + throw new RuntimeException("Can't initialize deflate context."); + // @codeCoverageIgnoreEnd + } + + // False positive, resource is no longer returned from this function + return $deflateContext; + default: + // @codeCoverageIgnoreStart + throw new RuntimeException('Unsupported Compression Method ' . print_r($this->compressionMethod, true)); + // @codeCoverageIgnoreEnd } } - /** - * Send CDR record for specified file. - * - * @return string - */ - public function getCdrFile(): string + private function getCdrFile(): string { - $name = static::filterFilename($this->name); - - // get attributes - $comment = $this->opt->getComment(); - - // get dos timestamp - $time = static::dosTime($this->opt->getTime()->getTimestamp()); - $footer = $this->buildZip64ExtraBlock(); - $fields = [ - ['V', ZipStream::CDR_FILE_SIGNATURE], // Central file header signature - ['v', ZipStream::ZIP_VERSION_MADE_BY], // Made by version - ['v', $this->version->getValue()], // Extract by version - ['v', $this->bits], // General purpose bit flags - data descriptor flag set - ['v', $this->method->getValue()], // Compression method - ['V', $time], // Timestamp (DOS Format) - ['V', $this->crc], // CRC32 - ['V', $this->zlen->getLowFF()], // Compressed Data Length - ['V', $this->len->getLowFF()], // Original Data Length - ['v', strlen($name)], // Length of filename - ['v', strlen($footer)], // Extra data len (see above) - ['v', strlen($comment)], // Length of comment - ['v', 0], // Disk number - ['v', 0], // Internal File Attributes - ['V', 32], // External File Attributes - ['V', $this->ofs->getLowFF()], // Relative offset of local header - ]; - - // pack fields, then append name and comment - $header = ZipStream::packFields($fields); - - return $header . $name . $footer . $comment; + return CentralDirectoryFileHeader::generate( + versionMadeBy: ZipStream::ZIP_VERSION_MADE_BY, + versionNeededToExtract: $this->version->value, + generalPurposeBitFlag: $this->generalPurposeBitFlag, + compressionMethod: $this->compressionMethod, + lastModificationDateTime: $this->lastModificationDateTime, + crc32: $this->crc, + compressedSize: $this->compressedSize > 0xFFFFFFFF + ? 0xFFFFFFFF + : $this->compressedSize, + uncompressedSize: $this->uncompressedSize > 0xFFFFFFFF + ? 0xFFFFFFFF + : $this->uncompressedSize, + fileName: $this->fileName, + extraField: $footer, + fileComment: $this->comment, + diskNumberStart: 0, + internalFileAttributes: 0, + externalFileAttributes: 32, + relativeOffsetOfLocalHeader: $this->startOffset > 0xFFFFFFFF + ? 0xFFFFFFFF + : $this->startOffset, + ); } - /** - * @return Bigint - */ - public function getTotalLength(): Bigint + private function isSimulation(): bool { - return $this->totalLength; - } - - /** - * Convert a UNIX timestamp to a DOS timestamp. - * - * @param int $when - * @return int DOS Timestamp - */ - final protected static function dosTime(int $when): int - { - // get date array for timestamp - $d = getdate($when); - - // set lower-bound on dates - if ($d['year'] < 1980) { - $d = [ - 'year' => 1980, - 'mon' => 1, - 'mday' => 1, - 'hours' => 0, - 'minutes' => 0, - 'seconds' => 0, - ]; - } - - // remove extra years from 1980 - $d['year'] -= 1980; - - // return date string - return - ($d['year'] << 25) | - ($d['mon'] << 21) | - ($d['mday'] << 16) | - ($d['hours'] << 11) | - ($d['minutes'] << 5) | - ($d['seconds'] >> 1); - } - - protected function buildZip64ExtraBlock(bool $force = false): string - { - $fields = []; - if ($this->len->isOver32($force)) { - $fields[] = ['P', $this->len]; // Length of original data - } - - if ($this->len->isOver32($force)) { - $fields[] = ['P', $this->zlen]; // Length of compressed data - } - - if ($this->ofs->isOver32()) { - $fields[] = ['P', $this->ofs]; // Offset of local header record - } - - if (!empty($fields)) { - if (!$this->zip->opt->isEnableZip64()) { - throw new OverflowException(); - } - - array_unshift( - $fields, - ['v', 0x0001], // 64 bit extension - ['v', count($fields) * 8] // Length of data block - ); - $this->version = Version::ZIP64(); - } - - if ($this->bits & self::BIT_EFS_UTF8) { - // Put the tricky entry to - // force Linux unzip to lookup EFS flag. - $fields[] = ['v', 0x5653]; // Choose 'ZS' for proprietary usage - $fields[] = ['v', 0x0000]; // zero length - } - - return ZipStream::packFields($fields); - } - - protected function processStreamWithZeroHeader(StreamInterface $stream): void - { - $this->bits |= self::BIT_ZERO_HEADER; - $this->addFileHeader(); - $this->readStream($stream, self::COMPUTE | self::SEND); - $this->addFileFooter(); - } - - protected function readStream(StreamInterface $stream, ?int $options = null): void - { - $this->deflateInit(); - $total = 0; - $size = $this->opt->getSize(); - while (!$stream->eof() && ($size === 0 || $total < $size)) { - $data = $stream->read(self::CHUNKED_READ_BLOCK_SIZE); - $total += strlen($data); - if ($size > 0 && $total > $size) { - $data = substr($data, 0, strlen($data)-($total - $size)); - } - $this->deflateData($stream, $data, $options); - if ($options & self::SEND) { - $this->zip->send($data); - } - } - $this->deflateFinish($options); - } - - protected function deflateInit(): void - { - $hash = hash_init(self::HASH_ALGORITHM); - $this->hash = $hash; - if ($this->method->equals(Method::DEFLATE())) { - $this->deflate = deflate_init( - ZLIB_ENCODING_RAW, - ['level' => $this->opt->getDeflateLevel()] - ); - } - } - - protected function deflateData(StreamInterface $stream, string &$data, ?int $options = null): void - { - if ($options & self::COMPUTE) { - $this->len = $this->len->add(Bigint::init(strlen($data))); - hash_update($this->hash, $data); - } - if ($this->deflate) { - $data = deflate_add( - $this->deflate, - $data, - $stream->eof() - ? ZLIB_FINISH - : ZLIB_NO_FLUSH - ); - } - if ($options & self::COMPUTE) { - $this->zlen = $this->zlen->add(Bigint::init(strlen($data))); - } - } - - protected function deflateFinish(?int $options = null): void - { - if ($options & self::COMPUTE) { - $this->crc = hexdec(hash_final($this->hash)); - } - } - - protected function processStreamWithComputedHeader(StreamInterface $stream): void - { - $this->readStream($stream, self::COMPUTE); - $stream->rewind(); - - $this->addFileHeader(); - $this->readStream($stream, self::SEND); - $this->addFileFooter(); + return $this->operationMode === OperationMode::SIMULATE_LAX || $this->operationMode === OperationMode::SIMULATE_STRICT; } } diff --git a/vendor/maennchen/zipstream-php/src/GeneralPurposeBitFlag.php b/vendor/maennchen/zipstream-php/src/GeneralPurposeBitFlag.php new file mode 100644 index 0000000..23a66d8 --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/GeneralPurposeBitFlag.php @@ -0,0 +1,89 @@ +value), + new PackField(format: 'V', value: Time::dateTimeToDosTime($lastModificationDateTime)), + new PackField(format: 'V', value: $crc32UncompressedData), + new PackField(format: 'V', value: $compressedSize), + new PackField(format: 'V', value: $uncompressedSize), + new PackField(format: 'v', value: strlen($fileName)), + new PackField(format: 'v', value: strlen($extraField)), + ) . $fileName . $extraField; + } +} diff --git a/vendor/maennchen/zipstream-php/src/OperationMode.php b/vendor/maennchen/zipstream-php/src/OperationMode.php new file mode 100644 index 0000000..dd650f0 --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/OperationMode.php @@ -0,0 +1,35 @@ + 4 GB or file count > 64k) - * - * @var bool - */ - private $enableZip64 = true; - - /** - * Enable streaming files with single read where - * general purpose bit 3 indicates local file header - * contain zero values in crc and size fields, - * these appear only after file contents - * in data descriptor block. - * - * @var bool - */ - private $zeroHeader = false; - - /** - * Enable reading file stat for determining file size. - * When a 32-bit system reads file size that is - * over 2 GB, invalid value appears in file size - * due to integer overflow. Should be disabled on - * 32-bit systems with method addFileFromPath - * if any file may exceed 2 GB. In this case file - * will be read in blocks and correct size will be - * determined from content. - * - * @var bool - */ - private $statFiles = true; - - /** - * Enable flush after every write to output stream. - * @var bool - */ - private $flushOutput = false; - - /** - * HTTP Content-Disposition. Defaults to - * 'attachment', where - * FILENAME is the specified filename. - * - * Note that this does nothing if you are - * not sending HTTP headers. - * - * @var string - */ - private $contentDisposition = 'attachment'; - - /** - * Note that this does nothing if you are - * not sending HTTP headers. - * - * @var string - */ - private $contentType = 'application/x-zip'; - - /** - * @var int - */ - private $deflateLevel = 6; - - /** - * @var StreamInterface|resource - */ - private $outputStream; - - /** - * Options constructor. - */ - public function __construct() - { - $this->largeFileMethod = Method::STORE(); - $this->outputStream = fopen('php://output', 'wb'); - } - - public function getComment(): string - { - return $this->comment; - } - - public function setComment(string $comment): void - { - $this->comment = $comment; - } - - public function getLargeFileSize(): int - { - return $this->largeFileSize; - } - - public function setLargeFileSize(int $largeFileSize): void - { - $this->largeFileSize = $largeFileSize; - } - - public function getLargeFileMethod(): Method - { - return $this->largeFileMethod; - } - - public function setLargeFileMethod(Method $largeFileMethod): void - { - $this->largeFileMethod = $largeFileMethod; - } - - public function isSendHttpHeaders(): bool - { - return $this->sendHttpHeaders; - } - - public function setSendHttpHeaders(bool $sendHttpHeaders): void - { - $this->sendHttpHeaders = $sendHttpHeaders; - } - - public function getHttpHeaderCallback(): callable - { - return $this->httpHeaderCallback; - } - - public function setHttpHeaderCallback(callable $httpHeaderCallback): void - { - $this->httpHeaderCallback = $httpHeaderCallback; - } - - public function isEnableZip64(): bool - { - return $this->enableZip64; - } - - public function setEnableZip64(bool $enableZip64): void - { - $this->enableZip64 = $enableZip64; - } - - public function isZeroHeader(): bool - { - return $this->zeroHeader; - } - - public function setZeroHeader(bool $zeroHeader): void - { - $this->zeroHeader = $zeroHeader; - } - - public function isFlushOutput(): bool - { - return $this->flushOutput; - } - - public function setFlushOutput(bool $flushOutput): void - { - $this->flushOutput = $flushOutput; - } - - public function isStatFiles(): bool - { - return $this->statFiles; - } - - public function setStatFiles(bool $statFiles): void - { - $this->statFiles = $statFiles; - } - - public function getContentDisposition(): string - { - return $this->contentDisposition; - } - - public function setContentDisposition(string $contentDisposition): void - { - $this->contentDisposition = $contentDisposition; - } - - public function getContentType(): string - { - return $this->contentType; - } - - public function setContentType(string $contentType): void - { - $this->contentType = $contentType; - } - - /** - * @return StreamInterface|resource - */ - public function getOutputStream() - { - return $this->outputStream; - } - - /** - * @param StreamInterface|resource $outputStream - */ - public function setOutputStream($outputStream): void - { - $this->outputStream = $outputStream; - } - - /** - * @return int - */ - public function getDeflateLevel(): int - { - return $this->deflateLevel; - } - - /** - * @param int $deflateLevel - */ - public function setDeflateLevel(int $deflateLevel): void - { - $this->deflateLevel = $deflateLevel; - } -} diff --git a/vendor/maennchen/zipstream-php/src/Option/File.php b/vendor/maennchen/zipstream-php/src/Option/File.php deleted file mode 100644 index 37e37ce..0000000 --- a/vendor/maennchen/zipstream-php/src/Option/File.php +++ /dev/null @@ -1,122 +0,0 @@ -deflateLevel = $this->deflateLevel ?: $archiveOptions->getDeflateLevel(); - $this->time = $this->time ?: new DateTime(); - } - - /** - * @return string - */ - public function getComment(): string - { - return $this->comment; - } - - /** - * @param string $comment - */ - public function setComment(string $comment): void - { - $this->comment = $comment; - } - - /** - * @return Method - */ - public function getMethod(): Method - { - return $this->method ?: Method::DEFLATE(); - } - - /** - * @param Method $method - */ - public function setMethod(Method $method): void - { - $this->method = $method; - } - - /** - * @return int - */ - public function getDeflateLevel(): int - { - return $this->deflateLevel ?: Archive::DEFAULT_DEFLATE_LEVEL; - } - - /** - * @param int $deflateLevel - */ - public function setDeflateLevel(int $deflateLevel): void - { - $this->deflateLevel = $deflateLevel; - } - - /** - * @return DateTimeInterface - */ - public function getTime(): DateTimeInterface - { - return $this->time; - } - - /** - * @param DateTimeInterface $time - */ - public function setTime(DateTimeInterface $time): void - { - $this->time = $time; - } - - /** - * @return int - */ - public function getSize(): int - { - return $this->size; - } - - /** - * @param int $size - */ - public function setSize(int $size): void - { - $this->size = $size; - } -} diff --git a/vendor/maennchen/zipstream-php/src/Option/Method.php b/vendor/maennchen/zipstream-php/src/Option/Method.php deleted file mode 100644 index 0dfce1b..0000000 --- a/vendor/maennchen/zipstream-php/src/Option/Method.php +++ /dev/null @@ -1,23 +0,0 @@ - - */ -class Method extends Enum -{ - public const STORE = 0x00; - - public const DEFLATE = 0x08; -} diff --git a/vendor/maennchen/zipstream-php/src/Option/Version.php b/vendor/maennchen/zipstream-php/src/Option/Version.php deleted file mode 100644 index f3daa85..0000000 --- a/vendor/maennchen/zipstream-php/src/Option/Version.php +++ /dev/null @@ -1,27 +0,0 @@ - - */ -class Version extends Enum -{ - public const STORE = 0x000A; // 1.00 - - public const DEFLATE = 0x0014; // 2.00 - - public const ZIP64 = 0x002D; // 4.50 -} diff --git a/vendor/maennchen/zipstream-php/src/PackField.php b/vendor/maennchen/zipstream-php/src/PackField.php new file mode 100644 index 0000000..892b400 --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/PackField.php @@ -0,0 +1,56 @@ +format; + }, ''); + + $args = array_map(function (self $field) { + switch ($field->format) { + case 'V': + if ($field->value > self::MAX_V) { + throw new RuntimeException(print_r($field->value, true) . ' is larger than 32 bits'); + } + break; + case 'v': + if ($field->value > self::MAX_v) { + throw new RuntimeException(print_r($field->value, true) . ' is larger than 16 bits'); + } + break; + case 'P': break; + default: + break; + } + + return $field->value; + }, $fields); + + return pack($fmt, ...$args); + } +} diff --git a/vendor/maennchen/zipstream-php/src/Stream.php b/vendor/maennchen/zipstream-php/src/Stream.php deleted file mode 100644 index d80e70f..0000000 --- a/vendor/maennchen/zipstream-php/src/Stream.php +++ /dev/null @@ -1,265 +0,0 @@ -stream = $stream; - } - - /** - * Reads all data from the stream into a string, from the beginning to end. - * - * This method MUST attempt to seek to the beginning of the stream before - * reading data and read the stream until the end is reached. - * - * Warning: This could attempt to load a large amount of data into memory. - * - * This method MUST NOT raise an exception in order to conform with PHP's - * string casting operations. - * - * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring - * @return string - */ - public function __toString(): string - { - try { - $this->seek(0); - } catch (RuntimeException $e) { - } - return (string) stream_get_contents($this->stream); - } - - /** - * Closes the stream and any underlying resources. - * - * @return void - */ - public function close(): void - { - if (is_resource($this->stream)) { - fclose($this->stream); - } - $this->detach(); - } - - /** - * Separates any underlying resources from the stream. - * - * After the stream has been detached, the stream is in an unusable state. - * - * @return resource|null Underlying PHP stream, if any - */ - public function detach() - { - $result = $this->stream; - $this->stream = null; - return $result; - } - - /** - * Seek to a position in the stream. - * - * @link http://www.php.net/manual/en/function.fseek.php - * @param int $offset Stream offset - * @param int $whence Specifies how the cursor position will be calculated - * based on the seek offset. Valid values are identical to the built-in - * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to - * offset bytes SEEK_CUR: Set position to current location plus offset - * SEEK_END: Set position to end-of-stream plus offset. - * @throws RuntimeException on failure. - */ - public function seek($offset, $whence = SEEK_SET): void - { - if (!$this->isSeekable()) { - throw new RuntimeException(); - } - if (fseek($this->stream, $offset, $whence) !== 0) { - throw new RuntimeException(); - } - } - - /** - * Returns whether or not the stream is seekable. - * - * @return bool - */ - public function isSeekable(): bool - { - return (bool)$this->getMetadata('seekable'); - } - - /** - * Get stream metadata as an associative array or retrieve a specific key. - * - * The keys returned are identical to the keys returned from PHP's - * stream_get_meta_data() function. - * - * @link http://php.net/manual/en/function.stream-get-meta-data.php - * @param string $key Specific metadata to retrieve. - * @return array|mixed|null Returns an associative array if no key is - * provided. Returns a specific key value if a key is provided and the - * value is found, or null if the key is not found. - */ - public function getMetadata($key = null) - { - $metadata = stream_get_meta_data($this->stream); - return $key !== null ? @$metadata[$key] : $metadata; - } - - /** - * Get the size of the stream if known. - * - * @return int|null Returns the size in bytes if known, or null if unknown. - */ - public function getSize(): ?int - { - $stats = fstat($this->stream); - return $stats['size']; - } - - /** - * Returns the current position of the file read/write pointer - * - * @return int Position of the file pointer - * @throws RuntimeException on error. - */ - public function tell(): int - { - $position = ftell($this->stream); - if ($position === false) { - throw new RuntimeException(); - } - return $position; - } - - /** - * Returns true if the stream is at the end of the stream. - * - * @return bool - */ - public function eof(): bool - { - return feof($this->stream); - } - - /** - * Seek to the beginning of the stream. - * - * If the stream is not seekable, this method will raise an exception; - * otherwise, it will perform a seek(0). - * - * @see seek() - * @link http://www.php.net/manual/en/function.fseek.php - * @throws RuntimeException on failure. - */ - public function rewind(): void - { - $this->seek(0); - } - - /** - * Write data to the stream. - * - * @param string $string The string that is to be written. - * @return int Returns the number of bytes written to the stream. - * @throws RuntimeException on failure. - */ - public function write($string): int - { - if (!$this->isWritable()) { - throw new RuntimeException(); - } - if (fwrite($this->stream, $string) === false) { - throw new RuntimeException(); - } - return mb_strlen($string); - } - - /** - * Returns whether or not the stream is writable. - * - * @return bool - */ - public function isWritable(): bool - { - $mode = $this->getMetadata('mode'); - if (!is_string($mode)) { - throw new RuntimeException('Could not get stream mode from metadata!'); - } - return preg_match('/[waxc+]/', $mode) === 1; - } - - /** - * Read data from the stream. - * - * @param int $length Read up to $length bytes from the object and return - * them. Fewer than $length bytes may be returned if underlying stream - * call returns fewer bytes. - * @return string Returns the data read from the stream, or an empty string - * if no bytes are available. - * @throws RuntimeException if an error occurs. - */ - public function read($length): string - { - if (!$this->isReadable()) { - throw new RuntimeException(); - } - $result = fread($this->stream, $length); - if ($result === false) { - throw new RuntimeException(); - } - return $result; - } - - /** - * Returns whether or not the stream is readable. - * - * @return bool - */ - public function isReadable(): bool - { - $mode = $this->getMetadata('mode'); - if (!is_string($mode)) { - throw new RuntimeException('Could not get stream mode from metadata!'); - } - return preg_match('/[r+]/', $mode) === 1; - } - - /** - * Returns the remaining contents in a string - * - * @return string - * @throws RuntimeException if unable to read or an error occurs while - * reading. - */ - public function getContents(): string - { - if (!$this->isReadable()) { - throw new RuntimeException(); - } - $result = stream_get_contents($this->stream); - if ($result === false) { - throw new RuntimeException(); - } - return $result; - } -} diff --git a/vendor/maennchen/zipstream-php/src/Time.php b/vendor/maennchen/zipstream-php/src/Time.php new file mode 100644 index 0000000..1b4121c --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Time.php @@ -0,0 +1,39 @@ +getTimestamp() < $dosMinimumDate->getTimestamp()) { + throw new DosTimeOverflowException(dateTime: $dateTime); + } + + $dateTime = DateTimeImmutable::createFromInterface($dateTime)->sub(new DateInterval('P1980Y')); + + [$year, $month, $day, $hour, $minute, $second] = explode(' ', $dateTime->format('Y n j G i s')); + + return + ((int) $year << 25) | + ((int) $month << 21) | + ((int) $day << 16) | + ((int) $hour << 11) | + ((int) $minute << 5) | + ((int) $second >> 1); + } +} diff --git a/vendor/maennchen/zipstream-php/src/Version.php b/vendor/maennchen/zipstream-php/src/Version.php new file mode 100644 index 0000000..c014f8a --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Version.php @@ -0,0 +1,12 @@ +addFile('some_file.gif', $data); + * ```php + * // add first file + * $zip->addFile(fileName: 'world.txt', data: 'Hello World'); * - * * add second file - * $data = file_get_contents('some_file.gif'); - * $zip->addFile('another_file.png', $data); + * // add second file + * $zip->addFile(fileName: 'moon.txt', data: 'Hello Moon'); + * ``` * * 3. Finish the zip stream: * - * $zip->finish(); + * ```php + * $zip->finish(); + * ``` * * You can also add an archive comment, add comments to individual files, * and adjust the timestamp of files. See the API documentation for each * method below for additional information. * - * Example: + * ## Example * - * // create a new zip stream object - * $zip = new ZipStream('some_files.zip'); + * ```php + * // create a new zip stream object + * $zip = new ZipStream(outputName: 'some_files.zip'); * - * // list of local files - * $files = array('foo.txt', 'bar.jpg'); + * // list of local files + * $files = array('foo.txt', 'bar.jpg'); * - * // read and add each file to the archive - * foreach ($files as $path) - * $zip->addFile($path, file_get_contents($path)); + * // read and add each file to the archive + * foreach ($files as $path) + * $zip->addFileFromPath(fileName: $path, $path); * - * // write archive footer to stream - * $zip->finish(); + * // write archive footer to stream + * $zip->finish(); + * ``` */ class ZipStream { @@ -80,529 +89,783 @@ class ZipStream * Here we are using 6 for the OS, indicating OS/2 H.P.F.S. * to prevent file permissions issues upon extract (see #84) * 0x603 is 00000110 00000011 in binary, so 6 and 3 + * + * @internal */ public const ZIP_VERSION_MADE_BY = 0x603; - /** - * The following signatures end with 0x4b50, which in ASCII is PK, - * the initials of the inventor Phil Katz. - * See https://en.wikipedia.org/wiki/Zip_(file_format)#File_headers - */ - public const FILE_HEADER_SIGNATURE = 0x04034b50; + private bool $ready = true; - public const CDR_FILE_SIGNATURE = 0x02014b50; - - public const CDR_EOF_SIGNATURE = 0x06054b50; - - public const DATA_DESCRIPTOR_SIGNATURE = 0x08074b50; - - public const ZIP64_CDR_EOF_SIGNATURE = 0x06064b50; - - public const ZIP64_CDR_LOCATOR_SIGNATURE = 0x07064b50; + private int $offset = 0; /** - * Global Options - * - * @var ArchiveOptions + * @var string[] */ - public $opt; + private array $centralDirectoryRecords = []; /** - * @var array + * @var resource */ - public $files = []; + private $outputStream; + + private readonly Closure $httpHeaderCallback; /** - * @var Bigint + * @var File[] */ - public $cdr_ofs; - - /** - * @var Bigint - */ - public $ofs; - - /** - * @var bool - */ - protected $need_headers; - - /** - * @var null|String - */ - protected $output_name; + private array $recordedSimulation = []; /** * Create a new ZipStream object. * - * Parameters: + * ##### Examples * - * @param String $name - Name of output file (optional). - * @param ArchiveOptions $opt - Archive Options + * ```php + * // create a new zip file named 'foo.zip' + * $zip = new ZipStream(outputName: 'foo.zip'); * - * Large File Support: + * // create a new zip file named 'bar.zip' with a comment + * $zip = new ZipStream( + * outputName: 'bar.zip', + * comment: 'this is a comment for the zip file.', + * ); + * ``` * - * By default, the method addFileFromPath() will send send files - * larger than 20 megabytes along raw rather than attempting to - * compress them. You can change both the maximum size and the - * compression behavior using the largeFile* options above, with the - * following caveats: + * @param OperationMode $operationMode + * The mode can be used to switch between `NORMAL` and `SIMULATION_*` modes. + * For details see the `OperationMode` documentation. * - * * For "small" files (e.g. files smaller than largeFileSize), the - * memory use can be up to twice that of the actual file. In other - * words, adding a 10 megabyte file to the archive could potentially - * occupy 20 megabytes of memory. + * Default to `NORMAL`. * - * * Enabling compression on large files (e.g. files larger than - * large_file_size) is extremely slow, because ZipStream has to pass - * over the large file once to calculate header information, and then - * again to compress and send the actual data. + * @param string $comment + * Archive Level Comment * - * Examples: + * @param StreamInterface|resource|null $outputStream + * Override the output of the archive to a different target. * - * // create a new zip file named 'foo.zip' - * $zip = new ZipStream('foo.zip'); + * By default the archive is sent to `STDOUT`. * - * // create a new zip file named 'bar.zip' with a comment - * $opt->setComment = 'this is a comment for the zip file.'; - * $zip = new ZipStream('bar.zip', $opt); + * @param CompressionMethod $defaultCompressionMethod + * How to handle file compression. Legal values are + * `CompressionMethod::DEFLATE` (the default), or + * `CompressionMethod::STORE`. `STORE` sends the file raw and is + * significantly faster, while `DEFLATE` compresses the file and + * is much, much slower. * - * Notes: + * @param int $defaultDeflateLevel + * Default deflation level. Only relevant if `compressionMethod` + * is `DEFLATE`. * - * In order to let this library send HTTP headers, a filename must be given - * _and_ the option `sendHttpHeaders` must be `true`. This behavior is to - * allow software to send its own headers (including the filename), and - * still use this library. + * See details of [`deflate_init`](https://www.php.net/manual/en/function.deflate-init.php#refsect1-function.deflate-init-parameters) + * + * @param bool $enableZip64 + * Enable Zip64 extension, supporting very large + * archives (any size > 4 GB or file count > 64k) + * + * @param bool $defaultEnableZeroHeader + * Enable streaming files with single read. + * + * When the zero header is set, the file is streamed into the output + * and the size & checksum are added at the end of the file. This is the + * fastest method and uses the least memory. Unfortunately not all + * ZIP clients fully support this and can lead to clients reporting + * the generated ZIP files as corrupted in combination with other + * circumstances. (Zip64 enabled, using UTF8 in comments / names etc.) + * + * When the zero header is not set, the length & checksum need to be + * defined before the file is actually added. To prevent loading all + * the data into memory, the data has to be read twice. If the data + * which is added is not seekable, this call will fail. + * + * @param bool $sendHttpHeaders + * Boolean indicating whether or not to send + * the HTTP headers for this file. + * + * @param ?Closure $httpHeaderCallback + * The method called to send HTTP headers + * + * @param string|null $outputName + * The name of the created archive. + * + * Only relevant if `$sendHttpHeaders = true`. + * + * @param string $contentDisposition + * HTTP Content-Disposition + * + * Only relevant if `sendHttpHeaders = true`. + * + * @param string $contentType + * HTTP Content Type + * + * Only relevant if `sendHttpHeaders = true`. + * + * @param bool $flushOutput + * Enable flush after every write to output stream. + * + * @return self */ - public function __construct(?string $name = null, ?ArchiveOptions $opt = null) - { - $this->opt = $opt ?: new ArchiveOptions(); - - $this->output_name = $name; - $this->need_headers = $name && $this->opt->isSendHttpHeaders(); - - $this->cdr_ofs = new Bigint(); - $this->ofs = new Bigint(); + public function __construct( + private OperationMode $operationMode = OperationMode::NORMAL, + private readonly string $comment = '', + $outputStream = null, + private readonly CompressionMethod $defaultCompressionMethod = CompressionMethod::DEFLATE, + private readonly int $defaultDeflateLevel = 6, + private readonly bool $enableZip64 = true, + private readonly bool $defaultEnableZeroHeader = true, + private bool $sendHttpHeaders = true, + ?Closure $httpHeaderCallback = null, + private readonly ?string $outputName = null, + private readonly string $contentDisposition = 'attachment', + private readonly string $contentType = 'application/x-zip', + private bool $flushOutput = false, + ) { + $this->outputStream = self::normalizeStream($outputStream); + $this->httpHeaderCallback = $httpHeaderCallback ?? header(...); } /** - * addFile - * * Add a file to the archive. * - * @param String $name - path of file in archive (including directory). - * @param String $data - contents of file - * @param FileOptions $options + * ##### File Options * - * File Options: - * time - Last-modified timestamp (seconds since the epoch) of - * this file. Defaults to the current time. - * comment - Comment related to this file. - * method - Storage method for file ("store" or "deflate") + * See {@see addFileFromPsr7Stream()} * - * Examples: + * ##### Examples * - * // add a file named 'foo.txt' - * $data = file_get_contents('foo.txt'); - * $zip->addFile('foo.txt', $data); + * ```php + * // add a file named 'world.txt' + * $zip->addFile(fileName: 'world.txt', data: 'Hello World!'); * - * // add a file named 'bar.jpg' with a comment and a last-modified - * // time of two hours ago - * $data = file_get_contents('bar.jpg'); - * $opt->setTime = time() - 2 * 3600; - * $opt->setComment = 'this is a comment about bar.jpg'; - * $zip->addFile('bar.jpg', $data, $opt); + * // add a file named 'bar.jpg' with a comment and a last-modified + * // time of two hours ago + * $zip->addFile( + * fileName: 'bar.jpg', + * data: $data, + * comment: 'this is a comment about bar.jpg', + * lastModificationDateTime: new DateTime('2 hours ago'), + * ); + * ``` + * + * @param string $data + * + * contents of file */ - public function addFile(string $name, string $data, ?FileOptions $options = null): void - { - $options = $options ?: new FileOptions(); - $options->defaultTo($this->opt); - - $file = new File($this, $name, $options); - $file->processData($data); + public function addFile( + string $fileName, + string $data, + string $comment = '', + ?CompressionMethod $compressionMethod = null, + ?int $deflateLevel = null, + ?DateTimeInterface $lastModificationDateTime = null, + ?int $maxSize = null, + ?int $exactSize = null, + ?bool $enableZeroHeader = null, + ): void { + $this->addFileFromCallback( + fileName: $fileName, + callback: fn() => $data, + comment: $comment, + compressionMethod: $compressionMethod, + deflateLevel: $deflateLevel, + lastModificationDateTime: $lastModificationDateTime, + maxSize: $maxSize, + exactSize: $exactSize, + enableZeroHeader: $enableZeroHeader, + ); } /** - * addFileFromPath - * * Add a file at path to the archive. * - * Note that large files may be compressed differently than smaller - * files; see the "Large File Support" section above for more - * information. + * ##### File Options * - * @param String $name - name of file in archive (including directory path). - * @param String $path - path to file on disk (note: paths should be encoded using - * UNIX-style forward slashes -- e.g '/path/to/some/file'). - * @param FileOptions $options + * See {@see addFileFromPsr7Stream()} * - * File Options: - * time - Last-modified timestamp (seconds since the epoch) of - * this file. Defaults to the current time. - * comment - Comment related to this file. - * method - Storage method for file ("store" or "deflate") + * ###### Examples * - * Examples: + * ```php + * // add a file named 'foo.txt' from the local file '/tmp/foo.txt' + * $zip->addFileFromPath( + * fileName: 'foo.txt', + * path: '/tmp/foo.txt', + * ); * - * // add a file named 'foo.txt' from the local file '/tmp/foo.txt' - * $zip->addFileFromPath('foo.txt', '/tmp/foo.txt'); + * // add a file named 'bigfile.rar' from the local file + * // '/usr/share/bigfile.rar' with a comment and a last-modified + * // time of two hours ago + * $zip->addFileFromPath( + * fileName: 'bigfile.rar', + * path: '/usr/share/bigfile.rar', + * comment: 'this is a comment about bigfile.rar', + * lastModificationDateTime: new DateTime('2 hours ago'), + * ); + * ``` * - * // add a file named 'bigfile.rar' from the local file - * // '/usr/share/bigfile.rar' with a comment and a last-modified - * // time of two hours ago - * $path = '/usr/share/bigfile.rar'; - * $opt->setTime = time() - 2 * 3600; - * $opt->setComment = 'this is a comment about bar.jpg'; - * $zip->addFileFromPath('bigfile.rar', $path, $opt); - * - * @return void * @throws \ZipStream\Exception\FileNotFoundException * @throws \ZipStream\Exception\FileNotReadableException */ - public function addFileFromPath(string $name, string $path, ?FileOptions $options = null): void - { - $options = $options ?: new FileOptions(); - $options->defaultTo($this->opt); + public function addFileFromPath( + /** + * name of file in archive (including directory path). + */ + string $fileName, - $file = new File($this, $name, $options); - $file->processPath($path); + /** + * path to file on disk (note: paths should be encoded using + * UNIX-style forward slashes -- e.g '/path/to/some/file'). + */ + string $path, + string $comment = '', + ?CompressionMethod $compressionMethod = null, + ?int $deflateLevel = null, + ?DateTimeInterface $lastModificationDateTime = null, + ?int $maxSize = null, + ?int $exactSize = null, + ?bool $enableZeroHeader = null, + ): void { + if (!is_readable($path)) { + if (!file_exists($path)) { + throw new FileNotFoundException($path); + } + throw new FileNotReadableException($path); + } + + $fileTime = filemtime($path); + if ($fileTime !== false) { + $lastModificationDateTime ??= (new DateTimeImmutable())->setTimestamp($fileTime); + } + + $this->addFileFromCallback( + fileName: $fileName, + callback: function () use ($path) { + + $stream = fopen($path, 'rb'); + + if (!$stream) { + // @codeCoverageIgnoreStart + throw new ResourceActionException('fopen'); + // @codeCoverageIgnoreEnd + } + + return $stream; + }, + comment: $comment, + compressionMethod: $compressionMethod, + deflateLevel: $deflateLevel, + lastModificationDateTime: $lastModificationDateTime, + maxSize: $maxSize, + exactSize: $exactSize, + enableZeroHeader: $enableZeroHeader, + ); } /** - * addFileFromStream + * Add an open stream (resource) to the archive. * - * Add an open stream to the archive. + * ##### File Options * - * @param String $name - path of file in archive (including directory). - * @param resource $stream - contents of file as a stream resource - * @param FileOptions $options + * See {@see addFileFromPsr7Stream()} * - * File Options: - * time - Last-modified timestamp (seconds since the epoch) of - * this file. Defaults to the current time. - * comment - Comment related to this file. + * ##### Examples * - * Examples: + * ```php + * // create a temporary file stream and write text to it + * $filePointer = tmpfile(); + * fwrite($filePointer, 'The quick brown fox jumped over the lazy dog.'); * - * // create a temporary file stream and write text to it - * $fp = tmpfile(); - * fwrite($fp, 'The quick brown fox jumped over the lazy dog.'); + * // add a file named 'streamfile.txt' from the content of the stream + * $archive->addFileFromStream( + * fileName: 'streamfile.txt', + * stream: $filePointer, + * ); + * ``` * - * // add a file named 'streamfile.txt' from the content of the stream - * $x->addFileFromStream('streamfile.txt', $fp); - * - * @return void + * @param resource $stream contents of file as a stream resource */ - public function addFileFromStream(string $name, $stream, ?FileOptions $options = null): void - { - $options = $options ?: new FileOptions(); - $options->defaultTo($this->opt); - - $file = new File($this, $name, $options); - $file->processStream(new Stream($stream)); + public function addFileFromStream( + string $fileName, + $stream, + string $comment = '', + ?CompressionMethod $compressionMethod = null, + ?int $deflateLevel = null, + ?DateTimeInterface $lastModificationDateTime = null, + ?int $maxSize = null, + ?int $exactSize = null, + ?bool $enableZeroHeader = null, + ): void { + $this->addFileFromCallback( + fileName: $fileName, + callback: fn() => $stream, + comment: $comment, + compressionMethod: $compressionMethod, + deflateLevel: $deflateLevel, + lastModificationDateTime: $lastModificationDateTime, + maxSize: $maxSize, + exactSize: $exactSize, + enableZeroHeader: $enableZeroHeader, + ); } /** - * addFileFromPsr7Stream - * * Add an open stream to the archive. * - * @param String $name - path of file in archive (including directory). - * @param StreamInterface $stream - contents of file as a stream resource - * @param FileOptions $options + * ##### Examples * - * File Options: - * time - Last-modified timestamp (seconds since the epoch) of - * this file. Defaults to the current time. - * comment - Comment related to this file. + * ```php + * $stream = $response->getBody(); + * // add a file named 'streamfile.txt' from the content of the stream + * $archive->addFileFromPsr7Stream( + * fileName: 'streamfile.txt', + * stream: $stream, + * ); + * ``` * - * Examples: + * @param string $fileName + * path of file in archive (including directory) * - * $stream = $response->getBody(); - * // add a file named 'streamfile.txt' from the content of the stream - * $x->addFileFromPsr7Stream('streamfile.txt', $stream); + * @param StreamInterface $stream + * contents of file as a stream resource * - * @return void + * @param string $comment + * ZIP comment for this file + * + * @param ?CompressionMethod $compressionMethod + * Override `defaultCompressionMethod` + * + * See {@see __construct()} + * + * @param ?int $deflateLevel + * Override `defaultDeflateLevel` + * + * See {@see __construct()} + * + * @param ?DateTimeInterface $lastModificationDateTime + * Set last modification time of file. + * + * Default: `now` + * + * @param ?int $maxSize + * Only read `maxSize` bytes from file. + * + * The file is considered done when either reaching `EOF` + * or the `maxSize`. + * + * @param ?int $exactSize + * Read exactly `exactSize` bytes from file. + * If `EOF` is reached before reading `exactSize` bytes, an error will be + * thrown. The parameter allows for faster size calculations if the `stream` + * does not support `fstat` size or is slow and otherwise known beforehand. + * + * @param ?bool $enableZeroHeader + * Override `defaultEnableZeroHeader` + * + * See {@see __construct()} */ public function addFileFromPsr7Stream( - string $name, + string $fileName, StreamInterface $stream, - ?FileOptions $options = null + string $comment = '', + ?CompressionMethod $compressionMethod = null, + ?int $deflateLevel = null, + ?DateTimeInterface $lastModificationDateTime = null, + ?int $maxSize = null, + ?int $exactSize = null, + ?bool $enableZeroHeader = null, ): void { - $options = $options ?: new FileOptions(); - $options->defaultTo($this->opt); - - $file = new File($this, $name, $options); - $file->processStream($stream); + $this->addFileFromCallback( + fileName: $fileName, + callback: fn() => $stream, + comment: $comment, + compressionMethod: $compressionMethod, + deflateLevel: $deflateLevel, + lastModificationDateTime: $lastModificationDateTime, + maxSize: $maxSize, + exactSize: $exactSize, + enableZeroHeader: $enableZeroHeader, + ); } /** - * finish + * Add a file based on a callback. * + * This is useful when you want to simulate a lot of files without keeping + * all of the file handles open at the same time. + * + * ##### Examples + * + * ```php + * foreach($files as $name => $size) { + * $archive->addFileFromCallback( + * fileName: 'streamfile.txt', + * exactSize: $size, + * callback: function() use($name): Psr\Http\Message\StreamInterface { + * $response = download($name); + * return $response->getBody(); + * } + * ); + * } + * ``` + * + * @param string $fileName + * path of file in archive (including directory) + * + * @param Closure $callback + * @psalm-param Closure(): (resource|StreamInterface|string) $callback + * A callback to get the file contents in the shape of a PHP stream, + * a Psr StreamInterface implementation, or a string. + * + * @param string $comment + * ZIP comment for this file + * + * @param ?CompressionMethod $compressionMethod + * Override `defaultCompressionMethod` + * + * See {@see __construct()} + * + * @param ?int $deflateLevel + * Override `defaultDeflateLevel` + * + * See {@see __construct()} + * + * @param ?DateTimeInterface $lastModificationDateTime + * Set last modification time of file. + * + * Default: `now` + * + * @param ?int $maxSize + * Only read `maxSize` bytes from file. + * + * The file is considered done when either reaching `EOF` + * or the `maxSize`. + * + * @param ?int $exactSize + * Read exactly `exactSize` bytes from file. + * If `EOF` is reached before reading `exactSize` bytes, an error will be + * thrown. The parameter allows for faster size calculations if the `stream` + * does not support `fstat` size or is slow and otherwise known beforehand. + * + * @param ?bool $enableZeroHeader + * Override `defaultEnableZeroHeader` + * + * See {@see __construct()} + */ + public function addFileFromCallback( + string $fileName, + Closure $callback, + string $comment = '', + ?CompressionMethod $compressionMethod = null, + ?int $deflateLevel = null, + ?DateTimeInterface $lastModificationDateTime = null, + ?int $maxSize = null, + ?int $exactSize = null, + ?bool $enableZeroHeader = null, + ): void { + $file = new File( + dataCallback: function () use ($callback, $maxSize) { + $data = $callback(); + + if (is_resource($data)) { + return $data; + } + + if ($data instanceof StreamInterface) { + return StreamWrapper::getResource($data); + } + + + $stream = fopen('php://memory', 'rw+'); + if ($stream === false) { + // @codeCoverageIgnoreStart + throw new ResourceActionException('fopen'); + // @codeCoverageIgnoreEnd + } + if ($maxSize !== null && fwrite($stream, $data, $maxSize) === false) { + // @codeCoverageIgnoreStart + throw new ResourceActionException('fwrite', $stream); + // @codeCoverageIgnoreEnd + } elseif (fwrite($stream, $data) === false) { + // @codeCoverageIgnoreStart + throw new ResourceActionException('fwrite', $stream); + // @codeCoverageIgnoreEnd + } + if (rewind($stream) === false) { + // @codeCoverageIgnoreStart + throw new ResourceActionException('rewind', $stream); + // @codeCoverageIgnoreEnd + } + + return $stream; + + }, + send: $this->send(...), + recordSentBytes: $this->recordSentBytes(...), + operationMode: $this->operationMode, + fileName: $fileName, + startOffset: $this->offset, + compressionMethod: $compressionMethod ?? $this->defaultCompressionMethod, + comment: $comment, + deflateLevel: $deflateLevel ?? $this->defaultDeflateLevel, + lastModificationDateTime: $lastModificationDateTime ?? new DateTimeImmutable(), + maxSize: $maxSize, + exactSize: $exactSize, + enableZip64: $this->enableZip64, + enableZeroHeader: $enableZeroHeader ?? $this->defaultEnableZeroHeader, + ); + + if ($this->operationMode !== OperationMode::NORMAL) { + $this->recordedSimulation[] = $file; + } + + $this->centralDirectoryRecords[] = $file->process(); + } + + /** + * Add a directory to the archive. + * + * ##### File Options + * + * See {@see addFileFromPsr7Stream()} + * + * ##### Examples + * + * ```php + * // add a directory named 'world/' + * $zip->addDirectory(fileName: 'world/'); + * ``` + */ + public function addDirectory( + string $fileName, + string $comment = '', + ?DateTimeInterface $lastModificationDateTime = null, + ): void { + if (!str_ends_with($fileName, '/')) { + $fileName .= '/'; + } + + $this->addFile( + fileName: $fileName, + data: '', + comment: $comment, + compressionMethod: CompressionMethod::STORE, + deflateLevel: null, + lastModificationDateTime: $lastModificationDateTime, + maxSize: 0, + exactSize: 0, + enableZeroHeader: false, + ); + } + + /** + * Executes a previously calculated simulation. + * + * ##### Example + * + * ```php + * $zip = new ZipStream( + * outputName: 'foo.zip', + * operationMode: OperationMode::SIMULATE_STRICT, + * ); + * + * $zip->addFile('test.txt', 'Hello World'); + * + * $size = $zip->finish(); + * + * header('Content-Length: '. $size); + * + * $zip->executeSimulation(); + * ``` + */ + public function executeSimulation(): void + { + if ($this->operationMode !== OperationMode::NORMAL) { + throw new RuntimeException('Zip simulation is not finished.'); + } + + foreach ($this->recordedSimulation as $file) { + $this->centralDirectoryRecords[] = $file->cloneSimulationExecution()->process(); + } + + $this->finish(); + } + + /** * Write zip footer to stream. * - * Example: + * The clase is left in an unusable state after `finish`. * - * // add a list of files to the archive - * $files = array('foo.txt', 'bar.jpg'); - * foreach ($files as $path) - * $zip->addFile($path, file_get_contents($path)); + * ##### Example * - * // write footer to stream - * $zip->finish(); - * @return void - * - * @throws OverflowException + * ```php + * // write footer to stream + * $zip->finish(); + * ``` */ - public function finish(): void + public function finish(): int { + $centralDirectoryStartOffsetOnDisk = $this->offset; + $sizeOfCentralDirectory = 0; + // add trailing cdr file records - foreach ($this->files as $cdrFile) { - $this->send($cdrFile); - $this->cdr_ofs = $this->cdr_ofs->add(Bigint::init(strlen($cdrFile))); + foreach ($this->centralDirectoryRecords as $centralDirectoryRecord) { + $this->send($centralDirectoryRecord); + $sizeOfCentralDirectory += strlen($centralDirectoryRecord); } // Add 64bit headers (if applicable) - if (count($this->files) >= 0xFFFF || - $this->cdr_ofs->isOver32() || - $this->ofs->isOver32()) { - if (!$this->opt->isEnableZip64()) { + if (count($this->centralDirectoryRecords) >= 0xFFFF || + $centralDirectoryStartOffsetOnDisk > 0xFFFFFFFF || + $sizeOfCentralDirectory > 0xFFFFFFFF) { + if (!$this->enableZip64) { throw new OverflowException(); } - $this->addCdr64Eof(); - $this->addCdr64Locator(); + $this->send(Zip64\EndOfCentralDirectory::generate( + versionMadeBy: self::ZIP_VERSION_MADE_BY, + versionNeededToExtract: Version::ZIP64->value, + numberOfThisDisk: 0, + numberOfTheDiskWithCentralDirectoryStart: 0, + numberOfCentralDirectoryEntriesOnThisDisk: count($this->centralDirectoryRecords), + numberOfCentralDirectoryEntries: count($this->centralDirectoryRecords), + sizeOfCentralDirectory: $sizeOfCentralDirectory, + centralDirectoryStartOffsetOnDisk: $centralDirectoryStartOffsetOnDisk, + extensibleDataSector: '', + )); + + $this->send(Zip64\EndOfCentralDirectoryLocator::generate( + numberOfTheDiskWithZip64CentralDirectoryStart: 0x00, + zip64centralDirectoryStartOffsetOnDisk: $centralDirectoryStartOffsetOnDisk + $sizeOfCentralDirectory, + totalNumberOfDisks: 1, + )); } // add trailing cdr eof record - $this->addCdrEof(); + $numberOfCentralDirectoryEntries = min(count($this->centralDirectoryRecords), 0xFFFF); + $this->send(EndOfCentralDirectory::generate( + numberOfThisDisk: 0x00, + numberOfTheDiskWithCentralDirectoryStart: 0x00, + numberOfCentralDirectoryEntriesOnThisDisk: $numberOfCentralDirectoryEntries, + numberOfCentralDirectoryEntries: $numberOfCentralDirectoryEntries, + sizeOfCentralDirectory: min($sizeOfCentralDirectory, 0xFFFFFFFF), + centralDirectoryStartOffsetOnDisk: min($centralDirectoryStartOffsetOnDisk, 0xFFFFFFFF), + zipFileComment: $this->comment, + )); + + $size = $this->offset; // The End $this->clear(); + + return $size; } /** - * Create a format string and argument list for pack(), then call - * pack() and return the result. - * - * @param array $fields - * @return string + * @param StreamInterface|resource|null $outputStream + * @return resource */ - public static function packFields(array $fields): string + private static function normalizeStream($outputStream) { - $fmt = ''; - $args = []; + if ($outputStream instanceof StreamInterface) { + return StreamWrapper::getResource($outputStream); + } + if (is_resource($outputStream)) { + return $outputStream; + } + $resource = fopen('php://output', 'wb'); - // populate format string and argument list - foreach ($fields as [$format, $value]) { - if ($format === 'P') { - $fmt .= 'VV'; - if ($value instanceof Bigint) { - $args[] = $value->getLow32(); - $args[] = $value->getHigh32(); - } else { - $args[] = $value; - $args[] = 0; - } - } else { - if ($value instanceof Bigint) { - $value = $value->getLow32(); - } - $fmt .= $format; - $args[] = $value; - } + if ($resource === false) { + throw new RuntimeException('fopen of php://output failed'); } - // prepend format string to argument list - array_unshift($args, $fmt); + return $resource; + } - // build output string from header and compressed data - return pack(...$args); + /** + * Record sent bytes + */ + private function recordSentBytes(int $sentBytes): void + { + $this->offset += $sentBytes; } /** * Send string, sending HTTP headers if necessary. * Flush output after write if configure option is set. - * - * @param String $str - * @return void */ - public function send(string $str): void + private function send(string $data): void { - if ($this->need_headers) { + if (!$this->ready) { + throw new RuntimeException('Archive is already finished'); + } + + if ($this->operationMode === OperationMode::NORMAL && $this->sendHttpHeaders) { $this->sendHttpHeaders(); - } - $this->need_headers = false; - - $outputStream = $this->opt->getOutputStream(); - - if ($outputStream instanceof StreamInterface) { - $outputStream->write($str); - } else { - fwrite($outputStream, $str); + $this->sendHttpHeaders = false; } - if ($this->opt->isFlushOutput()) { - // flush output buffer if it is on and flushable - $status = ob_get_status(); - if (isset($status['flags']) && ($status['flags'] & PHP_OUTPUT_HANDLER_FLUSHABLE)) { - ob_flush(); + $this->recordSentBytes(strlen($data)); + + if ($this->operationMode === OperationMode::NORMAL) { + if (fwrite($this->outputStream, $data) === false) { + throw new ResourceActionException('fwrite', $this->outputStream); } - // Flush system buffers after flushing userspace output buffer - flush(); + if ($this->flushOutput) { + // flush output buffer if it is on and flushable + $status = ob_get_status(); + if (isset($status['flags']) && is_int($status['flags']) && ($status['flags'] & PHP_OUTPUT_HANDLER_FLUSHABLE)) { + ob_flush(); + } + + // Flush system buffers after flushing userspace output buffer + flush(); + } } } /** - * Is this file larger than large_file_size? - * - * @param string $path - * @return bool - */ - public function isLargeFile(string $path): bool - { - if (!$this->opt->isStatFiles()) { - return false; - } - $stat = stat($path); - return $stat['size'] > $this->opt->getLargeFileSize(); - } - - /** - * Save file attributes for trailing CDR record. - * - * @param File $file - * @return void - */ - public function addToCdr(File $file): void - { - $file->ofs = $this->ofs; - $this->ofs = $this->ofs->add($file->getTotalLength()); - $this->files[] = $file->getCdrFile(); - } - - /** - * Send ZIP64 CDR EOF (Central Directory Record End-of-File) record. - * - * @return void - */ - protected function addCdr64Eof(): void - { - $num_files = count($this->files); - $cdr_length = $this->cdr_ofs; - $cdr_offset = $this->ofs; - - $fields = [ - ['V', static::ZIP64_CDR_EOF_SIGNATURE], // ZIP64 end of central file header signature - ['P', 44], // Length of data below this header (length of block - 12) = 44 - ['v', static::ZIP_VERSION_MADE_BY], // Made by version - ['v', Version::ZIP64], // Extract by version - ['V', 0x00], // disk number - ['V', 0x00], // no of disks - ['P', $num_files], // no of entries on disk - ['P', $num_files], // no of entries in cdr - ['P', $cdr_length], // CDR size - ['P', $cdr_offset], // CDR offset - ]; - - $ret = static::packFields($fields); - $this->send($ret); - } - - /** - * Send HTTP headers for this stream. - * - * @return void - */ - protected function sendHttpHeaders(): void + * Send HTTP headers for this stream. + */ + private function sendHttpHeaders(): void { // grab content disposition - $disposition = $this->opt->getContentDisposition(); + $disposition = $this->contentDisposition; - if ($this->output_name) { + if ($this->outputName !== null) { // Various different browsers dislike various characters here. Strip them all for safety. - $safe_output = trim(str_replace(['"', "'", '\\', ';', "\n", "\r"], '', $this->output_name)); + $safeOutput = trim(str_replace(['"', "'", '\\', ';', "\n", "\r"], '', $this->outputName)); // Check if we need to UTF-8 encode the filename - $urlencoded = rawurlencode($safe_output); + $urlencoded = rawurlencode($safeOutput); $disposition .= "; filename*=UTF-8''{$urlencoded}"; } $headers = [ - 'Content-Type' => $this->opt->getContentType(), + 'Content-Type' => $this->contentType, 'Content-Disposition' => $disposition, 'Pragma' => 'public', 'Cache-Control' => 'public, must-revalidate', 'Content-Transfer-Encoding' => 'binary', ]; - $call = $this->opt->getHttpHeaderCallback(); foreach ($headers as $key => $val) { - $call("$key: $val"); + ($this->httpHeaderCallback)("$key: $val"); } } - /** - * Send ZIP64 CDR Locator (Central Directory Record Locator) record. - * - * @return void - */ - protected function addCdr64Locator(): void - { - $cdr_offset = $this->ofs->add($this->cdr_ofs); - - $fields = [ - ['V', static::ZIP64_CDR_LOCATOR_SIGNATURE], // ZIP64 end of central file header signature - ['V', 0x00], // Disc number containing CDR64EOF - ['P', $cdr_offset], // CDR offset - ['V', 1], // Total number of disks - ]; - - $ret = static::packFields($fields); - $this->send($ret); - } - - /** - * Send CDR EOF (Central Directory Record End-of-File) record. - * - * @return void - */ - protected function addCdrEof(): void - { - $num_files = count($this->files); - $cdr_length = $this->cdr_ofs; - $cdr_offset = $this->ofs; - - // grab comment (if specified) - $comment = $this->opt->getComment(); - - $fields = [ - ['V', static::CDR_EOF_SIGNATURE], // end of central file header signature - ['v', 0x00], // disk number - ['v', 0x00], // no of disks - ['v', min($num_files, 0xFFFF)], // no of entries on disk - ['v', min($num_files, 0xFFFF)], // no of entries in cdr - ['V', $cdr_length->getLowFF()], // CDR size - ['V', $cdr_offset->getLowFF()], // CDR offset - ['v', strlen($comment)], // Zip Comment size - ]; - - $ret = static::packFields($fields) . $comment; - $this->send($ret); - } - /** * Clear all internal variables. Note that the stream object is not * usable after this. - * - * @return void */ - protected function clear(): void + private function clear(): void { - $this->files = []; - $this->ofs = new Bigint(); - $this->cdr_ofs = new Bigint(); - $this->opt = new ArchiveOptions(); + $this->centralDirectoryRecords = []; + $this->offset = 0; + + if ($this->operationMode === OperationMode::NORMAL) { + $this->ready = false; + $this->recordedSimulation = []; + } else { + $this->operationMode = OperationMode::NORMAL; + } } } diff --git a/vendor/maennchen/zipstream-php/src/Zs/ExtendedInformationExtraField.php b/vendor/maennchen/zipstream-php/src/Zs/ExtendedInformationExtraField.php new file mode 100644 index 0000000..bf621bc --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Zs/ExtendedInformationExtraField.php @@ -0,0 +1,23 @@ +fail("File {$filePath} must contain {$needle}"); + } + + protected function assertFileDoesNotContain(string $filePath, string $needle): void + { + $last = ''; + + $handle = fopen($filePath, 'r'); + while (!feof($handle)) { + $line = fgets($handle, 1024); + + if (str_contains($last . $line, $needle)) { + fclose($handle); + + $this->fail("File {$filePath} must not contain {$needle}"); + } + + $last = $line; + } + + fclose($handle); + } +} diff --git a/vendor/maennchen/zipstream-php/test/BigintTest.php b/vendor/maennchen/zipstream-php/test/BigintTest.php deleted file mode 100644 index 4d26fcd..0000000 --- a/vendor/maennchen/zipstream-php/test/BigintTest.php +++ /dev/null @@ -1,66 +0,0 @@ -assertSame('0x0000000012345678', $bigint->getHex64()); - $this->assertSame(0x12345678, $bigint->getLow32()); - $this->assertSame(0, $bigint->getHigh32()); - } - - public function testConstructLarge(): void - { - $bigint = new Bigint(0x87654321); - $this->assertSame('0x0000000087654321', $bigint->getHex64()); - $this->assertSame('87654321', bin2hex(pack('N', $bigint->getLow32()))); - $this->assertSame(0, $bigint->getHigh32()); - } - - public function testAddSmallValue(): void - { - $bigint = new Bigint(1); - $bigint = $bigint->add(Bigint::init(2)); - $this->assertSame(3, $bigint->getLow32()); - $this->assertFalse($bigint->isOver32()); - $this->assertTrue($bigint->isOver32(true)); - $this->assertSame($bigint->getLowFF(), (float)$bigint->getLow32()); - $this->assertSame($bigint->getLowFF(true), (float)0xFFFFFFFF); - } - - public function testAddWithOverflowAtLowestByte(): void - { - $bigint = new Bigint(0xFF); - $bigint = $bigint->add(Bigint::init(0x01)); - $this->assertSame(0x100, $bigint->getLow32()); - } - - public function testAddWithOverflowAtInteger32(): void - { - $bigint = new Bigint(0xFFFFFFFE); - $this->assertFalse($bigint->isOver32()); - $bigint = $bigint->add(Bigint::init(0x01)); - $this->assertTrue($bigint->isOver32()); - $bigint = $bigint->add(Bigint::init(0x01)); - $this->assertSame('0x0000000100000000', $bigint->getHex64()); - $this->assertTrue($bigint->isOver32()); - $this->assertSame((float)0xFFFFFFFF, $bigint->getLowFF()); - } - - public function testAddWithOverflowAtInteger64(): void - { - $bigint = Bigint::fromLowHigh(0xFFFFFFFF, 0xFFFFFFFF); - $this->assertSame('0xFFFFFFFFFFFFFFFF', $bigint->getHex64()); - $this->expectException(OverflowException::class); - $bigint->add(Bigint::init(1)); - } -} diff --git a/vendor/maennchen/zipstream-php/test/CentralDirectoryFileHeaderTest.php b/vendor/maennchen/zipstream-php/test/CentralDirectoryFileHeaderTest.php new file mode 100644 index 0000000..5457b4f --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/CentralDirectoryFileHeaderTest.php @@ -0,0 +1,60 @@ +assertSame( + bin2hex($header), + '504b0102' . // 4 bytes; central file header signature + '0306' . // 2 bytes; version made by + '2d00' . // 2 bytes; version needed to extract + '2222' . // 2 bytes; general purpose bit flag + '0800' . // 2 bytes; compression method + '2008' . // 2 bytes; last mod file time + '2154' . // 2 bytes; last mod file date + '11111111' . // 4 bytes; crc-32 + '77777777' . // 4 bytes; compressed size + '99999999' . // 4 bytes; uncompressed size + '0800' . // 2 bytes; file name length (n) + '0c00' . // 2 bytes; extra field length (m) + '0c00' . // 2 bytes; file comment length (o) + '0000' . // 2 bytes; disk number start + '0000' . // 2 bytes; internal file attributes + '20000000' . // 4 bytes; external file attributes + '34120000' . // 4 bytes; relative offset of local header + '746573742e706e67' . // n bytes; file name + '736f6d6520636f6e74656e74' . // m bytes; extra field + '736f6d6520636f6d6d656e74' // o bytes; file comment + ); + } +} diff --git a/vendor/maennchen/zipstream-php/test/DataDescriptorTest.php b/vendor/maennchen/zipstream-php/test/DataDescriptorTest.php new file mode 100644 index 0000000..cc886c7 --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/DataDescriptorTest.php @@ -0,0 +1,26 @@ +assertSame( + bin2hex(DataDescriptor::generate( + crc32UncompressedData: 0x11111111, + compressedSize: 0x77777777, + uncompressedSize: 0x99999999, + )), + '504b0708' . // 4 bytes; Optional data descriptor signature = 0x08074b50 + '11111111' . // 4 bytes; CRC-32 of uncompressed data + '77777777' . // 4 bytes; Compressed size + '99999999' // 4 bytes; Uncompressed size + ); + } +} diff --git a/vendor/maennchen/zipstream-php/test/EndOfCentralDirectoryTest.php b/vendor/maennchen/zipstream-php/test/EndOfCentralDirectoryTest.php new file mode 100644 index 0000000..be0a907 --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/EndOfCentralDirectoryTest.php @@ -0,0 +1,35 @@ +assertSame( + bin2hex(EndOfCentralDirectory::generate( + numberOfThisDisk: 0x00, + numberOfTheDiskWithCentralDirectoryStart: 0x00, + numberOfCentralDirectoryEntriesOnThisDisk: 0x10, + numberOfCentralDirectoryEntries: 0x10, + sizeOfCentralDirectory: 0x22, + centralDirectoryStartOffsetOnDisk: 0x33, + zipFileComment: 'foo', + )), + '504b0506' . // 4 bytes; end of central dir signature 0x06054b50 + '0000' . // 2 bytes; number of this disk + '0000' . // 2 bytes; number of the disk with the start of the central directory + '1000' . // 2 bytes; total number of entries in the central directory on this disk + '1000' . // 2 bytes; total number of entries in the central directory + '22000000' . // 4 bytes; size of the central directory + '33000000' . // 4 bytes; offset of start of central directory with respect to the starting disk number + '0300' . // 2 bytes; .ZIP file comment length + bin2hex('foo') + ); + } +} diff --git a/vendor/maennchen/zipstream-php/test/EndlessCycleStream.php b/vendor/maennchen/zipstream-php/test/EndlessCycleStream.php new file mode 100644 index 0000000..d9e7df1 --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/EndlessCycleStream.php @@ -0,0 +1,104 @@ +detach(); + } + + /** + * @return null + */ + public function detach() + { + return; + } + + public function getSize(): ?int + { + return null; + } + + public function tell(): int + { + return $this->offset; + } + + public function eof(): bool + { + return false; + } + + public function isSeekable(): bool + { + return true; + } + + public function seek(int $offset, int $whence = SEEK_SET): void + { + switch ($whence) { + case SEEK_SET: + $this->offset = $offset; + break; + case SEEK_CUR: + $this->offset += $offset; + break; + case SEEK_END: + throw new RuntimeException('Infinite Stream!'); + break; + } + } + + public function rewind(): void + { + $this->seek(0); + } + + public function isWritable(): bool + { + return false; + } + + public function write(string $string): int + { + throw new RuntimeException('Not writeable'); + } + + public function isReadable(): bool + { + return true; + } + + public function read(int $length): string + { + $this->offset += $length; + return substr(str_repeat($this->toRepeat, (int) ceil($length / strlen($this->toRepeat))), 0, $length); + } + + public function getContents(): string + { + throw new RuntimeException('Infinite Stream!'); + } + + public function getMetadata(?string $key = null): array|null + { + return $key !== null ? null : []; + } +} diff --git a/vendor/maennchen/zipstream-php/test/FaultInjectionResource.php b/vendor/maennchen/zipstream-php/test/FaultInjectionResource.php new file mode 100644 index 0000000..af9305b --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/FaultInjectionResource.php @@ -0,0 +1,141 @@ +context); + + if (!isset($options[self::NAME]['injectFaults'])) { + return false; + } + + $this->mode = $mode; + $this->injectFaults = $options[self::NAME]['injectFaults']; + + if ($this->shouldFail(__FUNCTION__)) { + return false; + } + + return true; + } + + public function stream_write(string $data) + { + if ($this->shouldFail(__FUNCTION__)) { + return false; + } + return true; + } + + public function stream_eof() + { + return true; + } + + public function stream_seek(int $offset, int $whence): bool + { + if ($this->shouldFail(__FUNCTION__)) { + return false; + } + + return true; + } + + public function stream_tell(): int + { + if ($this->shouldFail(__FUNCTION__)) { + return false; + } + + return 0; + } + + public static function register(): void + { + if (!in_array(self::NAME, stream_get_wrappers(), true)) { + stream_wrapper_register(self::NAME, __CLASS__); + } + } + + public function stream_stat(): array + { + static $modeMap = [ + 'r' => 33060, + 'rb' => 33060, + 'r+' => 33206, + 'w' => 33188, + 'wb' => 33188, + ]; + + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => $modeMap[$this->mode], + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0, + ]; + } + + public function url_stat(string $path, int $flags): array + { + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => 0, + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0, + ]; + } + + private static function createStreamContext(array $injectFaults) + { + return stream_context_create([ + self::NAME => ['injectFaults' => $injectFaults], + ]); + } + + private function shouldFail(string $function): bool + { + return in_array($function, $this->injectFaults, true); + } +} diff --git a/vendor/maennchen/zipstream-php/test/LocalFileHeaderTest.php b/vendor/maennchen/zipstream-php/test/LocalFileHeaderTest.php new file mode 100644 index 0000000..196dd0f --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/LocalFileHeaderTest.php @@ -0,0 +1,47 @@ +assertSame( + bin2hex((string) $header), + '504b0304' . // 4 bytes; Local file header signature + '2d00' . // 2 bytes; Version needed to extract (minimum) + '2222' . // 2 bytes; General purpose bit flag + '0800' . // 2 bytes; Compression method; e.g. none = 0, DEFLATE = 8 + '2008' . // 2 bytes; File last modification time + '2154' . // 2 bytes; File last modification date + '11111111' . // 4 bytes; CRC-32 of uncompressed data + '77777777' . // 4 bytes; Compressed size (or 0xffffffff for ZIP64) + '99999999' . // 4 bytes; Uncompressed size (or 0xffffffff for ZIP64) + '0800' . // 2 bytes; File name length (n) + '0c00' . // 2 bytes; Extra field length (m) + '746573742e706e67' . // n bytes; File name + '736f6d6520636f6e74656e74' // m bytes; Extra field + ); + } +} diff --git a/vendor/maennchen/zipstream-php/test/PackFieldTest.php b/vendor/maennchen/zipstream-php/test/PackFieldTest.php new file mode 100644 index 0000000..ecd66ba --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/PackFieldTest.php @@ -0,0 +1,42 @@ +assertSame( + bin2hex(PackField::pack(new PackField(format: 'v', value: 0x1122))), + '2211', + ); + } + + public function testOverflow2(): void + { + $this->expectException(RuntimeException::class); + + PackField::pack(new PackField(format: 'v', value: 0xFFFFF)); + } + + public function testOverflow4(): void + { + $this->expectException(RuntimeException::class); + + PackField::pack(new PackField(format: 'V', value: 0xFFFFFFFFF)); + } + + public function testUnknownOperator(): void + { + $this->assertSame( + bin2hex(PackField::pack(new PackField(format: 'a', value: 0x1122))), + '34', + ); + } +} diff --git a/vendor/maennchen/zipstream-php/test/ResourceStream.php b/vendor/maennchen/zipstream-php/test/ResourceStream.php new file mode 100644 index 0000000..752a1a3 --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/ResourceStream.php @@ -0,0 +1,159 @@ +isSeekable()) { + $this->seek(0); + } + return (string) stream_get_contents($this->stream); + } + + public function close(): void + { + $stream = $this->detach(); + if ($stream) { + fclose($stream); + } + } + + public function detach() + { + $result = $this->stream; + // According to the interface, the stream is left in an unusable state; + /** @psalm-suppress PossiblyNullPropertyAssignmentValue */ + $this->stream = null; + return $result; + } + + public function seek(int $offset, int $whence = SEEK_SET): void + { + if (!$this->isSeekable()) { + throw new RuntimeException(); + } + if (fseek($this->stream, $offset, $whence) !== 0) { + // @codeCoverageIgnoreStart + throw new RuntimeException(); + // @codeCoverageIgnoreEnd + } + } + + public function isSeekable(): bool + { + return (bool) $this->getMetadata('seekable'); + } + + public function getMetadata(?string $key = null) + { + $metadata = stream_get_meta_data($this->stream); + return $key !== null ? @$metadata[$key] : $metadata; + } + + public function getSize(): ?int + { + $stats = fstat($this->stream); + return $stats['size']; + } + + public function tell(): int + { + $position = ftell($this->stream); + if ($position === false) { + // @codeCoverageIgnoreStart + throw new RuntimeException(); + // @codeCoverageIgnoreEnd + } + return $position; + } + + public function eof(): bool + { + return feof($this->stream); + } + + public function rewind(): void + { + $this->seek(0); + } + + public function write(string $string): int + { + if (!$this->isWritable()) { + throw new RuntimeException(); + } + if (fwrite($this->stream, $string) === false) { + // @codeCoverageIgnoreStart + throw new RuntimeException(); + // @codeCoverageIgnoreEnd + } + return strlen($string); + } + + public function isWritable(): bool + { + $mode = $this->getMetadata('mode'); + if (!is_string($mode)) { + // @codeCoverageIgnoreStart + throw new RuntimeException('Could not get stream mode from metadata!'); + // @codeCoverageIgnoreEnd + } + return preg_match('/[waxc+]/', $mode) === 1; + } + + public function read(int $length): string + { + if (!$this->isReadable()) { + throw new RuntimeException(); + } + $result = fread($this->stream, $length); + if ($result === false) { + // @codeCoverageIgnoreStart + throw new RuntimeException(); + // @codeCoverageIgnoreEnd + } + return $result; + } + + public function isReadable(): bool + { + $mode = $this->getMetadata('mode'); + if (!is_string($mode)) { + // @codeCoverageIgnoreStart + throw new RuntimeException('Could not get stream mode from metadata!'); + // @codeCoverageIgnoreEnd + } + return preg_match('/[r+]/', $mode) === 1; + } + + public function getContents(): string + { + if (!$this->isReadable()) { + throw new RuntimeException(); + } + $result = stream_get_contents($this->stream); + if ($result === false) { + // @codeCoverageIgnoreStart + throw new RuntimeException(); + // @codeCoverageIgnoreEnd + } + return $result; + } +} diff --git a/vendor/maennchen/zipstream-php/test/Tempfile.php b/vendor/maennchen/zipstream-php/test/Tempfile.php new file mode 100644 index 0000000..7ef9c61 --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/Tempfile.php @@ -0,0 +1,42 @@ +getTmpFileStream(); + + $this->tempfile = $tempfile; + $this->tempfileStream = $tempfileStream; + } + + protected function tearDown(): void + { + unlink($this->tempfile); + if (is_resource($this->tempfileStream)) { + fclose($this->tempfileStream); + } + + $this->tempfile = null; + $this->tempfileStream = null; + } + + protected function getTmpFileStream(): array + { + $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest'); + $stream = fopen($tmp, 'wb+'); + + return [$tmp, $stream]; + } +} diff --git a/vendor/maennchen/zipstream-php/test/TimeTest.php b/vendor/maennchen/zipstream-php/test/TimeTest.php new file mode 100644 index 0000000..61cfe03 --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/TimeTest.php @@ -0,0 +1,44 @@ +assertSame( + Time::dateTimeToDosTime(new DateTimeImmutable('2014-11-17T17:46:08Z')), + 1165069764 + ); + + // January 1 1980 - DOS Epoch. + $this->assertSame( + Time::dateTimeToDosTime(new DateTimeImmutable('1980-01-01T00:00:00+00:00')), + 2162688 + ); + + // Local timezone different than UTC. + $prevLocalTimezone = date_default_timezone_get(); + date_default_timezone_set('Europe/Berlin'); + $this->assertSame( + Time::dateTimeToDosTime(new DateTimeImmutable('1980-01-01T00:00:00+00:00')), + 2162688 + ); + date_default_timezone_set($prevLocalTimezone); + } + + public function testTooEarlyDateToDosTime(): void + { + $this->expectException(DosTimeOverflowException::class); + + // January 1 1980 is the minimum DOS Epoch. + Time::dateTimeToDosTime(new DateTimeImmutable('1970-01-01T00:00:00+00:00')); + } +} diff --git a/vendor/maennchen/zipstream-php/test/Util.php b/vendor/maennchen/zipstream-php/test/Util.php new file mode 100644 index 0000000..86592b4 --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/Util.php @@ -0,0 +1,127 @@ +cmdExists('hexdump')) { + return ''; + } + + $output = []; + + if (!exec("hexdump -C \"$path\" | head -n 50", $output)) { + return ''; + } + + return "\nHexdump:\n" . implode("\n", $output); + } + + protected function validateAndExtractZip(string $zipPath): string + { + $tmpDir = $this->getTmpDir(); + + $zipArchive = new ZipArchive(); + $result = $zipArchive->open($zipPath); + + if ($result !== true) { + $codeName = $this->zipArchiveOpenErrorCodeName($result); + $debugInformation = $this->dumpZipContents($zipPath); + + $this->fail("Failed to open {$zipPath}. Code: $result ($codeName)$debugInformation"); + + return $tmpDir; + } + + $this->assertSame(0, $zipArchive->status); + $this->assertSame(0, $zipArchive->statusSys); + + $zipArchive->extractTo($tmpDir); + $zipArchive->close(); + + return $tmpDir; + } + + protected function zipArchiveOpenErrorCodeName(int $code): string + { + switch ($code) { + case ZipArchive::ER_EXISTS: return 'ER_EXISTS'; + case ZipArchive::ER_INCONS: return 'ER_INCONS'; + case ZipArchive::ER_INVAL: return 'ER_INVAL'; + case ZipArchive::ER_MEMORY: return 'ER_MEMORY'; + case ZipArchive::ER_NOENT: return 'ER_NOENT'; + case ZipArchive::ER_NOZIP: return 'ER_NOZIP'; + case ZipArchive::ER_OPEN: return 'ER_OPEN'; + case ZipArchive::ER_READ: return 'ER_READ'; + case ZipArchive::ER_SEEK: return 'ER_SEEK'; + default: return 'unknown'; + } + } + + protected function getTmpDir(): string + { + $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest'); + unlink($tmp); + mkdir($tmp) or $this->fail('Failed to make directory'); + + return $tmp; + } + + /** + * @return string[] + */ + protected function getRecursiveFileList(string $path, bool $includeDirectories = false): array + { + $data = []; + $path = (string) realpath($path); + $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)); + + $pathLen = strlen($path); + foreach ($files as $file) { + $filePath = $file->getRealPath(); + + if (is_dir($filePath) && !$includeDirectories) { + continue; + } + + $data[] = substr($filePath, $pathLen + 1); + } + + sort($data); + + return $data; + } +} diff --git a/vendor/maennchen/zipstream-php/test/Zip64/DataDescriptorTest.php b/vendor/maennchen/zipstream-php/test/Zip64/DataDescriptorTest.php new file mode 100644 index 0000000..49fb2cc --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/Zip64/DataDescriptorTest.php @@ -0,0 +1,28 @@ +assertSame( + bin2hex($descriptor), + '504b0708' . // 4 bytes; Optional data descriptor signature = 0x08074b50 + '11111111' . // 4 bytes; CRC-32 of uncompressed data + '6666666677777777' . // 8 bytes; Compressed size + '8888888899999999' // 8 bytes; Uncompressed size + ); + } +} diff --git a/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryLocatorTest.php b/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryLocatorTest.php new file mode 100644 index 0000000..271a298 --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryLocatorTest.php @@ -0,0 +1,28 @@ +assertSame( + bin2hex($descriptor), + '504b0607' . // 4 bytes; zip64 end of central dir locator signature - 0x07064b50 + '11111111' . // 4 bytes; number of the disk with the start of the zip64 end of central directory + '3333333322222222' . // 28 bytes; relative offset of the zip64 end of central directory record + '44444444' // 4 bytes;total number of disks + ); + } +} diff --git a/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryTest.php b/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryTest.php new file mode 100644 index 0000000..b86fb17 --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/Zip64/EndOfCentralDirectoryTest.php @@ -0,0 +1,41 @@ +assertSame( + bin2hex($descriptor), + '504b0606' . // 4 bytes;zip64 end of central dir signature - 0x06064b50 + '2f00000000000000' . // 8 bytes; size of zip64 end of central directory record + '3333' . // 2 bytes; version made by + '4444' . // 2 bytes; version needed to extract + '55555555' . // 4 bytes; number of this disk + '66666666' . // 4 bytes; number of the disk with the start of the central directory + '8888888877777777' . // 8 bytes; total number of entries in the central directory on this disk + 'aaaaaaaa99999999' . // 8 bytes; total number of entries in the central directory + 'ccccccccbbbbbbbb' . // 8 bytes; size of the central directory + 'eeeeeeeedddddddd' . // 8 bytes; offset of start of central directory with respect to the starting disk number + bin2hex('foo') + ); + } +} diff --git a/vendor/maennchen/zipstream-php/test/Zip64/ExtendedInformationExtraFieldTest.php b/vendor/maennchen/zipstream-php/test/Zip64/ExtendedInformationExtraFieldTest.php new file mode 100644 index 0000000..904783d --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/Zip64/ExtendedInformationExtraFieldTest.php @@ -0,0 +1,42 @@ +assertSame( + bin2hex($extraField), + '0100' . // 2 bytes; Tag for this "extra" block type + '1c00' . // 2 bytes; Size of this "extra" block + '6666666677777777' . // 8 bytes; Original uncompressed file size + '8888888899999999' . // 8 bytes; Size of compressed data + '1111111122222222' . // 8 bytes; Offset of local header record + '33333333' // 4 bytes; Number of the disk on which this file starts + ); + } + + public function testSerializesEmptyCorrectly(): void + { + $extraField = ExtendedInformationExtraField::generate(); + + $this->assertSame( + bin2hex($extraField), + '0100' . // 2 bytes; Tag for this "extra" block type + '0000' // 2 bytes; Size of this "extra" block + ); + } +} diff --git a/vendor/maennchen/zipstream-php/test/ZipStreamTest.php b/vendor/maennchen/zipstream-php/test/ZipStreamTest.php index 0aa6535..f4ead1d 100644 --- a/vendor/maennchen/zipstream-php/test/ZipStreamTest.php +++ b/vendor/maennchen/zipstream-php/test/ZipStreamTest.php @@ -2,80 +2,49 @@ declare(strict_types=1); -namespace ZipStreamTest; +namespace ZipStream\Test; +use DateTimeImmutable; use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\StreamWrapper; use org\bovigo\vfs\vfsStream; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; -use RecursiveDirectoryIterator; -use RecursiveIteratorIterator; -use ReflectionClass; +use Psr\Http\Message\StreamInterface; +use RuntimeException; use ZipArchive; -use ZipStream\File; -use ZipStream\Option\Archive as ArchiveOptions; -use ZipStream\Option\File as FileOptions; -use ZipStream\Option\Method; -use ZipStream\Stream; +use ZipStream\CompressionMethod; +use ZipStream\Exception\FileNotFoundException; +use ZipStream\Exception\FileNotReadableException; +use ZipStream\Exception\FileSizeIncorrectException; +use ZipStream\Exception\OverflowException; +use ZipStream\Exception\ResourceActionException; +use ZipStream\Exception\SimulationFileUnknownException; +use ZipStream\Exception\StreamNotReadableException; +use ZipStream\Exception\StreamNotSeekableException; +use ZipStream\OperationMode; +use ZipStream\PackField; use ZipStream\ZipStream; -/** - * Test Class for the Main ZipStream CLass - */ class ZipStreamTest extends TestCase { - public function testFileNotFoundException(): void - { - $this->expectException(\ZipStream\Exception\FileNotFoundException::class); - // Get ZipStream Object - $zip = new ZipStream(); - - // Trigger error by adding a file which doesn't exist - $zip->addFileFromPath('foobar.php', '/foo/bar/foobar.php'); - } - - public function testFileNotReadableException(): void - { - // create new virtual filesystem - $root = vfsStream::setup('vfs'); - // create a virtual file with no permissions - $file = vfsStream::newFile('foo.txt', 0)->at($root)->setContent('bar'); - $zip = new ZipStream(); - $this->expectException(\ZipStream\Exception\FileNotReadableException::class); - $zip->addFileFromPath('foo.txt', $file->url()); - } - - public function testDostime(): void - { - // Allows testing of protected method - $class = new ReflectionClass(File::class); - $method = $class->getMethod('dostime'); - $method->setAccessible(true); - - $this->assertSame($method->invoke(null, 1416246368), 1165069764); - - // January 1 1980 - DOS Epoch. - $this->assertSame($method->invoke(null, 315532800), 2162688); - - // January 1 1970 -> January 1 1980 due to minimum DOS Epoch. @todo Throw Exception? - $this->assertSame($method->invoke(null, 0), 2162688); - } + use Util; + use Assertions; + use Tempfile; public function testAddFile(): void { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + ); $zip->addFile('sample.txt', 'Sample String Data'); $zip->addFile('test/sample.txt', 'More Simple Sample Data'); $zip->finish(); - fclose($stream); - $tmpDir = $this->validateAndExtractZip($tmp); + $tmpDir = $this->validateAndExtractZip($this->tempfile); $files = $this->getRecursiveFileList($tmpDir); $this->assertSame(['sample.txt', 'test' . DIRECTORY_SEPARATOR . 'sample.txt'], $files); @@ -86,12 +55,10 @@ class ZipStreamTest extends TestCase public function testAddFileUtf8NameComment(): void { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + ); $name = 'árvíztűrő tükörfúrógép.txt'; $content = 'Sample String Data'; @@ -100,32 +67,26 @@ class ZipStreamTest extends TestCase 'from Hungarian language in lowercase. ' . 'In uppercase: ÁÍŰŐÜÖÚÓÉ'; - $fileOptions = new FileOptions(); - $fileOptions->setComment($comment); - - $zip->addFile($name, $content, $fileOptions); + $zip->addFile(fileName: $name, data: $content, comment: $comment); $zip->finish(); - fclose($stream); - $tmpDir = $this->validateAndExtractZip($tmp); + $tmpDir = $this->validateAndExtractZip($this->tempfile); $files = $this->getRecursiveFileList($tmpDir); $this->assertSame([$name], $files); $this->assertStringEqualsFile($tmpDir . '/' . $name, $content); - $zipArch = new ZipArchive(); - $zipArch->open($tmp); - $this->assertSame($comment, $zipArch->getCommentName($name)); + $zipArchive = new ZipArchive(); + $zipArchive->open($this->tempfile); + $this->assertSame($comment, $zipArchive->getCommentName($name)); } public function testAddFileUtf8NameNonUtfComment(): void { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + ); $name = 'á.txt'; $content = 'any'; @@ -137,135 +98,111 @@ class ZipStreamTest extends TestCase // nearly CP850 (DOS-Latin-1) $guessComment = mb_convert_encoding($comment, 'UTF-8', 'CP850'); - $fileOptions = new FileOptions(); - $fileOptions->setComment($comment); + $zip->addFile(fileName: $name, data: $content, comment: $comment); - $zip->addFile($name, $content, $fileOptions); $zip->finish(); - fclose($stream); $zipArch = new ZipArchive(); - $zipArch->open($tmp); + $zipArch->open($this->tempfile); $this->assertSame($guessComment, $zipArch->getCommentName($name)); $this->assertSame($comment, $zipArch->getCommentName($name, ZipArchive::FL_ENC_RAW)); } - public function testAddFileNonUtf8NameUtfComment(): void - { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); - - $name = mb_convert_encoding('á.txt', 'ISO-8859-2', 'UTF-8'); - $content = 'any'; - $comment = 'á'; - - // @see https://libzip.org/documentation/zip_get_name.html - // - // mb_convert_encoding hasn't CP437. - // nearly CP850 (DOS-Latin-1) - $guessName = mb_convert_encoding($name, 'UTF-8', 'CP850'); - - $fileOptions = new FileOptions(); - $fileOptions->setComment($comment); - - $zip->addFile($name, $content, $fileOptions); - $zip->finish(); - fclose($stream); - - $tmpDir = $this->validateAndExtractZip($tmp); - - $files = $this->getRecursiveFileList($tmpDir); - - $this->assertNotSame([$name], $files); - $this->assertSame([$guessName], $files); - $this->assertStringEqualsFile($tmpDir . '/' . $guessName, $content); - - $zipArch = new ZipArchive(); - $zipArch->open($tmp); - $this->assertSame($guessName, $zipArch->getNameIndex(0)); - $this->assertSame($name, $zipArch->getNameIndex(0, ZipArchive::FL_ENC_RAW)); - $this->assertSame($comment, $zipArch->getCommentName($guessName)); - } - public function testAddFileWithStorageMethod(): void { - [$tmp, $stream] = $this->getTmpFileStream(); + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + ); - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); - - $fileOptions = new FileOptions(); - $fileOptions->setMethod(Method::STORE()); - - $zip->addFile('sample.txt', 'Sample String Data', $fileOptions); - $zip->addFile('test/sample.txt', 'More Simple Sample Data'); + $zip->addFile(fileName: 'sample.txt', data: 'Sample String Data', compressionMethod: CompressionMethod::STORE); + $zip->addFile(fileName: 'test/sample.txt', data: 'More Simple Sample Data'); $zip->finish(); - fclose($stream); - $zipArch = new ZipArchive(); - $zipArch->open($tmp); + $zipArchive = new ZipArchive(); + $zipArchive->open($this->tempfile); - $sample1 = $zipArch->statName('sample.txt'); - $sample12 = $zipArch->statName('test/sample.txt'); - $this->assertSame($sample1['comp_method'], Method::STORE); - $this->assertSame($sample12['comp_method'], Method::DEFLATE); + $sample1 = $zipArchive->statName('sample.txt'); + $sample12 = $zipArchive->statName('test/sample.txt'); + $this->assertSame($sample1['comp_method'], CompressionMethod::STORE->value); + $this->assertSame($sample12['comp_method'], CompressionMethod::DEFLATE->value); - $zipArch->close(); + $zipArchive->close(); } public function testAddFileFromPath(): void { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + ); [$tmpExample, $streamExample] = $this->getTmpFileStream(); fwrite($streamExample, 'Sample String Data'); fclose($streamExample); - $zip->addFileFromPath('sample.txt', $tmpExample); + $zip->addFileFromPath(fileName: 'sample.txt', path: $tmpExample); [$tmpExample, $streamExample] = $this->getTmpFileStream(); fwrite($streamExample, 'More Simple Sample Data'); fclose($streamExample); - $zip->addFileFromPath('test/sample.txt', $tmpExample); + $zip->addFileFromPath(fileName: 'test/sample.txt', path: $tmpExample); $zip->finish(); - fclose($stream); - $tmpDir = $this->validateAndExtractZip($tmp); + $tmpDir = $this->validateAndExtractZip($this->tempfile); $files = $this->getRecursiveFileList($tmpDir); $this->assertSame(['sample.txt', 'test' . DIRECTORY_SEPARATOR . 'sample.txt'], $files); $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); + + unlink($tmpExample); + } + + public function testAddFileFromPathFileNotFoundException(): void + { + $this->expectException(FileNotFoundException::class); + + // Get ZipStream Object + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + ); + + // Trigger error by adding a file which doesn't exist + $zip->addFileFromPath(fileName: 'foobar.php', path: '/foo/bar/foobar.php'); + } + + public function testAddFileFromPathFileNotReadableException(): void + { + $this->expectException(FileNotReadableException::class); + + // create new virtual filesystem + $root = vfsStream::setup('vfs'); + // create a virtual file with no permissions + $file = vfsStream::newFile('foo.txt', 0)->at($root)->setContent('bar'); + + // Get ZipStream Object + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + ); + + $zip->addFileFromPath('foo.txt', $file->url()); } public function testAddFileFromPathWithStorageMethod(): void { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); - - $fileOptions = new FileOptions(); - $fileOptions->setMethod(Method::STORE()); + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + ); [$tmpExample, $streamExample] = $this->getTmpFileStream(); fwrite($streamExample, 'Sample String Data'); fclose($streamExample); - $zip->addFileFromPath('sample.txt', $tmpExample, $fileOptions); + $zip->addFileFromPath(fileName: 'sample.txt', path: $tmpExample, compressionMethod: CompressionMethod::STORE); [$tmpExample, $streamExample] = $this->getTmpFileStream(); fwrite($streamExample, 'More Simple Sample Data'); @@ -273,31 +210,32 @@ class ZipStreamTest extends TestCase $zip->addFileFromPath('test/sample.txt', $tmpExample); $zip->finish(); - fclose($stream); - $zipArch = new ZipArchive(); - $zipArch->open($tmp); + $zipArchive = new ZipArchive(); + $zipArchive->open($this->tempfile); - $sample1 = $zipArch->statName('sample.txt'); - $this->assertSame(Method::STORE, $sample1['comp_method']); + $sample1 = $zipArchive->statName('sample.txt'); + $this->assertSame(CompressionMethod::STORE->value, $sample1['comp_method']); - $sample2 = $zipArch->statName('test/sample.txt'); - $this->assertSame(Method::DEFLATE, $sample2['comp_method']); + $sample2 = $zipArchive->statName('test/sample.txt'); + $this->assertSame(CompressionMethod::DEFLATE->value, $sample2['comp_method']); - $zipArch->close(); + $zipArchive->close(); } public function testAddLargeFileFromPath(): void { - $methods = [Method::DEFLATE(), Method::STORE()]; - $falseTrue = [false, true]; - foreach ($methods as $method) { - foreach ($falseTrue as $zeroHeader) { - foreach ($falseTrue as $zip64) { - if ($zeroHeader && $method->equals(Method::DEFLATE())) { + foreach ([CompressionMethod::DEFLATE, CompressionMethod::STORE] as $compressionMethod) { + foreach ([false, true] as $zeroHeader) { + foreach ([false, true] as $zip64) { + if ($zeroHeader && $compressionMethod === CompressionMethod::DEFLATE) { continue; } - $this->addLargeFileFileFromPath($method, $zeroHeader, $zip64); + $this->addLargeFileFileFromPath( + compressionMethod: $compressionMethod, + zeroHeader: $zeroHeader, + zip64: $zip64 + ); } } } @@ -305,33 +243,27 @@ class ZipStreamTest extends TestCase public function testAddFileFromStream(): void { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + ); // In this test we can't use temporary stream to feed data // because zlib.deflate filter gives empty string before PHP 7 // it works fine with file stream $streamExample = fopen(__FILE__, 'rb'); $zip->addFileFromStream('sample.txt', $streamExample); -// fclose($streamExample); - - $fileOptions = new FileOptions(); - $fileOptions->setMethod(Method::STORE()); + fclose($streamExample); $streamExample2 = fopen('php://temp', 'wb+'); fwrite($streamExample2, 'More Simple Sample Data'); rewind($streamExample2); // move the pointer back to the beginning of file. - $zip->addFileFromStream('test/sample.txt', $streamExample2, $fileOptions); -// fclose($streamExample2); + $zip->addFileFromStream('test/sample.txt', $streamExample2); //, $fileOptions); + fclose($streamExample2); $zip->finish(); - fclose($stream); - $tmpDir = $this->validateAndExtractZip($tmp); + $tmpDir = $this->validateAndExtractZip($this->tempfile); $files = $this->getRecursiveFileList($tmpDir); $this->assertSame(['sample.txt', 'test' . DIRECTORY_SEPARATOR . 'sample.txt'], $files); @@ -340,92 +272,409 @@ class ZipStreamTest extends TestCase $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); } + public function testAddFileFromStreamUnreadableInput(): void + { + $this->expectException(StreamNotReadableException::class); + + [$tmpInput] = $this->getTmpFileStream(); + + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + ); + + $streamUnreadable = fopen($tmpInput, 'w'); + + $zip->addFileFromStream('sample.json', $streamUnreadable); + } + + public function testAddFileFromStreamBrokenOutputWrite(): void + { + $this->expectException(ResourceActionException::class); + + $outputStream = FaultInjectionResource::getResource(['stream_write']); + + $zip = new ZipStream( + outputStream: $outputStream, + sendHttpHeaders: false, + ); + + $zip->addFile('sample.txt', 'foobar'); + } + + public function testAddFileFromStreamBrokenInputRewind(): void + { + $this->expectException(ResourceActionException::class); + + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + defaultEnableZeroHeader: false, + ); + + $fileStream = FaultInjectionResource::getResource(['stream_seek']); + + $zip->addFileFromStream('sample.txt', $fileStream, maxSize: 0); + } + + public function testAddFileFromStreamUnseekableInputWithoutZeroHeader(): void + { + $this->expectException(StreamNotSeekableException::class); + + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + defaultEnableZeroHeader: false, + ); + + if (file_exists('/dev/null')) { + $streamUnseekable = fopen('/dev/null', 'w+'); + } elseif (file_exists('NUL')) { + $streamUnseekable = fopen('NUL', 'w+'); + } else { + $this->markTestSkipped('Needs file /dev/null'); + } + + $zip->addFileFromStream('sample.txt', $streamUnseekable, maxSize: 2); + } + + public function testAddFileFromStreamUnseekableInputWithZeroHeader(): void + { + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + defaultEnableZeroHeader: true, + defaultCompressionMethod: CompressionMethod::STORE, + ); + + $streamUnseekable = StreamWrapper::getResource(new class ('test') extends EndlessCycleStream { + public function isSeekable(): bool + { + return false; + } + + public function seek(int $offset, int $whence = SEEK_SET): void + { + throw new RuntimeException('Not seekable'); + } + }); + + $zip->addFileFromStream('sample.txt', $streamUnseekable, maxSize: 7); + + $zip->finish(); + + $tmpDir = $this->validateAndExtractZip($this->tempfile); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertSame(['sample.txt'], $files); + + $this->assertSame(filesize($tmpDir . '/sample.txt'), 7); + } + public function testAddFileFromStreamWithStorageMethod(): void { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); - - $fileOptions = new FileOptions(); - $fileOptions->setMethod(Method::STORE()); + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + ); $streamExample = fopen('php://temp', 'wb+'); fwrite($streamExample, 'Sample String Data'); rewind($streamExample); // move the pointer back to the beginning of file. - $zip->addFileFromStream('sample.txt', $streamExample, $fileOptions); -// fclose($streamExample); + $zip->addFileFromStream('sample.txt', $streamExample, compressionMethod: CompressionMethod::STORE); + fclose($streamExample); $streamExample2 = fopen('php://temp', 'bw+'); fwrite($streamExample2, 'More Simple Sample Data'); rewind($streamExample2); // move the pointer back to the beginning of file. - $zip->addFileFromStream('test/sample.txt', $streamExample2); -// fclose($streamExample2); + $zip->addFileFromStream('test/sample.txt', $streamExample2, compressionMethod: CompressionMethod::DEFLATE); + fclose($streamExample2); $zip->finish(); - fclose($stream); - $zipArch = new ZipArchive(); - $zipArch->open($tmp); + $zipArchive = new ZipArchive(); + $zipArchive->open($this->tempfile); - $sample1 = $zipArch->statName('sample.txt'); - $this->assertSame(Method::STORE, $sample1['comp_method']); + $sample1 = $zipArchive->statName('sample.txt'); + $this->assertSame(CompressionMethod::STORE->value, $sample1['comp_method']); - $sample2 = $zipArch->statName('test/sample.txt'); - $this->assertSame(Method::DEFLATE, $sample2['comp_method']); + $sample2 = $zipArchive->statName('test/sample.txt'); + $this->assertSame(CompressionMethod::DEFLATE->value, $sample2['comp_method']); - $zipArch->close(); + $zipArchive->close(); } public function testAddFileFromPsr7Stream(): void { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + ); $body = 'Sample String Data'; $response = new Response(200, [], $body); - $fileOptions = new FileOptions(); - $fileOptions->setMethod(Method::STORE()); - - $zip->addFileFromPsr7Stream('sample.json', $response->getBody(), $fileOptions); + $zip->addFileFromPsr7Stream('sample.json', $response->getBody()); $zip->finish(); - fclose($stream); - $tmpDir = $this->validateAndExtractZip($tmp); + $tmpDir = $this->validateAndExtractZip($this->tempfile); $files = $this->getRecursiveFileList($tmpDir); $this->assertSame(['sample.json'], $files); $this->assertStringEqualsFile($tmpDir . '/sample.json', $body); } + #[Group('slow')] + public function testAddLargeFileFromPsr7Stream(): void + { + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + enableZip64: true, + ); + + $zip->addFileFromPsr7Stream( + fileName: 'sample.json', + stream: new EndlessCycleStream('0'), + maxSize: 0x100000000, + compressionMethod: CompressionMethod::STORE, + lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'), + ); + $zip->finish(); + + $tmpDir = $this->validateAndExtractZip($this->tempfile); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertSame(['sample.json'], $files); + $this->assertFileIsReadable($tmpDir . '/sample.json'); + $this->assertStringStartsWith('000000', file_get_contents(filename: $tmpDir . '/sample.json', length: 20)); + } + + public function testContinueFinishedZip(): void + { + $this->expectException(RuntimeException::class); + + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + ); + $zip->finish(); + + $zip->addFile('sample.txt', '1234'); + } + + #[Group('slow')] + public function testManyFilesWithoutZip64(): void + { + $this->expectException(OverflowException::class); + + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + enableZip64: false, + ); + + for ($i = 0; $i <= 0xFFFF; $i++) { + $zip->addFile('sample' . $i, ''); + } + + $zip->finish(); + } + + #[Group('slow')] + public function testManyFilesWithZip64(): void + { + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + enableZip64: true, + ); + + for ($i = 0; $i <= 0xFFFF; $i++) { + $zip->addFile('sample' . $i, ''); + } + + $zip->finish(); + + $tmpDir = $this->validateAndExtractZip($this->tempfile); + + $files = $this->getRecursiveFileList($tmpDir); + + $this->assertSame(count($files), 0x10000); + } + + #[Group('slow')] + public function testLongZipWithout64(): void + { + $this->expectException(OverflowException::class); + + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + enableZip64: false, + defaultCompressionMethod: CompressionMethod::STORE, + ); + + for ($i = 0; $i < 4; $i++) { + $zip->addFileFromPsr7Stream( + fileName: 'sample' . $i, + stream: new EndlessCycleStream('0'), + maxSize: 0xFFFFFFFF, + compressionMethod: CompressionMethod::STORE, + lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'), + ); + } + } + + #[Group('slow')] + public function testLongZipWith64(): void + { + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + enableZip64: true, + defaultCompressionMethod: CompressionMethod::STORE, + ); + + for ($i = 0; $i < 4; $i++) { + $zip->addFileFromPsr7Stream( + fileName: 'sample' . $i, + stream: new EndlessCycleStream('0'), + maxSize: 0x5FFFFFFF, + compressionMethod: CompressionMethod::STORE, + lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'), + ); + } + + $zip->finish(); + + $tmpDir = $this->validateAndExtractZip($this->tempfile); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertSame(['sample0', 'sample1', 'sample2', 'sample3'], $files); + } + + #[Group('slow')] + public function testAddLargeFileWithoutZip64WithZeroHeader(): void + { + $this->expectException(OverflowException::class); + + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + enableZip64: false, + defaultEnableZeroHeader: true, + ); + + $zip->addFileFromPsr7Stream( + fileName: 'sample.json', + stream: new EndlessCycleStream('0'), + maxSize: 0x100000000, + compressionMethod: CompressionMethod::STORE, + lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'), + ); + } + + #[Group('slow')] + public function testAddsZip64HeaderWhenNeeded(): void + { + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + enableZip64: true, + defaultEnableZeroHeader: false, + ); + + $zip->addFileFromPsr7Stream( + fileName: 'sample.json', + stream: new EndlessCycleStream('0'), + maxSize: 0x100000000, + compressionMethod: CompressionMethod::STORE, + lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'), + ); + + $zip->finish(); + + $tmpDir = $this->validateAndExtractZip($this->tempfile); + $files = $this->getRecursiveFileList($tmpDir); + + $this->assertSame(['sample.json'], $files); + $this->assertFileContains($this->tempfile, PackField::pack( + new PackField(format: 'V', value: 0x06064b50) + )); + } + + #[Group('slow')] + public function testDoesNotAddZip64HeaderWhenNotNeeded(): void + { + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + enableZip64: true, + defaultEnableZeroHeader: false, + ); + + $zip->addFileFromPsr7Stream( + fileName: 'sample.json', + stream: new EndlessCycleStream('0'), + maxSize: 0x10, + compressionMethod: CompressionMethod::STORE, + lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'), + ); + + $zip->finish(); + + $tmpDir = $this->validateAndExtractZip($this->tempfile); + $files = $this->getRecursiveFileList($tmpDir); + + $this->assertSame(['sample.json'], $files); + $this->assertFileDoesNotContain($this->tempfile, PackField::pack( + new PackField(format: 'V', value: 0x06064b50) + )); + } + + #[Group('slow')] + public function testAddLargeFileWithoutZip64WithoutZeroHeader(): void + { + $this->expectException(OverflowException::class); + + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + enableZip64: false, + defaultEnableZeroHeader: false, + ); + + $zip->addFileFromPsr7Stream( + fileName: 'sample.json', + stream: new EndlessCycleStream('0'), + maxSize: 0x100000000, + compressionMethod: CompressionMethod::STORE, + lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'), + ); + } + public function testAddFileFromPsr7StreamWithOutputToPsr7Stream(): void { - [$tmp, $resource] = $this->getTmpFileStream(); - $psr7OutputStream = new Stream($resource); + $psr7OutputStream = new ResourceStream($this->tempfileStream); - $options = new ArchiveOptions(); - $options->setOutputStream($psr7OutputStream); - - $zip = new ZipStream(null, $options); + $zip = new ZipStream( + outputStream: $psr7OutputStream, + sendHttpHeaders: false, + ); $body = 'Sample String Data'; $response = new Response(200, [], $body); - $fileOptions = new FileOptions(); - $fileOptions->setMethod(Method::STORE()); - - $zip->addFileFromPsr7Stream('sample.json', $response->getBody(), $fileOptions); + $zip->addFileFromPsr7Stream( + fileName: 'sample.json', + stream: $response->getBody(), + compressionMethod: CompressionMethod::STORE, + ); $zip->finish(); $psr7OutputStream->close(); - $tmpDir = $this->validateAndExtractZip($tmp); + $tmpDir = $this->validateAndExtractZip($this->tempfile); $files = $this->getRecursiveFileList($tmpDir); $this->assertSame(['sample.json'], $files); @@ -434,12 +683,10 @@ class ZipStreamTest extends TestCase public function testAddFileFromPsr7StreamWithFileSizeSet(): void { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + ); $body = 'Sample String Data'; $fileSize = strlen($body); @@ -447,37 +694,63 @@ class ZipStreamTest extends TestCase $fakePadding = "\0\0\0\0\0\0"; $response = new Response(200, [], $body . $fakePadding); - $fileOptions = new FileOptions(); - $fileOptions->setMethod(Method::STORE()); - $fileOptions->setSize($fileSize); - $zip->addFileFromPsr7Stream('sample.json', $response->getBody(), $fileOptions); + $zip->addFileFromPsr7Stream( + fileName: 'sample.json', + stream: $response->getBody(), + compressionMethod: CompressionMethod::STORE, + maxSize: $fileSize + ); $zip->finish(); - fclose($stream); - $tmpDir = $this->validateAndExtractZip($tmp); + $tmpDir = $this->validateAndExtractZip($this->tempfile); $files = $this->getRecursiveFileList($tmpDir); $this->assertSame(['sample.json'], $files); $this->assertStringEqualsFile($tmpDir . '/sample.json', $body); } + public function testCreateArchiveHeaders(): void + { + $headers = []; + + $httpHeaderCallback = function (string $header) use (&$headers) { + $headers[] = $header; + }; + + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: true, + outputName: 'example.zip', + httpHeaderCallback: $httpHeaderCallback, + ); + + $zip->addFile( + fileName: 'sample.json', + data: 'foo', + ); + $zip->finish(); + + $this->assertContains('Content-Type: application/x-zip', $headers); + $this->assertContains("Content-Disposition: attachment; filename*=UTF-8''example.zip", $headers); + $this->assertContains('Pragma: public', $headers); + $this->assertContains('Cache-Control: public, must-revalidate', $headers); + $this->assertContains('Content-Transfer-Encoding: binary', $headers); + } + public function testCreateArchiveWithFlushOptionSet(): void { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - $options->setFlushOutput(true); - - $zip = new ZipStream(null, $options); + $zip = new ZipStream( + outputStream: $this->tempfileStream, + flushOutput: true, + sendHttpHeaders: false, + ); $zip->addFile('sample.txt', 'Sample String Data'); $zip->addFile('test/sample.txt', 'More Simple Sample Data'); $zip->finish(); - fclose($stream); - $tmpDir = $this->validateAndExtractZip($tmp); + $tmpDir = $this->validateAndExtractZip($this->tempfile); $files = $this->getRecursiveFileList($tmpDir); $this->assertSame(['sample.txt', 'test' . DIRECTORY_SEPARATOR . 'sample.txt'], $files); @@ -492,111 +765,433 @@ class ZipStreamTest extends TestCase ob_end_flush(); $this->assertSame(0, ob_get_level()); - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - $options->setFlushOutput(true); - - $zip = new ZipStream(null, $options); + $zip = new ZipStream( + outputStream: $this->tempfileStream, + flushOutput: true, + sendHttpHeaders: false, + ); $zip->addFile('sample.txt', 'Sample String Data'); $zip->finish(); - fclose($stream); - $tmpDir = $this->validateAndExtractZip($tmp); + $tmpDir = $this->validateAndExtractZip($this->tempfile); $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); // WORKAROUND (2/2): add back output buffering so that PHPUnit doesn't complain that it is missing ob_start(); } - /** - * @return array - */ - protected function getTmpFileStream(): array + public function testAddEmptyDirectory(): void { - $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest'); - $stream = fopen($tmp, 'wb+'); + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + ); - return [$tmp, $stream]; + $zip->addDirectory('foo'); + + $zip->finish(); + + $tmpDir = $this->validateAndExtractZip($this->tempfile); + + $files = $this->getRecursiveFileList($tmpDir, includeDirectories: true); + + $this->assertContains('foo', $files); + + $this->assertFileExists($tmpDir . DIRECTORY_SEPARATOR . 'foo'); + $this->assertDirectoryExists($tmpDir . DIRECTORY_SEPARATOR . 'foo'); } - /** - * @param string $tmp - * @return string - */ - protected function validateAndExtractZip($tmp): string + public function testAddFileSimulate(): void { - $tmpDir = $this->getTmpDir(); + $create = function (OperationMode $operationMode): int { + $zip = new ZipStream( + sendHttpHeaders: false, + operationMode: $operationMode, + defaultEnableZeroHeader: true, + outputStream: $this->tempfileStream, + ); - $zipArch = new ZipArchive(); - $res = $zipArch->open($tmp); + $zip->addFile('sample.txt', 'Sample String Data'); + $zip->addFile('test/sample.txt', 'More Simple Sample Data'); - if ($res !== true) { - $this->fail("Failed to open {$tmp}. Code: $res"); + return $zip->finish(); + }; - return $tmpDir; - } - $this->assertSame(0, $zipArch->status); - $this->assertSame(0, $zipArch->statusSys); + $sizeExpected = $create(OperationMode::NORMAL); + $sizeActual = $create(OperationMode::SIMULATE_LAX); - $zipArch->extractTo($tmpDir); - $zipArch->close(); - - return $tmpDir; + $this->assertEquals($sizeExpected, $sizeActual); } - protected function getTmpDir(): string + public function testAddFileSimulateWithMaxSize(): void { - $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest'); - unlink($tmp); - mkdir($tmp) or $this->fail('Failed to make directory'); + $create = function (OperationMode $operationMode): int { + $zip = new ZipStream( + sendHttpHeaders: false, + operationMode: $operationMode, + defaultCompressionMethod: CompressionMethod::STORE, + defaultEnableZeroHeader: true, + outputStream: $this->tempfileStream, + ); - return $tmp; + $zip->addFile('sample.txt', 'Sample String Data', maxSize: 0); + + return $zip->finish(); + }; + + + $sizeExpected = $create(OperationMode::NORMAL); + $sizeActual = $create(OperationMode::SIMULATE_LAX); + + $this->assertEquals($sizeExpected, $sizeActual); } - /** - * @param string $path - * @return string[] - */ - protected function getRecursiveFileList(string $path): array + public function testAddFileSimulateWithFstat(): void { - $data = []; - $path = (string)realpath($path); - $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)); + $create = function (OperationMode $operationMode): int { + $zip = new ZipStream( + sendHttpHeaders: false, + operationMode: $operationMode, + defaultCompressionMethod: CompressionMethod::STORE, + defaultEnableZeroHeader: true, + outputStream: $this->tempfileStream, + ); - $pathLen = strlen($path); - foreach ($files as $file) { - $filePath = $file->getRealPath(); - if (!is_dir($filePath)) { - $data[] = substr($filePath, $pathLen + 1); + $zip->addFile('sample.txt', 'Sample String Data'); + $zip->addFile('test/sample.txt', 'More Simple Sample Data'); + + return $zip->finish(); + }; + + + $sizeExpected = $create(OperationMode::NORMAL); + $sizeActual = $create(OperationMode::SIMULATE_LAX); + + $this->assertEquals($sizeExpected, $sizeActual); + } + + public function testAddFileSimulateWithExactSizeZero(): void + { + $create = function (OperationMode $operationMode): int { + $zip = new ZipStream( + sendHttpHeaders: false, + operationMode: $operationMode, + defaultCompressionMethod: CompressionMethod::STORE, + defaultEnableZeroHeader: true, + outputStream: $this->tempfileStream, + ); + + $zip->addFile('sample.txt', 'Sample String Data', exactSize: 18); + + return $zip->finish(); + }; + + + $sizeExpected = $create(OperationMode::NORMAL); + $sizeActual = $create(OperationMode::SIMULATE_LAX); + + $this->assertEquals($sizeExpected, $sizeActual); + } + + public function testAddFileSimulateWithExactSizeInitial(): void + { + $create = function (OperationMode $operationMode): int { + $zip = new ZipStream( + sendHttpHeaders: false, + operationMode: $operationMode, + defaultCompressionMethod: CompressionMethod::STORE, + defaultEnableZeroHeader: false, + outputStream: $this->tempfileStream, + ); + + $zip->addFile('sample.txt', 'Sample String Data', exactSize: 18); + + return $zip->finish(); + }; + + $sizeExpected = $create(OperationMode::NORMAL); + $sizeActual = $create(OperationMode::SIMULATE_LAX); + + $this->assertEquals($sizeExpected, $sizeActual); + } + + public function testAddFileSimulateWithZeroSizeInFstat(): void + { + $create = function (OperationMode $operationMode): int { + $zip = new ZipStream( + sendHttpHeaders: false, + operationMode: $operationMode, + defaultCompressionMethod: CompressionMethod::STORE, + defaultEnableZeroHeader: false, + outputStream: $this->tempfileStream, + ); + + $zip->addFileFromPsr7Stream('sample.txt', new class implements StreamInterface { + public $pos = 0; + + public function __toString(): string + { + return 'test'; + } + + public function close(): void {} + + public function detach() {} + + public function getSize(): ?int + { + return null; + } + + public function tell(): int + { + return $this->pos; + } + + public function eof(): bool + { + return $this->pos >= 4; + } + + public function isSeekable(): bool + { + return true; + } + + public function seek(int $offset, int $whence = SEEK_SET): void + { + $this->pos = $offset; + } + + public function rewind(): void + { + $this->pos = 0; + } + + public function isWritable(): bool + { + return false; + } + + public function write(string $string): int + { + return 0; + } + + public function isReadable(): bool + { + return true; + } + + public function read(int $length): string + { + $data = substr('test', $this->pos, $length); + $this->pos += strlen($data); + return $data; + } + + public function getContents(): string + { + return $this->read(4); + } + + public function getMetadata(?string $key = null) + { + return $key !== null ? null : []; + } + }); + + return $zip->finish(); + }; + + $sizeExpected = $create(OperationMode::NORMAL); + $sizeActual = $create(OperationMode::SIMULATE_LAX); + + + $this->assertEquals($sizeExpected, $sizeActual); + } + + public function testAddFileSimulateWithWrongExactSize(): void + { + $this->expectException(FileSizeIncorrectException::class); + + $zip = new ZipStream( + sendHttpHeaders: false, + operationMode: OperationMode::SIMULATE_LAX, + ); + + $zip->addFile('sample.txt', 'Sample String Data', exactSize: 1000); + } + + public function testAddFileSimulateStrictZero(): void + { + $this->expectException(SimulationFileUnknownException::class); + + $zip = new ZipStream( + sendHttpHeaders: false, + operationMode: OperationMode::SIMULATE_STRICT, + defaultEnableZeroHeader: true + ); + + $zip->addFile('sample.txt', 'Sample String Data'); + } + + public function testAddFileSimulateStrictInitial(): void + { + $this->expectException(SimulationFileUnknownException::class); + + $zip = new ZipStream( + sendHttpHeaders: false, + operationMode: OperationMode::SIMULATE_STRICT, + defaultEnableZeroHeader: false + ); + + $zip->addFile('sample.txt', 'Sample String Data'); + } + + public function testAddFileCallbackStrict(): void + { + $this->expectException(SimulationFileUnknownException::class); + + $zip = new ZipStream( + sendHttpHeaders: false, + operationMode: OperationMode::SIMULATE_STRICT, + defaultEnableZeroHeader: false + ); + + $zip->addFileFromCallback('sample.txt', callback: function () { + return ''; + }); + } + + public function testAddFileCallbackLax(): void + { + $zip = new ZipStream( + operationMode: OperationMode::SIMULATE_LAX, + defaultEnableZeroHeader: false, + sendHttpHeaders: false, + ); + + $zip->addFileFromCallback('sample.txt', callback: function () { + return 'Sample String Data'; + }); + + $size = $zip->finish(); + + $this->assertEquals($size, 142); + } + + public function testExecuteSimulation(): void + { + $zip = new ZipStream( + operationMode: OperationMode::SIMULATE_STRICT, + defaultCompressionMethod: CompressionMethod::STORE, + defaultEnableZeroHeader: false, + sendHttpHeaders: false, + outputStream: $this->tempfileStream, + ); + + $zip->addFileFromCallback( + 'sample.txt', + exactSize: 18, + callback: function () { + return 'Sample String Data'; } - } + ); - sort($data); + $zip->addFileFromCallback( + '.gitkeep', + exactSize: 0, + callback: function () { + return ''; + } + ); - return $data; + $size = $zip->finish(); + + $this->assertEquals(filesize($this->tempfile), 0); + + $zip->executeSimulation(); + + clearstatcache(); + + $this->assertEquals(filesize($this->tempfile), $size); + + $tmpDir = $this->validateAndExtractZip($this->tempfile); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertSame(['.gitkeep', 'sample.txt'], $files); } - protected function addLargeFileFileFromPath($method, $zeroHeader, $zip64): void + public function testExecuteSimulationBeforeFinish(): void + { + $this->expectException(RuntimeException::class); + + $zip = new ZipStream( + operationMode: OperationMode::SIMULATE_LAX, + defaultEnableZeroHeader: false, + sendHttpHeaders: false, + outputStream: $this->tempfileStream, + ); + + $zip->executeSimulation(); + } + + #[Group('slow')] + public function testSimulationWithLargeZip64AndZeroHeader(): void + { + $zip = new ZipStream( + outputStream: $this->tempfileStream, + sendHttpHeaders: false, + operationMode: OperationMode::SIMULATE_STRICT, + defaultCompressionMethod: CompressionMethod::STORE, + outputName: 'archive.zip', + enableZip64: true, + defaultEnableZeroHeader: true + ); + + $zip->addFileFromPsr7Stream( + fileName: 'large', + stream: new EndlessCycleStream('large'), + exactSize: 0x120000000, // ~5gb + compressionMethod: CompressionMethod::STORE, + lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'), + ); + + $zip->addFileFromPsr7Stream( + fileName: 'small', + stream: new EndlessCycleStream('small'), + exactSize: 0x20, + compressionMethod: CompressionMethod::STORE, + lastModificationDateTime: new DateTimeImmutable('2022-01-01 01:01:01Z'), + ); + + $forecastedSize = $zip->finish(); + + $zip->executeSimulation(); + + $this->assertSame($forecastedSize, filesize($this->tempfile)); + + $this->validateAndExtractZip($this->tempfile); + } + + private function addLargeFileFileFromPath(CompressionMethod $compressionMethod, $zeroHeader, $zip64): void { [$tmp, $stream] = $this->getTmpFileStream(); - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - $options->setLargeFileMethod($method); - $options->setLargeFileSize(5); - $options->setZeroHeader($zeroHeader); - $options->setEnableZip64($zip64); - - $zip = new ZipStream(null, $options); + $zip = new ZipStream( + outputStream: $stream, + sendHttpHeaders: false, + defaultEnableZeroHeader: $zeroHeader, + enableZip64: $zip64, + ); [$tmpExample, $streamExample] = $this->getTmpFileStream(); for ($i = 0; $i <= 10000; $i++) { - fwrite($streamExample, sha1((string)$i)); + fwrite($streamExample, sha1((string) $i)); if ($i % 100 === 0) { fwrite($streamExample, "\n"); } @@ -614,6 +1209,8 @@ class ZipStreamTest extends TestCase $files = $this->getRecursiveFileList($tmpDir); $this->assertSame(['sample.txt'], $files); - $this->assertSame(sha1_file($tmpDir . '/sample.txt'), $shaExample, "SHA-1 Mismatch Method: {$method}"); + $this->assertSame(sha1_file($tmpDir . '/sample.txt'), $shaExample, "SHA-1 Mismatch Method: {$compressionMethod->value}"); + + unlink($tmp); } } diff --git a/vendor/maennchen/zipstream-php/test/Zs/ExtendedInformationExtraFieldTest.php b/vendor/maennchen/zipstream-php/test/Zs/ExtendedInformationExtraFieldTest.php new file mode 100644 index 0000000..2b8dbed --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/Zs/ExtendedInformationExtraFieldTest.php @@ -0,0 +1,22 @@ +assertSame( + bin2hex((string) $extraField), + '5356' . // 2 bytes; Tag for this "extra" block type + '0000' // 2 bytes; TODO: Document + ); + } +} diff --git a/vendor/maennchen/zipstream-php/test/bug/BugHonorFileTimeTest.php b/vendor/maennchen/zipstream-php/test/bug/BugHonorFileTimeTest.php deleted file mode 100644 index 05de4fe..0000000 --- a/vendor/maennchen/zipstream-php/test/bug/BugHonorFileTimeTest.php +++ /dev/null @@ -1,40 +0,0 @@ -setOutputStream(fopen('php://memory', 'wb')); - $fileOpt->setTime(clone $expectedTime); - - $zip = new ZipStream(null, $archiveOpt); - - $zip->addFile('sample.txt', 'Sample', $fileOpt); - - $zip->finish(); - - $this->assertEquals($expectedTime, $fileOpt->getTime()); - } -} diff --git a/vendor/myclabs/php-enum/LICENSE b/vendor/myclabs/php-enum/LICENSE deleted file mode 100644 index 2a8cf22..0000000 --- a/vendor/myclabs/php-enum/LICENSE +++ /dev/null @@ -1,18 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 My C-Labs - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/myclabs/php-enum/README.md b/vendor/myclabs/php-enum/README.md deleted file mode 100644 index 681d55e..0000000 --- a/vendor/myclabs/php-enum/README.md +++ /dev/null @@ -1,194 +0,0 @@ -# PHP Enum implementation inspired from SplEnum - -[![GitHub Actions][GA Image]][GA Link] -[![Latest Stable Version](https://poser.pugx.org/myclabs/php-enum/version.png)](https://packagist.org/packages/myclabs/php-enum) -[![Total Downloads](https://poser.pugx.org/myclabs/php-enum/downloads.png)](https://packagist.org/packages/myclabs/php-enum) -[![Psalm Shepherd][Shepherd Image]][Shepherd Link] - -Maintenance for this project is [supported via Tidelift](https://tidelift.com/subscription/pkg/packagist-myclabs-php-enum?utm_source=packagist-myclabs-php-enum&utm_medium=referral&utm_campaign=readme). - -## Why? - -First, and mainly, `SplEnum` is not integrated to PHP, you have to install the extension separately. - -Using an enum instead of class constants provides the following advantages: - -- You can use an enum as a parameter type: `function setAction(Action $action) {` -- You can use an enum as a return type: `function getAction() : Action {` -- You can enrich the enum with methods (e.g. `format`, `parse`, …) -- You can extend the enum to add new values (make your enum `final` to prevent it) -- You can get a list of all the possible values (see below) - -This Enum class is not intended to replace class constants, but only to be used when it makes sense. - -## Installation - -``` -composer require myclabs/php-enum -``` - -## Declaration - -```php -use MyCLabs\Enum\Enum; - -/** - * Action enum - */ -final class Action extends Enum -{ - private const VIEW = 'view'; - private const EDIT = 'edit'; -} -``` - -## Usage - -```php -$action = Action::VIEW(); - -// or with a dynamic key: -$action = Action::$key(); -// or with a dynamic value: -$action = Action::from($value); -// or -$action = new Action($value); -``` - -As you can see, static methods are automatically implemented to provide quick access to an enum value. - -One advantage over using class constants is to be able to use an enum as a parameter type: - -```php -function setAction(Action $action) { - // ... -} -``` - -## Documentation - -- `__construct()` The constructor checks that the value exist in the enum -- `__toString()` You can `echo $myValue`, it will display the enum value (value of the constant) -- `getValue()` Returns the current value of the enum -- `getKey()` Returns the key of the current value on Enum -- `equals()` Tests whether enum instances are equal (returns `true` if enum values are equal, `false` otherwise) - -Static methods: - -- `from()` Creates an Enum instance, checking that the value exist in the enum -- `toArray()` method Returns all possible values as an array (constant name in key, constant value in value) -- `keys()` Returns the names (keys) of all constants in the Enum class -- `values()` Returns instances of the Enum class of all Enum constants (constant name in key, Enum instance in value) -- `isValid()` Check if tested value is valid on enum set -- `isValidKey()` Check if tested key is valid on enum set -- `assertValidValue()` Assert the value is valid on enum set, throwing exception otherwise -- `search()` Return key for searched value - -### Static methods - -```php -final class Action extends Enum -{ - private const VIEW = 'view'; - private const EDIT = 'edit'; -} - -// Static method: -$action = Action::VIEW(); -$action = Action::EDIT(); -``` - -Static method helpers are implemented using [`__callStatic()`](http://www.php.net/manual/en/language.oop5.overloading.php#object.callstatic). - -If you care about IDE autocompletion, you can either implement the static methods yourself: - -```php -final class Action extends Enum -{ - private const VIEW = 'view'; - - /** - * @return Action - */ - public static function VIEW() { - return new Action(self::VIEW); - } -} -``` - -or you can use phpdoc (this is supported in PhpStorm for example): - -```php -/** - * @method static Action VIEW() - * @method static Action EDIT() - */ -final class Action extends Enum -{ - private const VIEW = 'view'; - private const EDIT = 'edit'; -} -``` - -## Native enums and migration -Native enum arrived to PHP in version 8.1: https://www.php.net/enumerations -If your project is running PHP 8.1+ or your library has it as a minimum requirement you should use it instead of this library. - -When migrating from `myclabs/php-enum`, the effort should be small if the usage was in the recommended way: -- private constants -- final classes -- no method overridden - -Changes for migration: -- Class definition should be changed from -```php -/** - * @method static Action VIEW() - * @method static Action EDIT() - */ -final class Action extends Enum -{ - private const VIEW = 'view'; - private const EDIT = 'edit'; -} -``` - to -```php -enum Action: string -{ - case VIEW = 'view'; - case EDIT = 'edit'; -} -``` -All places where the class was used as a type will continue to work. - -Usages and the change needed: - -| Operation | myclabs/php-enum | native enum | -|----------------------------------------------------------------|----------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Obtain an instance will change from | `$enumCase = Action::VIEW()` | `$enumCase = Action::VIEW` | -| Create an enum from a backed value | `$enumCase = new Action('view')` | `$enumCase = Action::from('view')` | -| Get the backed value of the enum instance | `$enumCase->getValue()` | `$enumCase->value` | -| Compare two enum instances | `$enumCase1 == $enumCase2`
or
`$enumCase1->equals($enumCase2)` | `$enumCase1 === $enumCase2` | -| Get the key/name of the enum instance | `$enumCase->getKey()` | `$enumCase->name` | -| Get a list of all the possible instances of the enum | `Action::values()` | `Action::cases()` | -| Get a map of possible instances of the enum mapped by name | `Action::values()` | `array_combine(array_map(fn($case) => $case->name, Action::cases()), Action::cases())`
or
`(new ReflectionEnum(Action::class))->getConstants()` | -| Get a list of all possible names of the enum | `Action::keys()` | `array_map(fn($case) => $case->name, Action::cases())` | -| Get a list of all possible backed values of the enum | `Action::toArray()` | `array_map(fn($case) => $case->value, Action::cases())` | -| Get a map of possible backed values of the enum mapped by name | `Action::toArray()` | `array_combine(array_map(fn($case) => $case->name, Action::cases()), array_map(fn($case) => $case->value, Action::cases()))`
or
`array_map(fn($case) => $case->value, (new ReflectionEnum(Action::class))->getConstants()))` | - -## Related projects - -- [PHP 8.1+ native enum](https://www.php.net/enumerations) -- [Doctrine enum mapping](https://github.com/acelaya/doctrine-enum-type) -- [Symfony ParamConverter integration](https://github.com/Ex3v/MyCLabsEnumParamConverter) -- [PHPStan integration](https://github.com/timeweb/phpstan-enum) - - -[GA Image]: https://github.com/myclabs/php-enum/workflows/CI/badge.svg - -[GA Link]: https://github.com/myclabs/php-enum/actions?query=workflow%3A%22CI%22+branch%3Amaster - -[Shepherd Image]: https://shepherd.dev/github/myclabs/php-enum/coverage.svg - -[Shepherd Link]: https://shepherd.dev/github/myclabs/php-enum diff --git a/vendor/myclabs/php-enum/SECURITY.md b/vendor/myclabs/php-enum/SECURITY.md deleted file mode 100644 index 84fd4e3..0000000 --- a/vendor/myclabs/php-enum/SECURITY.md +++ /dev/null @@ -1,11 +0,0 @@ -# Security Policy - -## Supported Versions - -Only the latest stable release is supported. - -## Reporting a Vulnerability - -To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). - -Tidelift will coordinate the fix and disclosure. diff --git a/vendor/myclabs/php-enum/composer.json b/vendor/myclabs/php-enum/composer.json deleted file mode 100644 index 978cb19..0000000 --- a/vendor/myclabs/php-enum/composer.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "myclabs/php-enum", - "type": "library", - "description": "PHP Enum implementation", - "keywords": ["enum"], - "homepage": "http://github.com/myclabs/php-enum", - "license": "MIT", - "authors": [ - { - "name": "PHP Enum contributors", - "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" - } - ], - "autoload": { - "psr-4": { - "MyCLabs\\Enum\\": "src/" - }, - "classmap": [ - "stubs/Stringable.php" - ] - }, - "autoload-dev": { - "psr-4": { - "MyCLabs\\Tests\\Enum\\": "tests/" - } - }, - "require": { - "php": "^7.3 || ^8.0", - "ext-json": "*" - }, - "require-dev": { - "phpunit/phpunit": "^9.5", - "squizlabs/php_codesniffer": "1.*", - "vimeo/psalm": "^4.6.2" - } -} diff --git a/vendor/myclabs/php-enum/src/Enum.php b/vendor/myclabs/php-enum/src/Enum.php deleted file mode 100644 index 4c94cf6..0000000 --- a/vendor/myclabs/php-enum/src/Enum.php +++ /dev/null @@ -1,318 +0,0 @@ - - * @author Daniel Costa - * @author Mirosław Filip - * - * @psalm-template T - * @psalm-immutable - * @psalm-consistent-constructor - */ -abstract class Enum implements \JsonSerializable, \Stringable -{ - /** - * Enum value - * - * @var mixed - * @psalm-var T - */ - protected $value; - - /** - * Enum key, the constant name - * - * @var string - */ - private $key; - - /** - * Store existing constants in a static cache per object. - * - * - * @var array - * @psalm-var array> - */ - protected static $cache = []; - - /** - * Cache of instances of the Enum class - * - * @var array - * @psalm-var array> - */ - protected static $instances = []; - - /** - * Creates a new value of some type - * - * @psalm-pure - * @param mixed $value - * - * @psalm-param T $value - * @throws \UnexpectedValueException if incompatible type is given. - */ - public function __construct($value) - { - if ($value instanceof static) { - /** @psalm-var T */ - $value = $value->getValue(); - } - - /** @psalm-suppress ImplicitToStringCast assertValidValueReturningKey returns always a string but psalm has currently an issue here */ - $this->key = static::assertValidValueReturningKey($value); - - /** @psalm-var T */ - $this->value = $value; - } - - /** - * This method exists only for the compatibility reason when deserializing a previously serialized version - * that didn't had the key property - */ - public function __wakeup() - { - /** @psalm-suppress DocblockTypeContradiction key can be null when deserializing an enum without the key */ - if ($this->key === null) { - /** - * @psalm-suppress InaccessibleProperty key is not readonly as marked by psalm - * @psalm-suppress PossiblyFalsePropertyAssignmentValue deserializing a case that was removed - */ - $this->key = static::search($this->value); - } - } - - /** - * @param mixed $value - * @return static - */ - public static function from($value): self - { - $key = static::assertValidValueReturningKey($value); - - return self::__callStatic($key, []); - } - - /** - * @psalm-pure - * @return mixed - * @psalm-return T - */ - public function getValue() - { - return $this->value; - } - - /** - * Returns the enum key (i.e. the constant name). - * - * @psalm-pure - * @return string - */ - public function getKey() - { - return $this->key; - } - - /** - * @psalm-pure - * @psalm-suppress InvalidCast - * @return string - */ - public function __toString() - { - return (string)$this->value; - } - - /** - * Determines if Enum should be considered equal with the variable passed as a parameter. - * Returns false if an argument is an object of different class or not an object. - * - * This method is final, for more information read https://github.com/myclabs/php-enum/issues/4 - * - * @psalm-pure - * @psalm-param mixed $variable - * @return bool - */ - final public function equals($variable = null): bool - { - return $variable instanceof self - && $this->getValue() === $variable->getValue() - && static::class === \get_class($variable); - } - - /** - * Returns the names (keys) of all constants in the Enum class - * - * @psalm-pure - * @psalm-return list - * @return array - */ - public static function keys() - { - return \array_keys(static::toArray()); - } - - /** - * Returns instances of the Enum class of all Enum constants - * - * @psalm-pure - * @psalm-return array - * @return static[] Constant name in key, Enum instance in value - */ - public static function values() - { - $values = array(); - - /** @psalm-var T $value */ - foreach (static::toArray() as $key => $value) { - $values[$key] = new static($value); - } - - return $values; - } - - /** - * Returns all possible values as an array - * - * @psalm-pure - * @psalm-suppress ImpureStaticProperty - * - * @psalm-return array - * @return array Constant name in key, constant value in value - */ - public static function toArray() - { - $class = static::class; - - if (!isset(static::$cache[$class])) { - /** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */ - $reflection = new \ReflectionClass($class); - /** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */ - static::$cache[$class] = $reflection->getConstants(); - } - - return static::$cache[$class]; - } - - /** - * Check if is valid enum value - * - * @param $value - * @psalm-param mixed $value - * @psalm-pure - * @psalm-assert-if-true T $value - * @return bool - */ - public static function isValid($value) - { - return \in_array($value, static::toArray(), true); - } - - /** - * Asserts valid enum value - * - * @psalm-pure - * @psalm-assert T $value - * @param mixed $value - */ - public static function assertValidValue($value): void - { - self::assertValidValueReturningKey($value); - } - - /** - * Asserts valid enum value - * - * @psalm-pure - * @psalm-assert T $value - * @param mixed $value - * @return string - */ - private static function assertValidValueReturningKey($value): string - { - if (false === ($key = static::search($value))) { - throw new \UnexpectedValueException("Value '$value' is not part of the enum " . static::class); - } - - return $key; - } - - /** - * Check if is valid enum key - * - * @param $key - * @psalm-param string $key - * @psalm-pure - * @return bool - */ - public static function isValidKey($key) - { - $array = static::toArray(); - - return isset($array[$key]) || \array_key_exists($key, $array); - } - - /** - * Return key for value - * - * @param mixed $value - * - * @psalm-param mixed $value - * @psalm-pure - * @return string|false - */ - public static function search($value) - { - return \array_search($value, static::toArray(), true); - } - - /** - * Returns a value when called statically like so: MyEnum::SOME_VALUE() given SOME_VALUE is a class constant - * - * @param string $name - * @param array $arguments - * - * @return static - * @throws \BadMethodCallException - * - * @psalm-pure - */ - public static function __callStatic($name, $arguments) - { - $class = static::class; - if (!isset(self::$instances[$class][$name])) { - $array = static::toArray(); - if (!isset($array[$name]) && !\array_key_exists($name, $array)) { - $message = "No static method or enum constant '$name' in class " . static::class; - throw new \BadMethodCallException($message); - } - return self::$instances[$class][$name] = new static($array[$name]); - } - return clone self::$instances[$class][$name]; - } - - /** - * Specify data which should be serialized to JSON. This method returns data that can be serialized by json_encode() - * natively. - * - * @return mixed - * @link http://php.net/manual/en/jsonserializable.jsonserialize.php - * @psalm-pure - */ - #[\ReturnTypeWillChange] - public function jsonSerialize() - { - return $this->getValue(); - } -} diff --git a/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php b/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php deleted file mode 100644 index 302bf80..0000000 --- a/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php +++ /dev/null @@ -1,54 +0,0 @@ -register(new \MyCLabs\Enum\PHPUnit\Comparator()); - */ -final class Comparator extends \SebastianBergmann\Comparator\Comparator -{ - public function accepts($expected, $actual) - { - return $expected instanceof Enum && ( - $actual instanceof Enum || $actual === null - ); - } - - /** - * @param Enum $expected - * @param Enum|null $actual - * - * @return void - */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false) - { - if ($expected->equals($actual)) { - return; - } - - throw new ComparisonFailure( - $expected, - $actual, - $this->formatEnum($expected), - $this->formatEnum($actual), - false, - 'Failed asserting that two Enums are equal.' - ); - } - - private function formatEnum(Enum $enum = null) - { - if ($enum === null) { - return "null"; - } - - return get_class($enum)."::{$enum->getKey()}()"; - } -} diff --git a/vendor/myclabs/php-enum/stubs/Stringable.php b/vendor/myclabs/php-enum/stubs/Stringable.php deleted file mode 100644 index 4811af7..0000000 --- a/vendor/myclabs/php-enum/stubs/Stringable.php +++ /dev/null @@ -1,11 +0,0 @@ -=7.4.0 <8.5.0", + "php": "^7.4 || ^8.0", "ext-ctype": "*", "ext-dom": "*", "ext-fileinfo": "*", diff --git a/vendor/psr/http-factory/composer.json b/vendor/psr/http-factory/composer.json index d1bbdde..82a1d32 100644 --- a/vendor/psr/http-factory/composer.json +++ b/vendor/psr/http-factory/composer.json @@ -1,6 +1,6 @@ { "name": "psr/http-factory", - "description": "Common interfaces for PSR-7 HTTP message factories", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ "psr", "psr-7", @@ -18,8 +18,11 @@ "homepage": "https://www.php-fig.org/" } ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, "require": { - "php": ">=7.0.0", + "php": ">=7.1", "psr/http-message": "^1.0 || ^2.0" }, "autoload": { diff --git a/vendor/psr/http-factory/src/UploadedFileFactoryInterface.php b/vendor/psr/http-factory/src/UploadedFileFactoryInterface.php index 7db4e30..d7adbf0 100644 --- a/vendor/psr/http-factory/src/UploadedFileFactoryInterface.php +++ b/vendor/psr/http-factory/src/UploadedFileFactoryInterface.php @@ -15,10 +15,10 @@ interface UploadedFileFactoryInterface * * @param StreamInterface $stream Underlying stream representing the * uploaded file content. - * @param int $size in bytes + * @param int|null $size in bytes * @param int $error PHP file upload error - * @param string $clientFilename Filename as provided by the client, if any. - * @param string $clientMediaType Media type as provided by the client, if any. + * @param string|null $clientFilename Filename as provided by the client, if any. + * @param string|null $clientMediaType Media type as provided by the client, if any. * * @return UploadedFileInterface * @@ -26,9 +26,9 @@ interface UploadedFileFactoryInterface */ public function createUploadedFile( StreamInterface $stream, - int $size = null, + ?int $size = null, int $error = \UPLOAD_ERR_OK, - string $clientFilename = null, - string $clientMediaType = null + ?string $clientFilename = null, + ?string $clientMediaType = null ): UploadedFileInterface; } diff --git a/vendor/psr/http-message/composer.json b/vendor/psr/http-message/composer.json index 56e8c0a..c66e5ab 100644 --- a/vendor/psr/http-message/composer.json +++ b/vendor/psr/http-message/composer.json @@ -7,7 +7,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "require": { @@ -20,7 +20,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "2.0.x-dev" } } } diff --git a/vendor/psr/http-message/src/MessageInterface.php b/vendor/psr/http-message/src/MessageInterface.php index 8cdb4ed..a83c985 100644 --- a/vendor/psr/http-message/src/MessageInterface.php +++ b/vendor/psr/http-message/src/MessageInterface.php @@ -1,7 +1,5 @@ =8.0.2" + "php": ">=8.1" }, "autoload": { "files": [ @@ -25,7 +25,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.6-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/vendor/symfony/event-dispatcher-contracts/.gitignore b/vendor/symfony/event-dispatcher-contracts/.gitignore deleted file mode 100644 index c49a5d8..0000000 --- a/vendor/symfony/event-dispatcher-contracts/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/vendor/symfony/event-dispatcher-contracts/Event.php b/vendor/symfony/event-dispatcher-contracts/Event.php index 384a650..2e7f998 100644 --- a/vendor/symfony/event-dispatcher-contracts/Event.php +++ b/vendor/symfony/event-dispatcher-contracts/Event.php @@ -32,9 +32,6 @@ class Event implements StoppableEventInterface { private bool $propagationStopped = false; - /** - * {@inheritdoc} - */ public function isPropagationStopped(): bool { return $this->propagationStopped; diff --git a/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php index 351dc51..2d7840d 100644 --- a/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php +++ b/vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php @@ -21,11 +21,13 @@ interface EventDispatcherInterface extends PsrEventDispatcherInterface /** * Dispatches an event to all registered listeners. * - * @param object $event The event to pass to the event handlers/listeners + * @template T of object + * + * @param T $event The event to pass to the event handlers/listeners * @param string|null $eventName The name of the event to dispatch. If not supplied, * the class of $event should be used instead. * - * @return object The passed $event MUST be returned + * @return T The passed $event MUST be returned */ - public function dispatch(object $event, string $eventName = null): object; + public function dispatch(object $event, ?string $eventName = null): object; } diff --git a/vendor/symfony/event-dispatcher-contracts/LICENSE b/vendor/symfony/event-dispatcher-contracts/LICENSE index 74cdc2d..7536cae 100644 --- a/vendor/symfony/event-dispatcher-contracts/LICENSE +++ b/vendor/symfony/event-dispatcher-contracts/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2022 Fabien Potencier +Copyright (c) 2018-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/symfony/event-dispatcher-contracts/README.md b/vendor/symfony/event-dispatcher-contracts/README.md index b1ab4c0..332b961 100644 --- a/vendor/symfony/event-dispatcher-contracts/README.md +++ b/vendor/symfony/event-dispatcher-contracts/README.md @@ -3,7 +3,7 @@ Symfony EventDispatcher Contracts A set of abstractions extracted out of the Symfony components. -Can be used to build on semantics that the Symfony components proved useful - and +Can be used to build on semantics that the Symfony components proved useful and that already have battle tested implementations. See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/event-dispatcher-contracts/composer.json b/vendor/symfony/event-dispatcher-contracts/composer.json index b4c3933..d156b44 100644 --- a/vendor/symfony/event-dispatcher-contracts/composer.json +++ b/vendor/symfony/event-dispatcher-contracts/composer.json @@ -16,19 +16,16 @@ } ], "require": { - "php": ">=8.0.2", + "php": ">=8.1", "psr/event-dispatcher": "^1" }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, "autoload": { "psr-4": { "Symfony\\Contracts\\EventDispatcher\\": "" } }, "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.6-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/vendor/symfony/finder/CHANGELOG.md b/vendor/symfony/finder/CHANGELOG.md index 9e2fc5a..e838302 100644 --- a/vendor/symfony/finder/CHANGELOG.md +++ b/vendor/symfony/finder/CHANGELOG.md @@ -1,6 +1,17 @@ CHANGELOG ========= +6.4 +--- + + * Add early directory pruning to `Finder::filter()` + +6.2 +--- + + * Add `Finder::sortByExtension()` and `Finder::sortBySize()` + * Add `Finder::sortByCaseInsensitiveName()` to sort by name with case insensitive sorting methods + 6.0 --- diff --git a/vendor/symfony/finder/Comparator/Comparator.php b/vendor/symfony/finder/Comparator/Comparator.php index f1ba97d..41c02ac 100644 --- a/vendor/symfony/finder/Comparator/Comparator.php +++ b/vendor/symfony/finder/Comparator/Comparator.php @@ -16,16 +16,16 @@ namespace Symfony\Component\Finder\Comparator; */ class Comparator { - private string $target; private string $operator; - public function __construct(string $target, string $operator = '==') - { + public function __construct( + private string $target, + string $operator = '==', + ) { if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { - throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); + throw new \InvalidArgumentException(\sprintf('Invalid operator "%s".', $operator)); } - $this->target = $target; $this->operator = $operator; } @@ -50,19 +50,13 @@ class Comparator */ public function test(mixed $test): bool { - switch ($this->operator) { - case '>': - return $test > $this->target; - case '>=': - return $test >= $this->target; - case '<': - return $test < $this->target; - case '<=': - return $test <= $this->target; - case '!=': - return $test != $this->target; - } - - return $test == $this->target; + return match ($this->operator) { + '>' => $test > $this->target, + '>=' => $test >= $this->target, + '<' => $test < $this->target, + '<=' => $test <= $this->target, + '!=' => $test != $this->target, + default => $test == $this->target, + }; } } diff --git a/vendor/symfony/finder/Comparator/DateComparator.php b/vendor/symfony/finder/Comparator/DateComparator.php index 8f651e1..bcf93cf 100644 --- a/vendor/symfony/finder/Comparator/DateComparator.php +++ b/vendor/symfony/finder/Comparator/DateComparator.php @@ -26,17 +26,17 @@ class DateComparator extends Comparator public function __construct(string $test) { if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) { - throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test)); + throw new \InvalidArgumentException(\sprintf('Don\'t understand "%s" as a date test.', $test)); } try { - $date = new \DateTime($matches[2]); + $date = new \DateTimeImmutable($matches[2]); $target = $date->format('U'); - } catch (\Exception $e) { - throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); + } catch (\Exception) { + throw new \InvalidArgumentException(\sprintf('"%s" is not a valid date.', $matches[2])); } - $operator = $matches[1] ?? '=='; + $operator = $matches[1] ?: '=='; if ('since' === $operator || 'after' === $operator) { $operator = '>'; } diff --git a/vendor/symfony/finder/Comparator/NumberComparator.php b/vendor/symfony/finder/Comparator/NumberComparator.php index ff85d96..0ec0049 100644 --- a/vendor/symfony/finder/Comparator/NumberComparator.php +++ b/vendor/symfony/finder/Comparator/NumberComparator.php @@ -19,7 +19,7 @@ namespace Symfony\Component\Finder\Comparator; * magnitudes. * * The target value may use magnitudes of kilobytes (k, ki), - * megabytes (m, mi), or gigabytes (g, gi). Those suffixed + * megabytes (m, mi), or gigabytes (g, gi). Those suffixed * with an i use the appropriate 2**n version in accordance with the * IEC standard: http://physics.nist.gov/cuu/Units/binary.html * @@ -35,19 +35,19 @@ namespace Symfony\Component\Finder\Comparator; class NumberComparator extends Comparator { /** - * @param string|int $test A comparison string or an integer + * @param string|null $test A comparison string or null * * @throws \InvalidArgumentException If the test is not understood */ public function __construct(?string $test) { if (null === $test || !preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) { - throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test ?? 'null')); + throw new \InvalidArgumentException(\sprintf('Don\'t understand "%s" as a number test.', $test ?? 'null')); } $target = $matches[2]; if (!is_numeric($target)) { - throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target)); + throw new \InvalidArgumentException(\sprintf('Invalid number "%s".', $target)); } if (isset($matches[3])) { // magnitude diff --git a/vendor/symfony/finder/Finder.php b/vendor/symfony/finder/Finder.php index e5772c4..78673af 100644 --- a/vendor/symfony/finder/Finder.php +++ b/vendor/symfony/finder/Finder.php @@ -50,6 +50,7 @@ class Finder implements \IteratorAggregate, \Countable private array $notNames = []; private array $exclude = []; private array $filters = []; + private array $pruneFilters = []; private array $depths = []; private array $sizes = []; private bool $followLinks = false; @@ -123,7 +124,7 @@ class Finder implements \IteratorAggregate, \Countable public function depth(string|int|array $levels): static { foreach ((array) $levels as $level) { - $this->depths[] = new Comparator\NumberComparator($level); + $this->depths[] = new NumberComparator($level); } return $this; @@ -151,7 +152,7 @@ class Finder implements \IteratorAggregate, \Countable public function date(string|array $dates): static { foreach ((array) $dates as $date) { - $this->dates[] = new Comparator\DateComparator($date); + $this->dates[] = new DateComparator($date); } return $this; @@ -162,8 +163,8 @@ class Finder implements \IteratorAggregate, \Countable * * You can use patterns (delimited with / sign), globs or simple strings. * - * $finder->name('*.php') - * $finder->name('/\.php$/') // same as above + * $finder->name('/\.php$/') + * $finder->name('*.php') // same as above, without dot files * $finder->name('test.php') * $finder->name(['test.py', 'test.php']) * @@ -306,7 +307,7 @@ class Finder implements \IteratorAggregate, \Countable public function size(string|int|array $sizes): static { foreach ((array) $sizes as $size) { - $this->sizes[] = new Comparator\NumberComparator($size); + $this->sizes[] = new NumberComparator($size); } return $this; @@ -397,7 +398,7 @@ class Finder implements \IteratorAggregate, \Countable * * @param string|string[] $pattern VCS patterns to ignore */ - public static function addVCSPattern(string|array $pattern) + public static function addVCSPattern(string|array $pattern): void { foreach ((array) $pattern as $p) { self::$vcsPatterns[] = $p; @@ -424,6 +425,22 @@ class Finder implements \IteratorAggregate, \Countable return $this; } + /** + * Sorts files and directories by extension. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByExtension(): static + { + $this->sort = SortableIterator::SORT_BY_EXTENSION; + + return $this; + } + /** * Sorts files and directories by name. * @@ -435,7 +452,39 @@ class Finder implements \IteratorAggregate, \Countable */ public function sortByName(bool $useNaturalSort = false): static { - $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME; + $this->sort = $useNaturalSort ? SortableIterator::SORT_BY_NAME_NATURAL : SortableIterator::SORT_BY_NAME; + + return $this; + } + + /** + * Sorts files and directories by name case insensitive. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByCaseInsensitiveName(bool $useNaturalSort = false): static + { + $this->sort = $useNaturalSort ? SortableIterator::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE : SortableIterator::SORT_BY_NAME_CASE_INSENSITIVE; + + return $this; + } + + /** + * Sorts files and directories by size. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortBySize(): static + { + $this->sort = SortableIterator::SORT_BY_SIZE; return $this; } @@ -451,7 +500,7 @@ class Finder implements \IteratorAggregate, \Countable */ public function sortByType(): static { - $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; + $this->sort = SortableIterator::SORT_BY_TYPE; return $this; } @@ -469,7 +518,7 @@ class Finder implements \IteratorAggregate, \Countable */ public function sortByAccessedTime(): static { - $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; + $this->sort = SortableIterator::SORT_BY_ACCESSED_TIME; return $this; } @@ -501,7 +550,7 @@ class Finder implements \IteratorAggregate, \Countable */ public function sortByChangedTime(): static { - $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; + $this->sort = SortableIterator::SORT_BY_CHANGED_TIME; return $this; } @@ -519,7 +568,7 @@ class Finder implements \IteratorAggregate, \Countable */ public function sortByModifiedTime(): static { - $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; + $this->sort = SortableIterator::SORT_BY_MODIFIED_TIME; return $this; } @@ -530,14 +579,21 @@ class Finder implements \IteratorAggregate, \Countable * The anonymous function receives a \SplFileInfo and must return false * to remove files. * + * @param \Closure(SplFileInfo): bool $closure + * @param bool $prune Whether to skip traversing directories further + * * @return $this * * @see CustomFilterIterator */ - public function filter(\Closure $closure): static + public function filter(\Closure $closure, bool $prune = false): static { $this->filters[] = $closure; + if ($prune) { + $this->pruneFilters[] = $closure; + } + return $this; } @@ -585,9 +641,9 @@ class Finder implements \IteratorAggregate, \Countable $resolvedDirs[] = [$this->normalizeDir($dir)]; } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) { sort($glob); - $resolvedDirs[] = array_map([$this, 'normalizeDir'], $glob); + $resolvedDirs[] = array_map($this->normalizeDir(...), $glob); } else { - throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir)); + throw new DirectoryNotFoundException(\sprintf('The "%s" directory does not exist.', $dir)); } } @@ -615,7 +671,7 @@ class Finder implements \IteratorAggregate, \Countable $iterator = $this->searchInDirectory($this->dirs[0]); if ($this->sort || $this->reverseSorting) { - $iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); + $iterator = (new SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); } return $iterator; @@ -623,9 +679,7 @@ class Finder implements \IteratorAggregate, \Countable $iterator = new \AppendIterator(); foreach ($this->dirs as $dir) { - $iterator->append(new \IteratorIterator(new LazyIterator(function () use ($dir) { - return $this->searchInDirectory($dir); - }))); + $iterator->append(new \IteratorIterator(new LazyIterator(fn () => $this->searchInDirectory($dir)))); } foreach ($this->iterators as $it) { @@ -633,7 +687,7 @@ class Finder implements \IteratorAggregate, \Countable } if ($this->sort || $this->reverseSorting) { - $iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); + $iterator = (new SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); } return $iterator; @@ -645,8 +699,6 @@ class Finder implements \IteratorAggregate, \Countable * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array. * * @return $this - * - * @throws \InvalidArgumentException when the given argument is not iterable */ public function append(iterable $iterator): static { @@ -654,15 +706,13 @@ class Finder implements \IteratorAggregate, \Countable $this->iterators[] = $iterator->getIterator(); } elseif ($iterator instanceof \Iterator) { $this->iterators[] = $iterator; - } elseif (is_iterable($iterator)) { + } else { $it = new \ArrayIterator(); foreach ($iterator as $file) { $file = $file instanceof \SplFileInfo ? $file : new \SplFileInfo($file); $it[$file->getPathname()] = $file; } $this->iterators[] = $it; - } else { - throw new \InvalidArgumentException('Finder::append() method wrong argument type.'); } return $this; @@ -693,6 +743,10 @@ class Finder implements \IteratorAggregate, \Countable $exclude = $this->exclude; $notPaths = $this->notPaths; + if ($this->pruneFilters) { + $exclude = array_merge($exclude, $this->pruneFilters); + } + if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { $exclude = array_merge($exclude, self::$vcsPatterns); } @@ -732,13 +786,13 @@ class Finder implements \IteratorAggregate, \Countable $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs); if ($exclude) { - $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $exclude); + $iterator = new ExcludeDirectoryFilterIterator($iterator, $exclude); } $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); if ($minDepth > 0 || $maxDepth < \PHP_INT_MAX) { - $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth); + $iterator = new DepthRangeFilterIterator($iterator, $minDepth, $maxDepth); } if ($this->mode) { @@ -746,23 +800,23 @@ class Finder implements \IteratorAggregate, \Countable } if ($this->names || $this->notNames) { - $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); + $iterator = new FilenameFilterIterator($iterator, $this->names, $this->notNames); } if ($this->contains || $this->notContains) { - $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); + $iterator = new FilecontentFilterIterator($iterator, $this->contains, $this->notContains); } if ($this->sizes) { - $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); + $iterator = new SizeRangeFilterIterator($iterator, $this->sizes); } if ($this->dates) { - $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); + $iterator = new DateRangeFilterIterator($iterator, $this->dates); } if ($this->filters) { - $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); + $iterator = new CustomFilterIterator($iterator, $this->filters); } if ($this->paths || $notPaths) { diff --git a/vendor/symfony/finder/Gitignore.php b/vendor/symfony/finder/Gitignore.php index d42cca1..bf05c5b 100644 --- a/vendor/symfony/finder/Gitignore.php +++ b/vendor/symfony/finder/Gitignore.php @@ -43,7 +43,7 @@ class Gitignore foreach ($gitignoreLines as $line) { $line = preg_replace('~(? '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']', $regex); $regex = preg_replace('~(?:(?:\\\\\*){2,}(/?))+~', '(?:(?:(?!//).(? * - * @extends \FilterIterator - * @implements \RecursiveIterator + * @extends \FilterIterator + * + * @implements \RecursiveIterator */ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator { + /** @var \Iterator */ private \Iterator $iterator; private bool $isRecursive; + /** @var array */ private array $excludedDirs = []; private ?string $excludedPattern = null; + /** @var list */ + private array $pruneFilters = []; /** - * @param \Iterator $iterator The Iterator to filter - * @param string[] $directories An array of directories to exclude + * @param \Iterator $iterator The Iterator to filter + * @param list $directories An array of directories to exclude */ public function __construct(\Iterator $iterator, array $directories) { @@ -36,6 +43,16 @@ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \Recursi $this->isRecursive = $iterator instanceof \RecursiveIterator; $patterns = []; foreach ($directories as $directory) { + if (!\is_string($directory)) { + if (!\is_callable($directory)) { + throw new \InvalidArgumentException('Invalid PHP callback.'); + } + + $this->pruneFilters[] = $directory; + + continue; + } + $directory = rtrim($directory, '/'); if (!$this->isRecursive || str_contains($directory, '/')) { $patterns[] = preg_quote($directory, '#'); @@ -66,6 +83,14 @@ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \Recursi return !preg_match($this->excludedPattern, $path); } + if ($this->pruneFilters && $this->hasChildren()) { + foreach ($this->pruneFilters as $pruneFilter) { + if (!$pruneFilter($this->current())) { + return false; + } + } + } + return true; } diff --git a/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php b/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php index 2ed48fb..0d4a5fd 100644 --- a/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php +++ b/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php @@ -23,16 +23,14 @@ class FileTypeFilterIterator extends \FilterIterator public const ONLY_FILES = 1; public const ONLY_DIRECTORIES = 2; - private int $mode; - /** - * @param \Iterator $iterator The Iterator to filter - * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) + * @param \Iterator $iterator The Iterator to filter + * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) */ - public function __construct(\Iterator $iterator, int $mode) - { - $this->mode = $mode; - + public function __construct( + \Iterator $iterator, + private int $mode, + ) { parent::__construct($iterator); } diff --git a/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php b/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php index eaa7a5d..bdc71ff 100644 --- a/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php +++ b/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php @@ -11,13 +11,15 @@ namespace Symfony\Component\Finder\Iterator; +use Symfony\Component\Finder\SplFileInfo; + /** * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings). * * @author Fabien Potencier * @author Włodzimierz Gajda * - * @extends MultiplePcreFilterIterator + * @extends MultiplePcreFilterIterator */ class FilecontentFilterIterator extends MultiplePcreFilterIterator { diff --git a/vendor/symfony/finder/Iterator/LazyIterator.php b/vendor/symfony/finder/Iterator/LazyIterator.php index 71c4be8..5b5806b 100644 --- a/vendor/symfony/finder/Iterator/LazyIterator.php +++ b/vendor/symfony/finder/Iterator/LazyIterator.php @@ -22,7 +22,7 @@ class LazyIterator implements \IteratorAggregate public function __construct(callable $iteratorFactory) { - $this->iteratorFactory = $iteratorFactory instanceof \Closure ? $iteratorFactory : \Closure::fromCallable($iteratorFactory); + $this->iteratorFactory = $iteratorFactory(...); } public function getIterator(): \Traversable diff --git a/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php b/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php index 1e9e7ff..3450c49 100644 --- a/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php +++ b/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php @@ -23,13 +23,13 @@ namespace Symfony\Component\Finder\Iterator; */ abstract class MultiplePcreFilterIterator extends \FilterIterator { - protected $matchRegexps = []; - protected $noMatchRegexps = []; + protected array $matchRegexps = []; + protected array $noMatchRegexps = []; /** - * @param \Iterator $iterator The Iterator to filter - * @param string[] $matchPatterns An array of patterns that need to match - * @param string[] $noMatchPatterns An array of patterns that need to not match + * @param \Iterator $iterator The Iterator to filter + * @param string[] $matchPatterns An array of patterns that need to match + * @param string[] $noMatchPatterns An array of patterns that need to not match */ public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) { @@ -80,11 +80,7 @@ abstract class MultiplePcreFilterIterator extends \FilterIterator */ protected function isRegex(string $str): bool { - $availableModifiers = 'imsxuADU'; - - if (\PHP_VERSION_ID >= 80200) { - $availableModifiers .= 'n'; - } + $availableModifiers = 'imsxuADUn'; if (preg_match('/^(.{3,}?)['.$availableModifiers.']*$/', $str, $m)) { $start = substr($m[1], 0, 1); diff --git a/vendor/symfony/finder/Iterator/PathFilterIterator.php b/vendor/symfony/finder/Iterator/PathFilterIterator.php index bfe402a..c6d5813 100644 --- a/vendor/symfony/finder/Iterator/PathFilterIterator.php +++ b/vendor/symfony/finder/Iterator/PathFilterIterator.php @@ -11,13 +11,15 @@ namespace Symfony\Component\Finder\Iterator; +use Symfony\Component\Finder\SplFileInfo; + /** * PathFilterIterator filters files by path patterns (e.g. some/special/dir). * * @author Fabien Potencier * @author Włodzimierz Gajda * - * @extends MultiplePcreFilterIterator + * @extends MultiplePcreFilterIterator */ class PathFilterIterator extends MultiplePcreFilterIterator { diff --git a/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php b/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php index 4c9779f..f5fd2d4 100644 --- a/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php +++ b/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php @@ -18,11 +18,13 @@ use Symfony\Component\Finder\SplFileInfo; * Extends the \RecursiveDirectoryIterator to support relative paths. * * @author Victor Berchet + * + * @extends \RecursiveDirectoryIterator */ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator { private bool $ignoreUnreadableDirs; - private ?bool $rewindable = null; + private bool $ignoreFirstRewind = true; // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations private string $rootPath; @@ -61,8 +63,9 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator $subPathname .= $this->directorySeparator; } $subPathname .= $this->getFilename(); + $basePath = $this->rootPath; - if ('/' !== $basePath = $this->rootPath) { + if ('/' !== $basePath && !str_ends_with($basePath, $this->directorySeparator) && !str_ends_with($basePath, '/')) { $basePath .= $this->directorySeparator; } @@ -81,7 +84,7 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator parent::getChildren(); return true; - } catch (\UnexpectedValueException $e) { + } catch (\UnexpectedValueException) { // If directory is unreadable and finder is set to ignore it, skip children return false; } @@ -100,7 +103,6 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; // performance optimization to avoid redoing the same work in all children - $children->rewindable = &$this->rewindable; $children->rootPath = $this->rootPath; } @@ -110,36 +112,23 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator } } - /** - * Do nothing for non rewindable stream. - */ + public function next(): void + { + $this->ignoreFirstRewind = false; + + parent::next(); + } + public function rewind(): void { - if (false === $this->isRewindable()) { + // some streams like FTP are not rewindable, ignore the first rewind after creation, + // as newly created DirectoryIterator does not need to be rewound + if ($this->ignoreFirstRewind) { + $this->ignoreFirstRewind = false; + return; } parent::rewind(); } - - /** - * Checks if the stream is rewindable. - */ - public function isRewindable(): bool - { - if (null !== $this->rewindable) { - return $this->rewindable; - } - - if (false !== $stream = @opendir($this->getPath())) { - $infos = stream_get_meta_data($stream); - closedir($stream); - - if ($infos['seekable']) { - return $this->rewindable = true; - } - } - - return $this->rewindable = false; - } } diff --git a/vendor/symfony/finder/Iterator/SortableIterator.php b/vendor/symfony/finder/Iterator/SortableIterator.php index b6c34b6..177cd0b 100644 --- a/vendor/symfony/finder/Iterator/SortableIterator.php +++ b/vendor/symfony/finder/Iterator/SortableIterator.php @@ -27,7 +27,12 @@ class SortableIterator implements \IteratorAggregate public const SORT_BY_CHANGED_TIME = 4; public const SORT_BY_MODIFIED_TIME = 5; public const SORT_BY_NAME_NATURAL = 6; + public const SORT_BY_NAME_CASE_INSENSITIVE = 7; + public const SORT_BY_NAME_NATURAL_CASE_INSENSITIVE = 8; + public const SORT_BY_EXTENSION = 9; + public const SORT_BY_SIZE = 10; + /** @var \Traversable */ private \Traversable $iterator; private \Closure|int $sort; @@ -43,13 +48,13 @@ class SortableIterator implements \IteratorAggregate $order = $reverseOrder ? -1 : 1; if (self::SORT_BY_NAME === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); } elseif (self::SORT_BY_NAME_NATURAL === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + } elseif (self::SORT_BY_NAME_CASE_INSENSITIVE === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + } elseif (self::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); } elseif (self::SORT_BY_TYPE === $sort) { $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { if ($a->isDir() && $b->isFile()) { @@ -61,21 +66,19 @@ class SortableIterator implements \IteratorAggregate return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); }; } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * ($a->getATime() - $b->getATime()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getATime() - $b->getATime()); } elseif (self::SORT_BY_CHANGED_TIME === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * ($a->getCTime() - $b->getCTime()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getCTime() - $b->getCTime()); } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { - $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { - return $order * ($a->getMTime() - $b->getMTime()); - }; + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getMTime() - $b->getMTime()); + } elseif (self::SORT_BY_EXTENSION === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getExtension(), $b->getExtension()); + } elseif (self::SORT_BY_SIZE === $sort) { + $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getSize() - $b->getSize()); } elseif (self::SORT_BY_NONE === $sort) { $this->sort = $order; } elseif (\is_callable($sort)) { - $this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : \Closure::fromCallable($sort); + $this->sort = $reverseOrder ? static fn (\SplFileInfo $a, \SplFileInfo $b) => -$sort($a, $b) : $sort(...); } else { throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); } diff --git a/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php b/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php index e27158c..b278706 100644 --- a/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php +++ b/vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.php @@ -13,27 +13,37 @@ namespace Symfony\Component\Finder\Iterator; use Symfony\Component\Finder\Gitignore; +/** + * @extends \FilterIterator + */ final class VcsIgnoredFilterIterator extends \FilterIterator { - /** - * @var string - */ - private $baseDir; + private string $baseDir; /** * @var array */ - private $gitignoreFilesCache = []; + private array $gitignoreFilesCache = []; /** * @var array */ - private $ignoredPathsCache = []; + private array $ignoredPathsCache = []; + /** + * @param \Iterator $iterator + */ public function __construct(\Iterator $iterator, string $baseDir) { $this->baseDir = $this->normalizePath($baseDir); + foreach ([$this->baseDir, ...$this->parentDirectoriesUpwards($this->baseDir)] as $directory) { + if (@is_dir("{$directory}/.git")) { + $this->baseDir = $directory; + break; + } + } + parent::__construct($iterator); } @@ -58,7 +68,7 @@ final class VcsIgnoredFilterIterator extends \FilterIterator $ignored = false; - foreach ($this->parentsDirectoryDownward($fileRealPath) as $parentDirectory) { + foreach ($this->parentDirectoriesDownwards($fileRealPath) as $parentDirectory) { if ($this->isIgnored($parentDirectory)) { // rules in ignored directories are ignored, no need to check further. break; @@ -89,11 +99,11 @@ final class VcsIgnoredFilterIterator extends \FilterIterator /** * @return list */ - private function parentsDirectoryDownward(string $fileRealPath): array + private function parentDirectoriesUpwards(string $from): array { $parentDirectories = []; - $parentDirectory = $fileRealPath; + $parentDirectory = $from; while (true) { $newParentDirectory = \dirname($parentDirectory); @@ -103,16 +113,28 @@ final class VcsIgnoredFilterIterator extends \FilterIterator break; } - $parentDirectory = $newParentDirectory; - - if (0 !== strpos($parentDirectory, $this->baseDir)) { - break; - } - - $parentDirectories[] = $parentDirectory; + $parentDirectories[] = $parentDirectory = $newParentDirectory; } - return array_reverse($parentDirectories); + return $parentDirectories; + } + + private function parentDirectoriesUpTo(string $from, string $upTo): array + { + return array_filter( + $this->parentDirectoriesUpwards($from), + static fn (string $directory): bool => str_starts_with($directory, $upTo) + ); + } + + /** + * @return list + */ + private function parentDirectoriesDownwards(string $fileRealPath): array + { + return array_reverse( + $this->parentDirectoriesUpTo($fileRealPath, $this->baseDir) + ); } /** diff --git a/vendor/symfony/finder/LICENSE b/vendor/symfony/finder/LICENSE index 0083704..0138f8f 100644 --- a/vendor/symfony/finder/LICENSE +++ b/vendor/symfony/finder/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2023 Fabien Potencier +Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/symfony/finder/SplFileInfo.php b/vendor/symfony/finder/SplFileInfo.php index 867e8e8..2afc378 100644 --- a/vendor/symfony/finder/SplFileInfo.php +++ b/vendor/symfony/finder/SplFileInfo.php @@ -18,19 +18,17 @@ namespace Symfony\Component\Finder; */ class SplFileInfo extends \SplFileInfo { - private string $relativePath; - private string $relativePathname; - /** * @param string $file The file name * @param string $relativePath The relative path * @param string $relativePathname The relative path name */ - public function __construct(string $file, string $relativePath, string $relativePathname) - { + public function __construct( + string $file, + private string $relativePath, + private string $relativePathname, + ) { parent::__construct($file); - $this->relativePath = $relativePath; - $this->relativePathname = $relativePathname; } /** diff --git a/vendor/symfony/finder/composer.json b/vendor/symfony/finder/composer.json index 2e4b324..2b70600 100644 --- a/vendor/symfony/finder/composer.json +++ b/vendor/symfony/finder/composer.json @@ -16,7 +16,10 @@ } ], "require": { - "php": ">=8.0.2" + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" }, "autoload": { "psr-4": { "Symfony\\Component\\Finder\\": "" }, diff --git a/vendor/symfony/service-contracts/.gitignore b/vendor/symfony/service-contracts/.gitignore deleted file mode 100644 index c49a5d8..0000000 --- a/vendor/symfony/service-contracts/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/vendor/symfony/service-contracts/Attribute/SubscribedService.php b/vendor/symfony/service-contracts/Attribute/SubscribedService.php index 10d1bc3..f850b84 100644 --- a/vendor/symfony/service-contracts/Attribute/SubscribedService.php +++ b/vendor/symfony/service-contracts/Attribute/SubscribedService.php @@ -11,10 +11,15 @@ namespace Symfony\Contracts\Service\Attribute; -use Symfony\Contracts\Service\ServiceSubscriberTrait; +use Symfony\Contracts\Service\ServiceMethodsSubscriberTrait; +use Symfony\Contracts\Service\ServiceSubscriberInterface; /** - * Use with {@see ServiceSubscriberTrait} to mark a method's return type + * For use as the return value for {@see ServiceSubscriberInterface}. + * + * @example new SubscribedService('http_client', HttpClientInterface::class, false, new Target('githubApi')) + * + * Use with {@see ServiceMethodsSubscriberTrait} to mark a method's return type * as a subscribed service. * * @author Kevin Bond @@ -22,12 +27,21 @@ use Symfony\Contracts\Service\ServiceSubscriberTrait; #[\Attribute(\Attribute::TARGET_METHOD)] final class SubscribedService { + /** @var object[] */ + public array $attributes; + /** - * @param string|null $key The key to use for the service - * If null, use "ClassName::methodName" + * @param string|null $key The key to use for the service + * @param class-string|null $type The service class + * @param bool $nullable Whether the service is optional + * @param object|object[] $attributes One or more dependency injection attributes to use */ public function __construct( - public ?string $key = null + public ?string $key = null, + public ?string $type = null, + public bool $nullable = false, + array|object $attributes = [], ) { + $this->attributes = \is_array($attributes) ? $attributes : [$attributes]; } } diff --git a/vendor/symfony/service-contracts/LICENSE b/vendor/symfony/service-contracts/LICENSE index 74cdc2d..7536cae 100644 --- a/vendor/symfony/service-contracts/LICENSE +++ b/vendor/symfony/service-contracts/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2022 Fabien Potencier +Copyright (c) 2018-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/symfony/service-contracts/README.md b/vendor/symfony/service-contracts/README.md index 41e054a..42841a5 100644 --- a/vendor/symfony/service-contracts/README.md +++ b/vendor/symfony/service-contracts/README.md @@ -3,7 +3,7 @@ Symfony Service Contracts A set of abstractions extracted out of the Symfony components. -Can be used to build on semantics that the Symfony components proved useful - and +Can be used to build on semantics that the Symfony components proved useful and that already have battle tested implementations. See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/vendor/symfony/service-contracts/ResetInterface.php b/vendor/symfony/service-contracts/ResetInterface.php index 1af1075..a4f389b 100644 --- a/vendor/symfony/service-contracts/ResetInterface.php +++ b/vendor/symfony/service-contracts/ResetInterface.php @@ -26,5 +26,8 @@ namespace Symfony\Contracts\Service; */ interface ResetInterface { + /** + * @return void + */ public function reset(); } diff --git a/vendor/symfony/service-contracts/ServiceCollectionInterface.php b/vendor/symfony/service-contracts/ServiceCollectionInterface.php new file mode 100644 index 0000000..2333139 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceCollectionInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * A ServiceProviderInterface that is also countable and iterable. + * + * @author Kevin Bond + * + * @template-covariant T of mixed + * + * @extends ServiceProviderInterface + * @extends \IteratorAggregate + */ +interface ServiceCollectionInterface extends ServiceProviderInterface, \Countable, \IteratorAggregate +{ +} diff --git a/vendor/symfony/service-contracts/ServiceLocatorTrait.php b/vendor/symfony/service-contracts/ServiceLocatorTrait.php index 19d3e80..bbe4548 100644 --- a/vendor/symfony/service-contracts/ServiceLocatorTrait.php +++ b/vendor/symfony/service-contracts/ServiceLocatorTrait.php @@ -26,29 +26,22 @@ class_exists(NotFoundExceptionInterface::class); */ trait ServiceLocatorTrait { - private array $factories; private array $loading = []; private array $providedTypes; /** - * @param callable[] $factories + * @param array $factories */ - public function __construct(array $factories) - { - $this->factories = $factories; + public function __construct( + private array $factories, + ) { } - /** - * {@inheritdoc} - */ public function has(string $id): bool { return isset($this->factories[$id]); } - /** - * {@inheritdoc} - */ public function get(string $id): mixed { if (!isset($this->factories[$id])) { @@ -71,9 +64,6 @@ trait ServiceLocatorTrait } } - /** - * {@inheritdoc} - */ public function getProvidedServices(): array { if (!isset($this->providedTypes)) { @@ -100,16 +90,16 @@ trait ServiceLocatorTrait } else { $last = array_pop($alternatives); if ($alternatives) { - $message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last); + $message = \sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last); } else { - $message = sprintf('only knows about the "%s" service.', $last); + $message = \sprintf('only knows about the "%s" service.', $last); } } if ($this->loading) { - $message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message); + $message = \sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message); } else { - $message = sprintf('Service "%s" not found: the current service locator %s', $id, $message); + $message = \sprintf('Service "%s" not found: the current service locator %s', $id, $message); } return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface { @@ -118,7 +108,7 @@ trait ServiceLocatorTrait private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface { - return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface { + return new class(\sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface { }; } } diff --git a/vendor/symfony/service-contracts/ServiceMethodsSubscriberTrait.php b/vendor/symfony/service-contracts/ServiceMethodsSubscriberTrait.php new file mode 100644 index 0000000..844be89 --- /dev/null +++ b/vendor/symfony/service-contracts/ServiceMethodsSubscriberTrait.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\Attribute\Required; +use Symfony\Contracts\Service\Attribute\SubscribedService; + +/** + * Implementation of ServiceSubscriberInterface that determines subscribed services + * from methods that have the #[SubscribedService] attribute. + * + * Service ids are available as "ClassName::methodName" so that the implementation + * of subscriber methods can be just `return $this->container->get(__METHOD__);`. + * + * @author Kevin Bond + */ +trait ServiceMethodsSubscriberTrait +{ + protected ContainerInterface $container; + + public static function getSubscribedServices(): array + { + $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : []; + + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if (self::class !== $method->getDeclaringClass()->name) { + continue; + } + + if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) { + continue; + } + + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + throw new \LogicException(\sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); + } + + if (!$returnType = $method->getReturnType()) { + throw new \LogicException(\sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); + } + + /* @var SubscribedService $attribute */ + $attribute = $attribute->newInstance(); + $attribute->key ??= self::class.'::'.$method->name; + $attribute->type ??= $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; + $attribute->nullable = $attribute->nullable ?: $returnType->allowsNull(); + + if ($attribute->attributes) { + $services[] = $attribute; + } else { + $services[$attribute->key] = ($attribute->nullable ? '?' : '').$attribute->type; + } + } + + return $services; + } + + #[Required] + public function setContainer(ContainerInterface $container): ?ContainerInterface + { + $ret = null; + if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { + $ret = parent::setContainer($container); + } + + $this->container = $container; + + return $ret; + } +} diff --git a/vendor/symfony/service-contracts/ServiceProviderInterface.php b/vendor/symfony/service-contracts/ServiceProviderInterface.php index c60ad0b..2e71f00 100644 --- a/vendor/symfony/service-contracts/ServiceProviderInterface.php +++ b/vendor/symfony/service-contracts/ServiceProviderInterface.php @@ -18,9 +18,18 @@ use Psr\Container\ContainerInterface; * * @author Nicolas Grekas * @author Mateusz Sip + * + * @template-covariant T of mixed */ interface ServiceProviderInterface extends ContainerInterface { + /** + * @return T + */ + public function get(string $id): mixed; + + public function has(string $id): bool; + /** * Returns an associative array of service types keyed by the identifiers provided by the current container. * @@ -30,7 +39,7 @@ interface ServiceProviderInterface extends ContainerInterface * * ['foo' => '?'] means the container provides service name "foo" of unspecified type * * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null * - * @return string[] The provided service types, keyed by service names + * @return array The provided service types, keyed by service names */ public function getProvidedServices(): array; } diff --git a/vendor/symfony/service-contracts/ServiceSubscriberInterface.php b/vendor/symfony/service-contracts/ServiceSubscriberInterface.php index 881ab97..3da1916 100644 --- a/vendor/symfony/service-contracts/ServiceSubscriberInterface.php +++ b/vendor/symfony/service-contracts/ServiceSubscriberInterface.php @@ -11,6 +11,8 @@ namespace Symfony\Contracts\Service; +use Symfony\Contracts\Service\Attribute\SubscribedService; + /** * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method. * @@ -29,7 +31,8 @@ namespace Symfony\Contracts\Service; interface ServiceSubscriberInterface { /** - * Returns an array of service types required by such instances, optionally keyed by the service names used internally. + * Returns an array of service types (or {@see SubscribedService} objects) required + * by such instances, optionally keyed by the service names used internally. * * For mandatory dependencies: * @@ -47,7 +50,13 @@ interface ServiceSubscriberInterface * * ['?Psr\Log\LoggerInterface'] is a shortcut for * * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface'] * - * @return string[] The required service types, optionally keyed by service names + * additionally, an array of {@see SubscribedService}'s can be returned: + * + * * [new SubscribedService('logger', Psr\Log\LoggerInterface::class)] + * * [new SubscribedService(type: Psr\Log\LoggerInterface::class, nullable: true)] + * * [new SubscribedService('http_client', HttpClientInterface::class, attributes: new Target('githubApi'))] + * + * @return string[]|SubscribedService[] The required service types, optionally keyed by service names */ public static function getSubscribedServices(): array; } diff --git a/vendor/symfony/service-contracts/ServiceSubscriberTrait.php b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php index ee9d9d9..ed4cec0 100644 --- a/vendor/symfony/service-contracts/ServiceSubscriberTrait.php +++ b/vendor/symfony/service-contracts/ServiceSubscriberTrait.php @@ -12,22 +12,26 @@ namespace Symfony\Contracts\Service; use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\Attribute\Required; use Symfony\Contracts\Service\Attribute\SubscribedService; +trigger_deprecation('symfony/contracts', 'v3.5', '"%s" is deprecated, use "ServiceMethodsSubscriberTrait" instead.', ServiceSubscriberTrait::class); + /** - * Implementation of ServiceSubscriberInterface that determines subscribed services from - * method return types. Service ids are available as "ClassName::methodName". + * Implementation of ServiceSubscriberInterface that determines subscribed services + * from methods that have the #[SubscribedService] attribute. + * + * Service ids are available as "ClassName::methodName" so that the implementation + * of subscriber methods can be just `return $this->container->get(__METHOD__);`. + * + * @property ContainerInterface $container * * @author Kevin Bond + * + * @deprecated since symfony/contracts v3.5, use ServiceMethodsSubscriberTrait instead */ trait ServiceSubscriberTrait { - /** @var ContainerInterface */ - protected $container; - - /** - * {@inheritdoc} - */ public static function getSubscribedServices(): array { $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : []; @@ -42,36 +46,39 @@ trait ServiceSubscriberTrait } if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { - throw new \LogicException(sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); + throw new \LogicException(\sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); } if (!$returnType = $method->getReturnType()) { - throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); + throw new \LogicException(\sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); } - $serviceId = $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; + /* @var SubscribedService $attribute */ + $attribute = $attribute->newInstance(); + $attribute->key ??= self::class.'::'.$method->name; + $attribute->type ??= $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; + $attribute->nullable = $attribute->nullable ?: $returnType->allowsNull(); - if ($returnType->allowsNull()) { - $serviceId = '?'.$serviceId; + if ($attribute->attributes) { + $services[] = $attribute; + } else { + $services[$attribute->key] = ($attribute->nullable ? '?' : '').$attribute->type; } - - $services[$attribute->newInstance()->key ?? self::class.'::'.$method->name] = $serviceId; } return $services; } - /** - * @required - */ + #[Required] public function setContainer(ContainerInterface $container): ?ContainerInterface { - $this->container = $container; - + $ret = null; if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { - return parent::setContainer($container); + $ret = parent::setContainer($container); } - return null; + $this->container = $container; + + return $ret; } } diff --git a/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php index 88f6a06..07d12b4 100644 --- a/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php +++ b/vendor/symfony/service-contracts/Test/ServiceLocatorTest.php @@ -11,82 +11,13 @@ namespace Symfony\Contracts\Service\Test; -use PHPUnit\Framework\TestCase; -use Psr\Container\ContainerInterface; -use Symfony\Contracts\Service\ServiceLocatorTrait; +class_alias(ServiceLocatorTestCase::class, ServiceLocatorTest::class); -abstract class ServiceLocatorTest extends TestCase -{ - protected function getServiceLocator(array $factories): ContainerInterface +if (false) { + /** + * @deprecated since PHPUnit 9.6 + */ + class ServiceLocatorTest { - return new class($factories) implements ContainerInterface { - use ServiceLocatorTrait; - }; - } - - public function testHas() - { - $locator = $this->getServiceLocator([ - 'foo' => function () { return 'bar'; }, - 'bar' => function () { return 'baz'; }, - function () { return 'dummy'; }, - ]); - - $this->assertTrue($locator->has('foo')); - $this->assertTrue($locator->has('bar')); - $this->assertFalse($locator->has('dummy')); - } - - public function testGet() - { - $locator = $this->getServiceLocator([ - 'foo' => function () { return 'bar'; }, - 'bar' => function () { return 'baz'; }, - ]); - - $this->assertSame('bar', $locator->get('foo')); - $this->assertSame('baz', $locator->get('bar')); - } - - public function testGetDoesNotMemoize() - { - $i = 0; - $locator = $this->getServiceLocator([ - 'foo' => function () use (&$i) { - ++$i; - - return 'bar'; - }, - ]); - - $this->assertSame('bar', $locator->get('foo')); - $this->assertSame('bar', $locator->get('foo')); - $this->assertSame(2, $i); - } - - public function testThrowsOnUndefinedInternalService() - { - if (!$this->getExpectedException()) { - $this->expectException(\Psr\Container\NotFoundExceptionInterface::class); - $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.'); - } - $locator = $this->getServiceLocator([ - 'foo' => function () use (&$locator) { return $locator->get('bar'); }, - ]); - - $locator->get('foo'); - } - - public function testThrowsOnCircularReference() - { - $this->expectException(\Psr\Container\ContainerExceptionInterface::class); - $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".'); - $locator = $this->getServiceLocator([ - 'foo' => function () use (&$locator) { return $locator->get('bar'); }, - 'bar' => function () use (&$locator) { return $locator->get('baz'); }, - 'baz' => function () use (&$locator) { return $locator->get('bar'); }, - ]); - - $locator->get('foo'); } } diff --git a/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php b/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php new file mode 100644 index 0000000..fdd5b27 --- /dev/null +++ b/vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Test; + +use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\ContainerInterface; +use Psr\Container\NotFoundExceptionInterface; +use Symfony\Contracts\Service\ServiceLocatorTrait; + +abstract class ServiceLocatorTestCase extends TestCase +{ + /** + * @param array $factories + */ + protected function getServiceLocator(array $factories): ContainerInterface + { + return new class($factories) implements ContainerInterface { + use ServiceLocatorTrait; + }; + } + + public function testHas() + { + $locator = $this->getServiceLocator([ + 'foo' => fn () => 'bar', + 'bar' => fn () => 'baz', + fn () => 'dummy', + ]); + + $this->assertTrue($locator->has('foo')); + $this->assertTrue($locator->has('bar')); + $this->assertFalse($locator->has('dummy')); + } + + public function testGet() + { + $locator = $this->getServiceLocator([ + 'foo' => fn () => 'bar', + 'bar' => fn () => 'baz', + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('baz', $locator->get('bar')); + } + + public function testGetDoesNotMemoize() + { + $i = 0; + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$i) { + ++$i; + + return 'bar'; + }, + ]); + + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame('bar', $locator->get('foo')); + $this->assertSame(2, $i); + } + + public function testThrowsOnUndefinedInternalService() + { + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $this->expectException(NotFoundExceptionInterface::class); + $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.'); + + $locator->get('foo'); + } + + public function testThrowsOnCircularReference() + { + $locator = $this->getServiceLocator([ + 'foo' => function () use (&$locator) { return $locator->get('bar'); }, + 'bar' => function () use (&$locator) { return $locator->get('baz'); }, + 'baz' => function () use (&$locator) { return $locator->get('bar'); }, + ]); + + $this->expectException(ContainerExceptionInterface::class); + $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".'); + + $locator->get('foo'); + } +} diff --git a/vendor/symfony/service-contracts/composer.json b/vendor/symfony/service-contracts/composer.json index d3b047f..bc2e99a 100644 --- a/vendor/symfony/service-contracts/composer.json +++ b/vendor/symfony/service-contracts/composer.json @@ -16,22 +16,23 @@ } ], "require": { - "php": ">=8.0.2", - "psr/container": "^2.0" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "ext-psr": "<1.1|>=2" }, - "suggest": { - "symfony/service-implementation": "" - }, "autoload": { - "psr-4": { "Symfony\\Contracts\\Service\\": "" } + "psr-4": { "Symfony\\Contracts\\Service\\": "" }, + "exclude-from-classmap": [ + "/Test/" + ] }, "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.6-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/vendor/symfony/var-exporter/CHANGELOG.md b/vendor/symfony/var-exporter/CHANGELOG.md index 3406c30..fdca002 100644 --- a/vendor/symfony/var-exporter/CHANGELOG.md +++ b/vendor/symfony/var-exporter/CHANGELOG.md @@ -1,6 +1,19 @@ CHANGELOG ========= +6.4 +--- + + * Deprecate per-property lazy-initializers + +6.2 +--- + + * Add support for lazy ghost objects and virtual proxies + * Add `Hydrator::hydrate()` + * Preserve PHP references also when using `Hydrator::hydrate()` or `Instantiator::instantiate()` + * Add support for hydrating from native (array) casts + 5.1.0 ----- diff --git a/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php b/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php index 4cebe44..2acecc4 100644 --- a/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php +++ b/vendor/symfony/var-exporter/Exception/ClassNotFoundException.php @@ -13,8 +13,8 @@ namespace Symfony\Component\VarExporter\Exception; class ClassNotFoundException extends \Exception implements ExceptionInterface { - public function __construct(string $class, \Throwable $previous = null) + public function __construct(string $class, ?\Throwable $previous = null) { - parent::__construct(sprintf('Class "%s" not found.', $class), 0, $previous); + parent::__construct(\sprintf('Class "%s" not found.', $class), 0, $previous); } } diff --git a/vendor/symfony/var-exporter/Exception/LogicException.php b/vendor/symfony/var-exporter/Exception/LogicException.php new file mode 100644 index 0000000..619d055 --- /dev/null +++ b/vendor/symfony/var-exporter/Exception/LogicException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php b/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php index 771ee61..bc2bcaa 100644 --- a/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php +++ b/vendor/symfony/var-exporter/Exception/NotInstantiableTypeException.php @@ -13,8 +13,8 @@ namespace Symfony\Component\VarExporter\Exception; class NotInstantiableTypeException extends \Exception implements ExceptionInterface { - public function __construct(string $type, \Throwable $previous = null) + public function __construct(string $type, ?\Throwable $previous = null) { - parent::__construct(sprintf('Type "%s" is not instantiable.', $type), 0, $previous); + parent::__construct(\sprintf('Type "%s" is not instantiable.', $type), 0, $previous); } } diff --git a/vendor/symfony/var-exporter/Hydrator.php b/vendor/symfony/var-exporter/Hydrator.php new file mode 100644 index 0000000..b718921 --- /dev/null +++ b/vendor/symfony/var-exporter/Hydrator.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Internal\Hydrator as InternalHydrator; + +/** + * Utility class to hydrate the properties of an object. + * + * @author Nicolas Grekas + */ +final class Hydrator +{ + /** + * Sets the properties of an object, including private and protected ones. + * + * For example: + * + * // Sets the public or protected $object->propertyName property + * Hydrator::hydrate($object, ['propertyName' => $propertyValue]); + * + * // Sets a private property defined on its parent Bar class: + * Hydrator::hydrate($object, ["\0Bar\0privateBarProperty" => $propertyValue]); + * + * // Alternative way to set the private $object->privateBarProperty property + * Hydrator::hydrate($object, [], [ + * Bar::class => ['privateBarProperty' => $propertyValue], + * ]); + * + * Instances of ArrayObject, ArrayIterator and SplObjectStorage can be hydrated + * by using the special "\0" property name to define their internal value: + * + * // Hydrates an SplObjectStorage where $info1 is attached to $obj1, etc. + * Hydrator::hydrate($object, ["\0" => [$obj1, $info1, $obj2, $info2...]]); + * + * // Hydrates an ArrayObject populated with $inputArray + * Hydrator::hydrate($object, ["\0" => [$inputArray]]); + * + * @template T of object + * + * @param T $instance The object to hydrate + * @param array $properties The properties to set on the instance + * @param array> $scopedProperties The properties to set on the instance, + * keyed by their declaring class + * + * @return T + */ + public static function hydrate(object $instance, array $properties = [], array $scopedProperties = []): object + { + if ($properties) { + $class = $instance::class; + $propertyScopes = InternalHydrator::$propertyScopes[$class] ??= InternalHydrator::getPropertyScopes($class); + + foreach ($properties as $name => &$value) { + [$scope, $name, $writeScope] = $propertyScopes[$name] ?? [$class, $name, $class]; + $scopedProperties[$writeScope ?? $scope][$name] = &$value; + } + unset($value); + } + + foreach ($scopedProperties as $scope => $properties) { + if ($properties) { + (InternalHydrator::$simpleHydrators[$scope] ??= InternalHydrator::getSimpleHydrator($scope))($properties, $instance); + } + } + + return $instance; + } +} diff --git a/vendor/symfony/var-exporter/Instantiator.php b/vendor/symfony/var-exporter/Instantiator.php index 38fce27..10200c0 100644 --- a/vendor/symfony/var-exporter/Instantiator.php +++ b/vendor/symfony/var-exporter/Instantiator.php @@ -13,7 +13,6 @@ namespace Symfony\Component\VarExporter; use Symfony\Component\VarExporter\Exception\ExceptionInterface; use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; -use Symfony\Component\VarExporter\Internal\Hydrator; use Symfony\Component\VarExporter\Internal\Registry; /** @@ -26,67 +25,35 @@ final class Instantiator /** * Creates an object and sets its properties without calling its constructor nor any other methods. * - * For example: + * @see Hydrator::hydrate() for examples * - * // creates an empty instance of Foo - * Instantiator::instantiate(Foo::class); + * @template T of object * - * // creates a Foo instance and sets one of its properties - * Instantiator::instantiate(Foo::class, ['propertyName' => $propertyValue]); + * @param class-string $class The class of the instance to create + * @param array $properties The properties to set on the instance + * @param array> $scopedProperties The properties to set on the instance, + * keyed by their declaring class * - * // creates a Foo instance and sets a private property defined on its parent Bar class - * Instantiator::instantiate(Foo::class, [], [ - * Bar::class => ['privateBarProperty' => $propertyValue], - * ]); - * - * Instances of ArrayObject, ArrayIterator and SplObjectStorage can be created - * by using the special "\0" property name to define their internal value: - * - * // creates an SplObjectStorage where $info1 is attached to $obj1, etc. - * Instantiator::instantiate(SplObjectStorage::class, ["\0" => [$obj1, $info1, $obj2, $info2...]]); - * - * // creates an ArrayObject populated with $inputArray - * Instantiator::instantiate(ArrayObject::class, ["\0" => [$inputArray]]); - * - * @param string $class The class of the instance to create - * @param array $properties The properties to set on the instance - * @param array $privateProperties The private properties to set on the instance, - * keyed by their declaring class + * @return T * * @throws ExceptionInterface When the instance cannot be created */ - public static function instantiate(string $class, array $properties = [], array $privateProperties = []): object + public static function instantiate(string $class, array $properties = [], array $scopedProperties = []): object { - $reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class); + $reflector = Registry::$reflectors[$class] ??= Registry::getClassReflector($class); if (Registry::$cloneable[$class]) { - $wrappedInstance = [clone Registry::$prototypes[$class]]; + $instance = clone Registry::$prototypes[$class]; } elseif (Registry::$instantiableWithoutConstructor[$class]) { - $wrappedInstance = [$reflector->newInstanceWithoutConstructor()]; + $instance = $reflector->newInstanceWithoutConstructor(); } elseif (null === Registry::$prototypes[$class]) { throw new NotInstantiableTypeException($class); } elseif ($reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize')) { - $wrappedInstance = [unserialize('C:'.\strlen($class).':"'.$class.'":0:{}')]; + $instance = unserialize('C:'.\strlen($class).':"'.$class.'":0:{}'); } else { - $wrappedInstance = [unserialize('O:'.\strlen($class).':"'.$class.'":0:{}')]; + $instance = unserialize('O:'.\strlen($class).':"'.$class.'":0:{}'); } - if ($properties) { - $privateProperties[$class] = isset($privateProperties[$class]) ? $properties + $privateProperties[$class] : $properties; - } - - foreach ($privateProperties as $class => $properties) { - if (!$properties) { - continue; - } - foreach ($properties as $name => $value) { - // because they're also used for "unserialization", hydrators - // deal with array of instances, so we need to wrap values - $properties[$name] = [$value]; - } - (Hydrator::$hydrators[$class] ?? Hydrator::getHydrator($class))($properties, $wrappedInstance); - } - - return $wrappedInstance[0]; + return $properties || $scopedProperties ? Hydrator::hydrate($instance, $properties, $scopedProperties) : $instance; } } diff --git a/vendor/symfony/var-exporter/Internal/Exporter.php b/vendor/symfony/var-exporter/Internal/Exporter.php index 6ee3ee7..13141a4 100644 --- a/vendor/symfony/var-exporter/Internal/Exporter.php +++ b/vendor/symfony/var-exporter/Internal/Exporter.php @@ -31,9 +31,11 @@ class Exporter * @param int &$objectsCount * @param bool &$valuesAreStatic * + * @return array + * * @throws NotInstantiableTypeException When a value cannot be serialized */ - public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic): array + public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic) { $refs = $values; foreach ($values as $k => $value) { @@ -71,26 +73,26 @@ class Exporter goto handle_value; } - $class = \get_class($value); - $reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class); - - if ($reflector->hasMethod('__serialize')) { - if (!$reflector->getMethod('__serialize')->isPublic()) { - throw new \Error(sprintf('Call to %s method "%s::__serialize()".', $reflector->getMethod('__serialize')->isProtected() ? 'protected' : 'private', $class)); - } - - if (!\is_array($properties = $value->__serialize())) { - throw new \TypeError($class.'::__serialize() must return an array'); - } - - goto prepare_value; - } - + $class = $value::class; + $reflector = Registry::$reflectors[$class] ??= Registry::getClassReflector($class); $properties = []; $sleep = null; $proto = Registry::$prototypes[$class]; - if (($value instanceof \ArrayIterator || $value instanceof \ArrayObject) && null !== $proto) { + if ($reflector->hasMethod('__serialize')) { + if (!$reflector->getMethod('__serialize')->isPublic()) { + throw new \Error(\sprintf('Call to %s method "%s::__serialize()".', $reflector->getMethod('__serialize')->isProtected() ? 'protected' : 'private', $class)); + } + + if (!\is_array($arrayValue = $value->__serialize())) { + throw new \TypeError($class.'::__serialize() must return an array'); + } + + if ($reflector->hasMethod('__unserialize')) { + $properties = $arrayValue; + goto prepare_value; + } + } elseif (($value instanceof \ArrayIterator || $value instanceof \ArrayObject) && null !== $proto) { // ArrayIterator and ArrayObject need special care because their "flags" // option changes the behavior of the (array) casting operator. [$arrayValue, $properties] = self::getArrayObjectProperties($value, $proto); @@ -106,10 +108,7 @@ class Exporter } $properties = ['SplObjectStorage' => ["\0" => $properties]]; $arrayValue = (array) $value; - } elseif ($value instanceof \Serializable - || $value instanceof \__PHP_Incomplete_Class - || \PHP_VERSION_ID < 80200 && $value instanceof \DatePeriod - ) { + } elseif ($value instanceof \Serializable || $value instanceof \__PHP_Incomplete_Class || \PHP_VERSION_ID < 80200 && $value instanceof \DatePeriod) { ++$objectsCount; $objectsPool[$value] = [$id = \count($objectsPool), serialize($value), [], 0]; $value = new Reference($id); @@ -133,36 +132,40 @@ class Exporter $i = 0; $n = (string) $name; if ('' === $n || "\0" !== $n[0]) { - $c = \PHP_VERSION_ID >= 80100 && $reflector->hasProperty($n) && ($p = $reflector->getProperty($n))->isReadOnly() ? $p->class : 'stdClass'; + $parent = $reflector; + do { + $p = $parent->hasProperty($n) ? $parent->getProperty($n) : null; + } while (!$p && $parent = $parent->getParentClass()); + + $c = $p && (!$p->isPublic() || (\PHP_VERSION_ID >= 80400 ? $p->isProtectedSet() || $p->isPrivateSet() : $p->isReadOnly())) ? $p->class : 'stdClass'; } elseif ('*' === $n[1]) { $n = substr($n, 3); $c = $reflector->getProperty($n)->class; - if ('Error' === $c) { - $c = 'TypeError'; - } elseif ('Exception' === $c) { - $c = 'ErrorException'; - } } else { $i = strpos($n, "\0", 2); $c = substr($n, 1, $i - 1); $n = substr($n, 1 + $i); } if (null !== $sleep) { - if (!isset($sleep[$n]) || ($i && $c !== $class)) { + if (!isset($sleep[$name]) && (!isset($sleep[$n]) || ($i && $c !== $class))) { unset($arrayValue[$name]); continue; } - $sleep[$n] = false; + unset($sleep[$name], $sleep[$n]); } - if (!\array_key_exists($name, $proto) || $proto[$name] !== $v || "\x00Error\x00trace" === $name || "\x00Exception\x00trace" === $name) { + if ("\x00Error\x00trace" === $name || "\x00Exception\x00trace" === $name) { $properties[$c][$n] = $v; + } elseif (!\array_key_exists($name, $proto) || $proto[$name] !== $v) { + $properties[match ($c) { + 'Error' => 'TypeError', + 'Exception' => 'ErrorException', + default => $c, + }][$n] = $v; } } if ($sleep) { foreach ($sleep as $n => $v) { - if (false !== $v) { - trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $n), \E_USER_NOTICE); - } + trigger_error(\sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $n), \E_USER_NOTICE); } } if (method_exists($class, '__unserialize')) { @@ -189,7 +192,7 @@ class Exporter return $values; } - public static function export($value, string $indent = '') + public static function export($value, $indent = '') { switch (true) { case \is_int($value) || \is_float($value): return var_export($value, true); @@ -215,10 +218,10 @@ class Exporter $subIndent = $indent.' '; if (\is_string($value)) { - $code = sprintf("'%s'", addcslashes($value, "'\\")); + $code = \sprintf("'%s'", addcslashes($value, "'\\")); $code = preg_replace_callback("/((?:[\\0\\r\\n]|\u{202A}|\u{202B}|\u{202D}|\u{202E}|\u{2066}|\u{2067}|\u{2068}|\u{202C}|\u{2069})++)(.)/", function ($m) use ($subIndent) { - $m[1] = sprintf('\'."%s".\'', str_replace( + $m[1] = \sprintf('\'."%s".\'', str_replace( ["\0", "\r", "\n", "\u{202A}", "\u{202B}", "\u{202D}", "\u{202E}", "\u{2066}", "\u{2067}", "\u{2068}", "\u{202C}", "\u{2069}", '\n\\'], ['\0', '\r', '\n', '\u{202A}', '\u{202B}', '\u{202D}', '\u{202E}', '\u{2066}', '\u{2067}', '\u{2068}', '\u{202C}', '\u{2069}', '\n"'."\n".$subIndent.'."\\'], $m[1] @@ -228,7 +231,7 @@ class Exporter return substr($m[1], 0, -2); } - if ('n".\'' === substr($m[1], -4)) { + if (str_ends_with($m[1], 'n".\'')) { return substr_replace($m[1], "\n".$subIndent.".'".$m[2], -2); } @@ -276,7 +279,7 @@ class Exporter return self::exportHydrator($value, $indent, $subIndent); } - throw new \UnexpectedValueException(sprintf('Cannot export value of type "%s".', get_debug_type($value))); + throw new \UnexpectedValueException(\sprintf('Cannot export value of type "%s".', get_debug_type($value))); } private static function exportRegistry(Registry $value, string $indent, string $subIndent): string @@ -366,7 +369,7 @@ class Exporter self::export($value->wakeups, $subIndent), ]; - return '\\'.\get_class($value)."::hydrate(\n".$subIndent.implode(",\n".$subIndent, $code)."\n".$indent.')'; + return '\\'.$value::class."::hydrate(\n".$subIndent.implode(",\n".$subIndent, $code)."\n".$indent.')'; } /** @@ -376,7 +379,7 @@ class Exporter private static function getArrayObjectProperties($value, $proto): array { $reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject'; - $reflector = Registry::$reflectors[$reflector] ?? Registry::getClassReflector($reflector); + $reflector = Registry::$reflectors[$reflector] ??= Registry::getClassReflector($reflector); $properties = [ $arrayValue = (array) $value, diff --git a/vendor/symfony/var-exporter/Internal/Hydrator.php b/vendor/symfony/var-exporter/Internal/Hydrator.php index 5ed6bdc..ebbceed 100644 --- a/vendor/symfony/var-exporter/Internal/Hydrator.php +++ b/vendor/symfony/var-exporter/Internal/Hydrator.php @@ -20,7 +20,12 @@ use Symfony\Component\VarExporter\Exception\ClassNotFoundException; */ class Hydrator { - public static $hydrators = []; + public const PROPERTY_HAS_HOOKS = 1; + public const PROPERTY_NOT_BY_REF = 2; + + public static array $hydrators = []; + public static array $simpleHydrators = []; + public static array $propertyScopes = []; public $registry; public $values; @@ -40,7 +45,7 @@ class Hydrator public static function hydrate($objects, $values, $properties, $value, $wakeups) { foreach ($properties as $class => $vars) { - (self::$hydrators[$class] ?? self::getHydrator($class))($vars, $objects); + (self::$hydrators[$class] ??= self::getHydrator($class))($vars, $objects); } foreach ($wakeups as $k => $v) { if (\is_array($v)) { @@ -55,31 +60,33 @@ class Hydrator public static function getHydrator($class) { + $baseHydrator = self::$hydrators['stdClass'] ??= static function ($properties, $objects) { + foreach ($properties as $name => $values) { + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + switch ($class) { case 'stdClass': - return self::$hydrators[$class] = static function ($properties, $objects) { - foreach ($properties as $name => $values) { - foreach ($values as $i => $v) { - $objects[$i]->$name = $v; - } - } - }; + return $baseHydrator; case 'ErrorException': - return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \ErrorException { + return $baseHydrator->bindTo(null, new class extends \ErrorException { }); case 'TypeError': - return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \Error { + return $baseHydrator->bindTo(null, new class extends \Error { }); case 'SplObjectStorage': - return self::$hydrators[$class] = static function ($properties, $objects) { + return static function ($properties, $objects) { foreach ($properties as $name => $values) { if ("\0" === $name) { foreach ($values as $i => $v) { for ($j = 0; $j < \count($v); ++$j) { - $objects[$i]->attach($v[$j], $v[++$j]); + $objects[$i][$v[$j]] = $v[++$j]; } } continue; @@ -99,9 +106,9 @@ class Hydrator switch ($class) { case 'ArrayIterator': case 'ArrayObject': - $constructor = \Closure::fromCallable([$classReflector->getConstructor(), 'invokeArgs']); + $constructor = $classReflector->getConstructor()->invokeArgs(...); - return self::$hydrators[$class] = static function ($properties, $objects) use ($constructor) { + return static function ($properties, $objects) use ($constructor) { foreach ($properties as $name => $values) { if ("\0" !== $name) { foreach ($values as $i => $v) { @@ -116,26 +123,25 @@ class Hydrator } if (!$classReflector->isInternal()) { - return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, $class); + return $baseHydrator->bindTo(null, $class); } if ($classReflector->name !== $class) { - return self::$hydrators[$classReflector->name] ?? self::getHydrator($classReflector->name); + return self::$hydrators[$classReflector->name] ??= self::getHydrator($classReflector->name); } $propertySetters = []; foreach ($classReflector->getProperties() as $propertyReflector) { if (!$propertyReflector->isStatic()) { - $propertyReflector->setAccessible(true); - $propertySetters[$propertyReflector->name] = \Closure::fromCallable([$propertyReflector, 'setValue']); + $propertySetters[$propertyReflector->name] = $propertyReflector->setValue(...); } } if (!$propertySetters) { - return self::$hydrators[$class] = self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'); + return $baseHydrator; } - return self::$hydrators[$class] = static function ($properties, $objects) use ($propertySetters) { + return static function ($properties, $objects) use ($propertySetters) { foreach ($properties as $name => $values) { if ($setValue = $propertySetters[$name] ?? null) { foreach ($values as $i => $v) { @@ -149,4 +155,175 @@ class Hydrator } }; } + + public static function getSimpleHydrator($class) + { + $baseHydrator = self::$simpleHydrators['stdClass'] ??= (function ($properties, $object) { + $notByRef = (array) $this; + + foreach ($properties as $name => &$value) { + if (!$noRef = $notByRef[$name] ?? false) { + $object->$name = $value; + $object->$name = &$value; + } elseif (true !== $noRef) { + $noRef($object, $value); + } else { + $object->$name = $value; + } + } + })->bindTo(new \stdClass()); + + switch ($class) { + case 'stdClass': + return $baseHydrator; + + case 'ErrorException': + return $baseHydrator->bindTo(new \stdClass(), new class extends \ErrorException { + }); + + case 'TypeError': + return $baseHydrator->bindTo(new \stdClass(), new class extends \Error { + }); + + case 'SplObjectStorage': + return static function ($properties, $object) { + foreach ($properties as $name => &$value) { + if ("\0" !== $name) { + $object->$name = $value; + $object->$name = &$value; + continue; + } + for ($i = 0; $i < \count($value); ++$i) { + $object[$value[$i]] = $value[++$i]; + } + } + }; + } + + if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { + throw new ClassNotFoundException($class); + } + $classReflector = new \ReflectionClass($class); + + switch ($class) { + case 'ArrayIterator': + case 'ArrayObject': + $constructor = $classReflector->getConstructor()->invokeArgs(...); + + return static function ($properties, $object) use ($constructor) { + foreach ($properties as $name => &$value) { + if ("\0" === $name) { + $constructor($object, $value); + } else { + $object->$name = $value; + $object->$name = &$value; + } + } + }; + } + + if (!$classReflector->isInternal()) { + $notByRef = new \stdClass(); + foreach ($classReflector->getProperties() as $propertyReflector) { + if ($propertyReflector->isStatic()) { + continue; + } + if (\PHP_VERSION_ID >= 80400 && !$propertyReflector->isAbstract() && $propertyReflector->getHooks()) { + $notByRef->{$propertyReflector->name} = $propertyReflector->setRawValue(...); + } elseif ($propertyReflector->isReadOnly()) { + $notByRef->{$propertyReflector->name} = true; + } + } + + return $baseHydrator->bindTo($notByRef, $class); + } + + if ($classReflector->name !== $class) { + return self::$simpleHydrators[$classReflector->name] ??= self::getSimpleHydrator($classReflector->name); + } + + $propertySetters = []; + foreach ($classReflector->getProperties() as $propertyReflector) { + if (!$propertyReflector->isStatic()) { + $propertySetters[$propertyReflector->name] = $propertyReflector->setValue(...); + } + } + + if (!$propertySetters) { + return $baseHydrator; + } + + return static function ($properties, $object) use ($propertySetters) { + foreach ($properties as $name => &$value) { + if ($setValue = $propertySetters[$name] ?? null) { + $setValue($object, $value); + } else { + $object->$name = $value; + $object->$name = &$value; + } + } + }; + } + + /** + * @return array + */ + public static function getPropertyScopes($class) + { + $propertyScopes = []; + $r = new \ReflectionClass($class); + + foreach ($r->getProperties() as $property) { + $flags = $property->getModifiers(); + + if (\ReflectionProperty::IS_STATIC & $flags) { + continue; + } + $name = $property->name; + $access = ($flags << 2) | ($flags & \ReflectionProperty::IS_READONLY ? self::PROPERTY_NOT_BY_REF : 0); + + if (\PHP_VERSION_ID >= 80400 && !$property->isAbstract() && $h = $property->getHooks()) { + $access |= self::PROPERTY_HAS_HOOKS | (isset($h['get']) && !$h['get']->returnsReference() ? self::PROPERTY_NOT_BY_REF : 0); + } + + if (\ReflectionProperty::IS_PRIVATE & $flags) { + $propertyScopes["\0$class\0$name"] = $propertyScopes[$name] = [$class, $name, null, $access, $property]; + + continue; + } + + $propertyScopes[$name] = [$class, $name, null, $access, $property]; + + if ($flags & (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PRIVATE_SET : \ReflectionProperty::IS_READONLY)) { + $propertyScopes[$name][2] = $property->class; + } + + if (\ReflectionProperty::IS_PROTECTED & $flags) { + $propertyScopes["\0*\0$name"] = $propertyScopes[$name]; + } + } + + while ($r = $r->getParentClass()) { + $class = $r->name; + + foreach ($r->getProperties(\ReflectionProperty::IS_PRIVATE) as $property) { + $flags = $property->getModifiers(); + + if (\ReflectionProperty::IS_STATIC & $flags) { + continue; + } + $name = $property->name; + $access = ($flags << 2) | ($flags & \ReflectionProperty::IS_READONLY ? self::PROPERTY_NOT_BY_REF : 0); + + if (\PHP_VERSION_ID >= 80400 && $h = $property->getHooks()) { + $access |= self::PROPERTY_HAS_HOOKS | (isset($h['get']) && !$h['get']->returnsReference() ? self::PROPERTY_NOT_BY_REF : 0); + } + + $propertyScopes["\0$class\0$name"] = [$class, $name, null, $access, $property]; + $propertyScopes[$name] ??= $propertyScopes["\0$class\0$name"]; + } + } + + return $propertyScopes; + } } diff --git a/vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php b/vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php new file mode 100644 index 0000000..d096be8 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +/** + * Stores the state of lazy objects and caches related reflection information. + * + * As a micro-optimization, this class uses no type declarations. + * + * @internal + */ +class LazyObjectRegistry +{ + /** + * @var array + */ + public static array $classReflectors = []; + + /** + * @var array> + */ + public static array $defaultProperties = []; + + /** + * @var array> + */ + public static array $classResetters = []; + + /** + * @var array + */ + public static array $classAccessors = []; + + /** + * @var array + */ + public static array $parentMethods = []; + + public static ?\Closure $noInitializerState = null; + + public static function getClassResetters($class) + { + $classProperties = []; + $hookedProperties = []; + + if ((self::$classReflectors[$class] ??= new \ReflectionClass($class))->isInternal()) { + $propertyScopes = []; + } else { + $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); + } + + foreach ($propertyScopes as $key => [$scope, $name, $writeScope, $access]) { + $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; + + if ($k !== $key || "\0$class\0lazyObjectState" === $k) { + continue; + } + + if ($access & Hydrator::PROPERTY_HAS_HOOKS) { + $hookedProperties[$k] = true; + } else { + $classProperties[$writeScope ?? $scope][$name] = $key; + } + } + + $resetters = []; + foreach ($classProperties as $scope => $properties) { + $resetters[] = \Closure::bind(static function ($instance, $skippedProperties, $onlyProperties = null) use ($properties) { + foreach ($properties as $name => $key) { + if (!\array_key_exists($key, $skippedProperties) && (null === $onlyProperties || \array_key_exists($key, $onlyProperties))) { + unset($instance->$name); + } + } + }, null, $scope); + } + + $resetters[] = static function ($instance, $skippedProperties, $onlyProperties = null) use ($hookedProperties) { + foreach ((array) $instance as $name => $value) { + if ("\0" !== ($name[0] ?? '') + && !\array_key_exists($name, $skippedProperties) + && (null === $onlyProperties || \array_key_exists($name, $onlyProperties)) + && !isset($hookedProperties[$name]) + ) { + unset($instance->$name); + } + } + }; + + return $resetters; + } + + public static function getClassAccessors($class) + { + return \Closure::bind(static fn () => [ + 'get' => static function &($instance, $name, $notByRef) { + if (!$notByRef) { + return $instance->$name; + } + $value = $instance->$name; + + return $value; + }, + 'set' => static function ($instance, $name, $value) { + $instance->$name = $value; + }, + 'isset' => static fn ($instance, $name) => isset($instance->$name), + 'unset' => static function ($instance, $name) { + unset($instance->$name); + }, + ], null, \Closure::class === $class ? null : $class)(); + } + + public static function getParentMethods($class) + { + $parent = get_parent_class($class); + $methods = []; + + foreach (['set', 'isset', 'unset', 'clone', 'serialize', 'unserialize', 'sleep', 'wakeup', 'destruct', 'get'] as $method) { + if (!$parent || !method_exists($parent, '__'.$method)) { + $methods[$method] = false; + } else { + $m = new \ReflectionMethod($parent, '__'.$method); + $methods[$method] = !$m->isAbstract() && !$m->isPrivate(); + } + } + + $methods['get'] = $methods['get'] ? ($m->returnsReference() ? 2 : 1) : 0; + + return $methods; + } + + public static function getScopeForRead($propertyScopes, $class, $property) + { + if (!isset($propertyScopes[$k = "\0$class\0$property"]) && !isset($propertyScopes[$k = "\0*\0$property"])) { + return null; + } + $frame = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]; + + if (\ReflectionProperty::class === $scope = $frame['class'] ?? \Closure::class) { + $scope = $frame['object']->class; + } + if ('*' === $k[1] && ($class === $scope || (is_subclass_of($class, $scope) && !isset($propertyScopes["\0$scope\0$property"])))) { + return null; + } + + return $scope; + } + + public static function getScopeForWrite($propertyScopes, $class, $property, $flags) + { + if (!($flags & (\ReflectionProperty::IS_PRIVATE | \ReflectionProperty::IS_PROTECTED | \ReflectionProperty::IS_READONLY | (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PRIVATE_SET | \ReflectionProperty::IS_PROTECTED_SET : 0)))) { + return null; + } + $frame = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2]; + + if (\ReflectionProperty::class === $scope = $frame['class'] ?? \Closure::class) { + $scope = $frame['object']->class; + } + if ($flags & (\ReflectionProperty::IS_PRIVATE | (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PRIVATE_SET : \ReflectionProperty::IS_READONLY))) { + return $scope; + } + if ($flags & (\ReflectionProperty::IS_PROTECTED | (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PROTECTED_SET : 0)) && ($class === $scope || (is_subclass_of($class, $scope) && !isset($propertyScopes["\0$scope\0$property"])))) { + return null; + } + + return $scope; + } +} diff --git a/vendor/symfony/var-exporter/Internal/LazyObjectState.php b/vendor/symfony/var-exporter/Internal/LazyObjectState.php new file mode 100644 index 0000000..619555e --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/LazyObjectState.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Hydrator as PublicHydrator; + +/** + * Keeps the state of lazy objects. + * + * As a micro-optimization, this class uses no type declarations. + * + * @internal + */ +class LazyObjectState +{ + public const STATUS_UNINITIALIZED_FULL = 1; + public const STATUS_UNINITIALIZED_PARTIAL = 2; + public const STATUS_INITIALIZED_FULL = 3; + public const STATUS_INITIALIZED_PARTIAL = 4; + + /** + * @var array + */ + public readonly array $skippedProperties; + + /** + * @var self::STATUS_* + */ + public int $status = 0; + + public object $realInstance; + + public function __construct(public readonly \Closure|array $initializer, $skippedProperties = []) + { + $this->skippedProperties = $skippedProperties; + $this->status = \is_array($initializer) ? self::STATUS_UNINITIALIZED_PARTIAL : self::STATUS_UNINITIALIZED_FULL; + } + + public function initialize($instance, $propertyName, $writeScope) + { + if (self::STATUS_INITIALIZED_FULL === $this->status) { + return self::STATUS_INITIALIZED_FULL; + } + + if (\is_array($this->initializer)) { + $class = $instance::class; + $writeScope ??= $class; + $propertyScopes = Hydrator::$propertyScopes[$class]; + $propertyScopes[$k = "\0$writeScope\0$propertyName"] ?? $propertyScopes[$k = "\0*\0$propertyName"] ?? $k = $propertyName; + + if ($initializer = $this->initializer[$k] ?? null) { + $value = $initializer(...[$instance, $propertyName, $writeScope, LazyObjectRegistry::$defaultProperties[$class][$k] ?? null]); + $accessor = LazyObjectRegistry::$classAccessors[$writeScope] ??= LazyObjectRegistry::getClassAccessors($writeScope); + $accessor['set']($instance, $propertyName, $value); + + return $this->status = self::STATUS_INITIALIZED_PARTIAL; + } + + if ($initializer = $this->initializer["\0"] ?? null) { + if (!\is_array($values = $initializer($instance, LazyObjectRegistry::$defaultProperties[$class]))) { + throw new \TypeError(\sprintf('The lazy-initializer defined for instance of "%s" must return an array, got "%s".', $class, get_debug_type($values))); + } + $properties = (array) $instance; + foreach ($values as $key => $value) { + if (!\array_key_exists($key, $properties) && [$scope, $name, $writeScope] = $propertyScopes[$key] ?? null) { + $scope = $writeScope ?? $scope; + $accessor = LazyObjectRegistry::$classAccessors[$scope] ??= LazyObjectRegistry::getClassAccessors($scope); + $accessor['set']($instance, $name, $value); + + if ($k === $key) { + $this->status = self::STATUS_INITIALIZED_PARTIAL; + } + } + } + } + + return $this->status; + } + + if (self::STATUS_INITIALIZED_PARTIAL === $this->status) { + return self::STATUS_INITIALIZED_PARTIAL; + } + + $this->status = self::STATUS_INITIALIZED_PARTIAL; + + try { + if ($defaultProperties = array_diff_key(LazyObjectRegistry::$defaultProperties[$instance::class], $this->skippedProperties)) { + PublicHydrator::hydrate($instance, $defaultProperties); + } + + ($this->initializer)($instance); + } catch (\Throwable $e) { + $this->status = self::STATUS_UNINITIALIZED_FULL; + $this->reset($instance); + + throw $e; + } + + return $this->status = self::STATUS_INITIALIZED_FULL; + } + + public function reset($instance): void + { + $class = $instance::class; + $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); + $skippedProperties = $this->skippedProperties; + $properties = (array) $instance; + $onlyProperties = \is_array($this->initializer) ? $this->initializer : null; + + foreach ($propertyScopes as $key => [$scope, $name, , $access]) { + $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; + + if ($k === $key && ($access & Hydrator::PROPERTY_HAS_HOOKS || ($access >> 2) & \ReflectionProperty::IS_READONLY || !\array_key_exists($k, $properties))) { + $skippedProperties[$k] = true; + } + } + + foreach (LazyObjectRegistry::$classResetters[$class] as $reset) { + $reset($instance, $skippedProperties, $onlyProperties); + } + + $this->status = self::STATUS_INITIALIZED_FULL === $this->status ? self::STATUS_UNINITIALIZED_FULL : self::STATUS_UNINITIALIZED_PARTIAL; + } +} diff --git a/vendor/symfony/var-exporter/Internal/LazyObjectTrait.php b/vendor/symfony/var-exporter/Internal/LazyObjectTrait.php new file mode 100644 index 0000000..4a6f232 --- /dev/null +++ b/vendor/symfony/var-exporter/Internal/LazyObjectTrait.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\Serializer\Attribute\Ignore; + +if (\PHP_VERSION_ID >= 80300) { + /** + * @internal + */ + trait LazyObjectTrait + { + #[Ignore] + private readonly LazyObjectState $lazyObjectState; + } +} else { + /** + * @internal + */ + trait LazyObjectTrait + { + #[Ignore] + private LazyObjectState $lazyObjectState; + } +} diff --git a/vendor/symfony/var-exporter/Internal/Reference.php b/vendor/symfony/var-exporter/Internal/Reference.php index e371c07..2c7bd7b 100644 --- a/vendor/symfony/var-exporter/Internal/Reference.php +++ b/vendor/symfony/var-exporter/Internal/Reference.php @@ -18,13 +18,11 @@ namespace Symfony\Component\VarExporter\Internal; */ class Reference { - public $id; - public $value; - public $count = 0; + public int $count = 0; - public function __construct(int $id, $value = null) - { - $this->id = $id; - $this->value = $value; + public function __construct( + public readonly int $id, + public readonly mixed $value = null, + ) { } } diff --git a/vendor/symfony/var-exporter/Internal/Registry.php b/vendor/symfony/var-exporter/Internal/Registry.php index a9fb061..db05bbb 100644 --- a/vendor/symfony/var-exporter/Internal/Registry.php +++ b/vendor/symfony/var-exporter/Internal/Registry.php @@ -21,11 +21,11 @@ use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; */ class Registry { - public static $reflectors = []; - public static $prototypes = []; - public static $factories = []; - public static $cloneable = []; - public static $instantiableWithoutConstructor = []; + public static array $reflectors = []; + public static array $prototypes = []; + public static array $factories = []; + public static array $cloneable = []; + public static array $instantiableWithoutConstructor = []; public $classes = []; @@ -58,9 +58,9 @@ class Registry public static function f($class) { - $reflector = self::$reflectors[$class] ?? self::getClassReflector($class, true, false); + $reflector = self::$reflectors[$class] ??= self::getClassReflector($class, true, false); - return self::$factories[$class] = \Closure::fromCallable([$reflector, 'newInstanceWithoutConstructor']); + return self::$factories[$class] = [$reflector, 'newInstanceWithoutConstructor'](...); } public static function getClassReflector($class, $instantiableWithoutConstructor = false, $cloneable = null) @@ -75,17 +75,17 @@ class Registry } elseif (!$isClass || $reflector->isAbstract()) { throw new NotInstantiableTypeException($class); } elseif ($reflector->name !== $class) { - $reflector = self::$reflectors[$name = $reflector->name] ?? self::getClassReflector($name, false, $cloneable); + $reflector = self::$reflectors[$name = $reflector->name] ??= self::getClassReflector($name, false, $cloneable); self::$cloneable[$class] = self::$cloneable[$name]; self::$instantiableWithoutConstructor[$class] = self::$instantiableWithoutConstructor[$name]; self::$prototypes[$class] = self::$prototypes[$name]; - return self::$reflectors[$class] = $reflector; + return $reflector; } else { try { $proto = $reflector->newInstanceWithoutConstructor(); $instantiableWithoutConstructor = true; - } catch (\ReflectionException $e) { + } catch (\ReflectionException) { $proto = $reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize') ? 'C:' : 'O:'; if ('C:' === $proto && !$reflector->getMethod('unserialize')->isInternal()) { $proto = null; @@ -132,15 +132,13 @@ class Registry new \ReflectionProperty(\Error::class, 'trace'), new \ReflectionProperty(\Exception::class, 'trace'), ]; - $setTrace[0]->setAccessible(true); - $setTrace[1]->setAccessible(true); - $setTrace[0] = \Closure::fromCallable([$setTrace[0], 'setValue']); - $setTrace[1] = \Closure::fromCallable([$setTrace[1], 'setValue']); + $setTrace[0] = $setTrace[0]->setValue(...); + $setTrace[1] = $setTrace[1]->setValue(...); } $setTrace[$proto instanceof \Exception]($proto, []); } - return self::$reflectors[$class] = $reflector; + return $reflector; } } diff --git a/vendor/symfony/var-exporter/LICENSE b/vendor/symfony/var-exporter/LICENSE index 99757d5..7536cae 100644 --- a/vendor/symfony/var-exporter/LICENSE +++ b/vendor/symfony/var-exporter/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018-2023 Fabien Potencier +Copyright (c) 2018-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/symfony/var-exporter/LazyGhostTrait.php b/vendor/symfony/var-exporter/LazyGhostTrait.php new file mode 100644 index 0000000..79d8a0f --- /dev/null +++ b/vendor/symfony/var-exporter/LazyGhostTrait.php @@ -0,0 +1,409 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\Serializer\Attribute\Ignore; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry; +use Symfony\Component\VarExporter\Internal\LazyObjectState; +use Symfony\Component\VarExporter\Internal\LazyObjectTrait; + +trait LazyGhostTrait +{ + use LazyObjectTrait; + + /** + * Creates a lazy-loading ghost instance. + * + * Skipped properties should be indexed by their array-cast identifier, see + * https://php.net/manual/language.types.array#language.types.array.casting + * + * @param (\Closure(static):void $initializer The closure should initialize the object it receives as argument + * @param array|null $skippedProperties An array indexed by the properties to skip, a.k.a. the ones + * that the initializer doesn't initialize, if any + * @param static|null $instance + */ + public static function createLazyGhost(\Closure|array $initializer, ?array $skippedProperties = null, ?object $instance = null): static + { + if (\is_array($initializer)) { + trigger_deprecation('symfony/var-exporter', '6.4', 'Per-property lazy-initializers are deprecated and won\'t be supported anymore in 7.0, use an object initializer instead.'); + } + + $onlyProperties = null === $skippedProperties && \is_array($initializer) ? $initializer : null; + + if (self::class !== $class = $instance ? $instance::class : static::class) { + $skippedProperties["\0".self::class."\0lazyObjectState"] = true; + } elseif (\defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) { + Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES; + } + + $instance ??= (Registry::$classReflectors[$class] ??= new \ReflectionClass($class))->newInstanceWithoutConstructor(); + Registry::$defaultProperties[$class] ??= (array) $instance; + $instance->lazyObjectState = new LazyObjectState($initializer, $skippedProperties ??= []); + + foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) { + $reset($instance, $skippedProperties, $onlyProperties); + } + + return $instance; + } + + /** + * Returns whether the object is initialized. + * + * @param $partial Whether partially initialized objects should be considered as initialized + */ + #[Ignore] + public function isLazyObjectInitialized(bool $partial = false): bool + { + if (!$state = $this->lazyObjectState ?? null) { + return true; + } + + if (!\is_array($state->initializer)) { + return LazyObjectState::STATUS_INITIALIZED_FULL === $state->status; + } + + $class = $this::class; + $properties = (array) $this; + + if ($partial) { + return (bool) array_intersect_key($state->initializer, $properties); + } + + $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); + foreach ($state->initializer as $key => $initializer) { + if (!\array_key_exists($key, $properties) && isset($propertyScopes[$key])) { + return false; + } + } + + return true; + } + + /** + * Forces initialization of a lazy object and returns it. + */ + public function initializeLazyObject(): static + { + if (!$state = $this->lazyObjectState ?? null) { + return $this; + } + + if (!\is_array($state->initializer)) { + if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { + $state->initialize($this, '', null); + } + + return $this; + } + + $values = isset($state->initializer["\0"]) ? null : []; + + $class = $this::class; + $properties = (array) $this; + $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class); + foreach ($state->initializer as $key => $initializer) { + if (\array_key_exists($key, $properties) || ![$scope, $name, $writeScope] = $propertyScopes[$key] ?? null) { + continue; + } + $scope = $writeScope ?? $scope; + + if (null === $values) { + if (!\is_array($values = ($state->initializer["\0"])($this, Registry::$defaultProperties[$class]))) { + throw new \TypeError(\sprintf('The lazy-initializer defined for instance of "%s" must return an array, got "%s".', $class, get_debug_type($values))); + } + + if (\array_key_exists($key, $properties = (array) $this)) { + continue; + } + } + + if (\array_key_exists($key, $values)) { + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + $accessor['set']($this, $name, $properties[$key] = $values[$key]); + } else { + $state->initialize($this, $name, $scope); + $properties = (array) $this; + } + } + + return $this; + } + + /** + * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object + */ + public function resetLazyObject(): bool + { + if (!$state = $this->lazyObjectState ?? null) { + return false; + } + + if (LazyObjectState::STATUS_UNINITIALIZED_FULL !== $state->status) { + $state->reset($this); + } + + return true; + } + + public function &__get($name): mixed + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + $notByRef = 0; + + if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScopeForRead($propertyScopes, $class, $name); + $state = $this->lazyObjectState ?? null; + + if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"]))) { + $notByRef = $access & Hydrator::PROPERTY_NOT_BY_REF; + + if (LazyObjectState::STATUS_INITIALIZED_FULL === $state->status) { + // Work around php/php-src#12695 + $property = null === $scope ? $name : "\0$scope\0$name"; + $property = $propertyScopes[$property][4] + ?? Hydrator::$propertyScopes[$this::class][$property][4] = new \ReflectionProperty($scope ?? $class, $name); + } else { + $property = null; + } + if (\PHP_VERSION_ID >= 80400 && !$notByRef && ($access >> 2) & \ReflectionProperty::IS_PRIVATE_SET) { + $scope ??= $writeScope; + } + + if ($property?->isInitialized($this) ?? LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $writeScope ?? $scope)) { + goto get_in_scope; + } + } + } + + if ($parent = (Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['get']) { + if (2 === $parent) { + return parent::__get($name); + } + $value = parent::__get($name); + + return $value; + } + + if (null === $class) { + $frame = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]; + trigger_error(\sprintf('Undefined property: %s::$%s in %s on line %s', $this::class, $name, $frame['file'], $frame['line']), \E_USER_NOTICE); + } + + get_in_scope: + + try { + if (null === $scope) { + if (!$notByRef) { + return $this->$name; + } + $value = $this->$name; + + return $value; + } + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + + return $accessor['get']($this, $name, $notByRef); + } catch (\Error $e) { + if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) { + throw $e; + } + + try { + if (null === $scope) { + $this->$name = []; + + return $this->$name; + } + + $accessor['set']($this, $name, []); + + return $accessor['get']($this, $name, $notByRef); + } catch (\Error) { + if (preg_match('/^Cannot access uninitialized non-nullable property ([^ ]++) by reference$/', $e->getMessage(), $matches)) { + throw new \Error('Typed property '.$matches[1].' must not be accessed before initialization', $e->getCode(), $e->getPrevious()); + } + + throw $e; + } + } + } + + public function __set($name, $value): void + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + + if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2); + $state = $this->lazyObjectState ?? null; + + if ($state && ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"])) + && LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status + ) { + if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { + $state->initialize($this, $name, $writeScope ?? $scope); + } + goto set_in_scope; + } + } + + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['set']) { + parent::__set($name, $value); + + return; + } + + set_in_scope: + + if (null === $scope) { + $this->$name = $value; + } else { + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + $accessor['set']($this, $name, $value); + } + } + + public function __isset($name): bool + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + + if ([$class, , $writeScope] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScopeForRead($propertyScopes, $class, $name); + $state = $this->lazyObjectState ?? null; + + if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"])) + && LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status + && LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $writeScope ?? $scope) + ) { + goto isset_in_scope; + } + } + + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['isset']) { + return parent::__isset($name); + } + + isset_in_scope: + + if (null === $scope) { + return isset($this->$name); + } + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + + return $accessor['isset']($this, $name); + } + + public function __unset($name): void + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + + if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2); + $state = $this->lazyObjectState ?? null; + + if ($state && ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"])) + && LazyObjectState::STATUS_INITIALIZED_FULL !== $state->status + ) { + if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) { + $state->initialize($this, $name, $writeScope ?? $scope); + } + goto unset_in_scope; + } + } + + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['unset']) { + parent::__unset($name); + + return; + } + + unset_in_scope: + + if (null === $scope) { + unset($this->$name); + } else { + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + $accessor['unset']($this, $name); + } + } + + public function __clone(): void + { + if ($state = $this->lazyObjectState ?? null) { + $this->lazyObjectState = clone $state; + } + + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) { + parent::__clone(); + } + } + + public function __serialize(): array + { + $class = self::class; + + if ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['serialize']) { + $properties = parent::__serialize(); + } else { + $this->initializeLazyObject(); + $properties = (array) $this; + } + unset($properties["\0$class\0lazyObjectState"]); + + if (Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) { + return $properties; + } + + $scope = get_parent_class($class); + $data = []; + + foreach (parent::__sleep() as $name) { + $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0$class\0$name"] ?? $properties[$k = "\0$scope\0$name"] ?? $k = null; + + if (null === $k) { + trigger_error(\sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $name), \E_USER_NOTICE); + } else { + $data[$k] = $value; + } + } + + return $data; + } + + public function __destruct() + { + $state = $this->lazyObjectState ?? null; + + if ($state && \in_array($state->status, [LazyObjectState::STATUS_UNINITIALIZED_FULL, LazyObjectState::STATUS_UNINITIALIZED_PARTIAL], true)) { + return; + } + + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) { + parent::__destruct(); + } + } + + #[Ignore] + private function setLazyObjectAsInitialized(bool $initialized): void + { + $state = $this->lazyObjectState ?? null; + + if ($state && !\is_array($state->initializer)) { + $state->status = $initialized ? LazyObjectState::STATUS_INITIALIZED_FULL : LazyObjectState::STATUS_UNINITIALIZED_FULL; + } + } +} diff --git a/vendor/symfony/var-exporter/LazyObjectInterface.php b/vendor/symfony/var-exporter/LazyObjectInterface.php new file mode 100644 index 0000000..3670884 --- /dev/null +++ b/vendor/symfony/var-exporter/LazyObjectInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +interface LazyObjectInterface +{ + /** + * Returns whether the object is initialized. + * + * @param $partial Whether partially initialized objects should be considered as initialized + */ + public function isLazyObjectInitialized(bool $partial = false): bool; + + /** + * Forces initialization of a lazy object and returns it. + */ + public function initializeLazyObject(): object; + + /** + * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object + */ + public function resetLazyObject(): bool; +} diff --git a/vendor/symfony/var-exporter/LazyProxyTrait.php b/vendor/symfony/var-exporter/LazyProxyTrait.php new file mode 100644 index 0000000..af009ae --- /dev/null +++ b/vendor/symfony/var-exporter/LazyProxyTrait.php @@ -0,0 +1,355 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\Serializer\Attribute\Ignore; +use Symfony\Component\VarExporter\Hydrator as PublicHydrator; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry; +use Symfony\Component\VarExporter\Internal\LazyObjectState; +use Symfony\Component\VarExporter\Internal\LazyObjectTrait; + +trait LazyProxyTrait +{ + use LazyObjectTrait; + + /** + * Creates a lazy-loading virtual proxy. + * + * @param \Closure():object $initializer Returns the proxied object + * @param static|null $instance + */ + public static function createLazyProxy(\Closure $initializer, ?object $instance = null): static + { + if (self::class !== $class = $instance ? $instance::class : static::class) { + $skippedProperties = ["\0".self::class."\0lazyObjectState" => true]; + } elseif (\defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) { + Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES; + } + + $instance ??= (Registry::$classReflectors[$class] ??= new \ReflectionClass($class))->newInstanceWithoutConstructor(); + $instance->lazyObjectState = new LazyObjectState($initializer); + + foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) { + $reset($instance, $skippedProperties ??= []); + } + + return $instance; + } + + /** + * Returns whether the object is initialized. + * + * @param $partial Whether partially initialized objects should be considered as initialized + */ + #[Ignore] + public function isLazyObjectInitialized(bool $partial = false): bool + { + return !isset($this->lazyObjectState) || isset($this->lazyObjectState->realInstance) || Registry::$noInitializerState === $this->lazyObjectState->initializer; + } + + /** + * Forces initialization of a lazy object and returns it. + */ + public function initializeLazyObject(): parent + { + if ($state = $this->lazyObjectState ?? null) { + return $state->realInstance ??= ($state->initializer)(); + } + + return $this; + } + + /** + * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object + */ + public function resetLazyObject(): bool + { + if (!isset($this->lazyObjectState) || Registry::$noInitializerState === $this->lazyObjectState->initializer) { + return false; + } + + unset($this->lazyObjectState->realInstance); + + return true; + } + + public function &__get($name): mixed + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + $instance = $this; + $notByRef = 0; + + if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) { + $notByRef = $access & Hydrator::PROPERTY_NOT_BY_REF; + $scope = Registry::getScopeForRead($propertyScopes, $class, $name); + + if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) { + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } + if (\PHP_VERSION_ID >= 80400 && !$notByRef && ($access >> 2) & \ReflectionProperty::IS_PRIVATE_SET) { + $scope ??= $writeScope; + } + $parent = 2; + goto get_in_scope; + } + } + $parent = (Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['get']; + + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } else { + if (2 === $parent) { + return parent::__get($name); + } + $value = parent::__get($name); + + return $value; + } + + if (!$parent && null === $class && !\array_key_exists($name, (array) $instance)) { + $frame = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]; + trigger_error(\sprintf('Undefined property: %s::$%s in %s on line %s', $instance::class, $name, $frame['file'], $frame['line']), \E_USER_NOTICE); + } + + get_in_scope: + $notByRef = $notByRef || 1 === $parent; + + try { + if (null === $scope) { + if (!$notByRef) { + return $instance->$name; + } + $value = $instance->$name; + + return $value; + } + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + + return $accessor['get']($instance, $name, $notByRef); + } catch (\Error $e) { + if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) { + throw $e; + } + + try { + if (null === $scope) { + $instance->$name = []; + + return $instance->$name; + } + + $accessor['set']($instance, $name, []); + + return $accessor['get']($instance, $name, $notByRef); + } catch (\Error) { + throw $e; + } + } + } + + public function __set($name, $value): void + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + $instance = $this; + + if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2); + + if ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"])) { + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } + goto set_in_scope; + } + } + + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['set']) { + parent::__set($name, $value); + + return; + } + + set_in_scope: + + if (null === $scope) { + $instance->$name = $value; + } else { + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + $accessor['set']($instance, $name, $value); + } + } + + public function __isset($name): bool + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + $instance = $this; + + if ([$class] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScopeForRead($propertyScopes, $class, $name); + + if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) { + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } + goto isset_in_scope; + } + } + + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['isset']) { + return parent::__isset($name); + } + + isset_in_scope: + + if (null === $scope) { + return isset($instance->$name); + } + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + + return $accessor['isset']($instance, $name); + } + + public function __unset($name): void + { + $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class); + $scope = null; + $instance = $this; + + if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) { + $scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2); + + if ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"])) { + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } + goto unset_in_scope; + } + } + + if ($state = $this->lazyObjectState ?? null) { + $instance = $state->realInstance ??= ($state->initializer)(); + } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['unset']) { + parent::__unset($name); + + return; + } + + unset_in_scope: + + if (null === $scope) { + unset($instance->$name); + } else { + $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope); + $accessor['unset']($instance, $name); + } + } + + public function __clone(): void + { + if (!isset($this->lazyObjectState)) { + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) { + parent::__clone(); + } + + return; + } + + $this->lazyObjectState = clone $this->lazyObjectState; + + if (isset($this->lazyObjectState->realInstance)) { + $this->lazyObjectState->realInstance = clone $this->lazyObjectState->realInstance; + } + } + + public function __serialize(): array + { + $class = self::class; + $state = $this->lazyObjectState ?? null; + + if (!$state && (Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['serialize']) { + $properties = parent::__serialize(); + } else { + $properties = (array) $this; + + if ($state) { + unset($properties["\0$class\0lazyObjectState"]); + $properties["\0$class\0lazyObjectReal"] = $state->realInstance ??= ($state->initializer)(); + } + } + + if ($state || Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) { + return $properties; + } + + $scope = get_parent_class($class); + $data = []; + + foreach (parent::__sleep() as $name) { + $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0$class\0$name"] ?? $properties[$k = "\0$scope\0$name"] ?? $k = null; + + if (null === $k) { + trigger_error(\sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $name), \E_USER_NOTICE); + } else { + $data[$k] = $value; + } + } + + return $data; + } + + public function __unserialize(array $data): void + { + $class = self::class; + + if ($instance = $data["\0$class\0lazyObjectReal"] ?? null) { + unset($data["\0$class\0lazyObjectReal"]); + + foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) { + $reset($this, $data); + } + + if ($data) { + PublicHydrator::hydrate($this, $data); + } + $this->lazyObjectState = new LazyObjectState(Registry::$noInitializerState ??= static fn () => throw new \LogicException('Lazy proxy has no initializer.')); + $this->lazyObjectState->realInstance = $instance; + } elseif ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['unserialize']) { + parent::__unserialize($data); + } else { + PublicHydrator::hydrate($this, $data); + + if (Registry::$parentMethods[$class]['wakeup']) { + parent::__wakeup(); + } + } + } + + public function __destruct() + { + if (isset($this->lazyObjectState)) { + return; + } + + if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) { + parent::__destruct(); + } + } +} diff --git a/vendor/symfony/var-exporter/ProxyHelper.php b/vendor/symfony/var-exporter/ProxyHelper.php new file mode 100644 index 0000000..56fd00e --- /dev/null +++ b/vendor/symfony/var-exporter/ProxyHelper.php @@ -0,0 +1,553 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Exception\LogicException; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\LazyObjectRegistry; + +/** + * @author Nicolas Grekas + */ +final class ProxyHelper +{ + /** + * Helps generate lazy-loading ghost objects. + * + * @throws LogicException When the class is incompatible with ghost objects + */ + public static function generateLazyGhost(\ReflectionClass $class): string + { + if (\PHP_VERSION_ID >= 80200 && \PHP_VERSION_ID < 80300 && $class->isReadOnly()) { + throw new LogicException(\sprintf('Cannot generate lazy ghost with PHP < 8.3: class "%s" is readonly.', $class->name)); + } + if ($class->isFinal()) { + throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" is final.', $class->name)); + } + if ($class->isInterface() || $class->isAbstract() || $class->isTrait()) { + throw new LogicException(\sprintf('Cannot generate lazy ghost: "%s" is not a concrete class.', $class->name)); + } + if (\stdClass::class !== $class->name && $class->isInternal()) { + throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" is internal.', $class->name)); + } + if ($class->hasMethod('__get') && 'mixed' !== (self::exportType($class->getMethod('__get')) ?? 'mixed')) { + throw new LogicException(\sprintf('Cannot generate lazy ghost: return type of method "%s::__get()" should be "mixed".', $class->name)); + } + + static $traitMethods; + $traitMethods ??= (new \ReflectionClass(LazyGhostTrait::class))->getMethods(); + + foreach ($traitMethods as $method) { + if ($class->hasMethod($method->name) && $class->getMethod($method->name)->isFinal()) { + throw new LogicException(\sprintf('Cannot generate lazy ghost: method "%s::%s()" is final.', $class->name, $method->name)); + } + } + + $parent = $class; + while ($parent = $parent->getParentClass()) { + if (\stdClass::class !== $parent->name && $parent->isInternal()) { + throw new LogicException(\sprintf('Cannot generate lazy ghost: class "%s" extends "%s" which is internal.', $class->name, $parent->name)); + } + } + + $hooks = ''; + $propertyScopes = Hydrator::$propertyScopes[$class->name] ??= Hydrator::getPropertyScopes($class->name); + foreach ($propertyScopes as $key => [$scope, $name, , $access]) { + $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; + $flags = $access >> 2; + + if ($k !== $key || !($access & Hydrator::PROPERTY_HAS_HOOKS) || $flags & \ReflectionProperty::IS_VIRTUAL) { + continue; + } + + if ($flags & (\ReflectionProperty::IS_FINAL | \ReflectionProperty::IS_PRIVATE)) { + throw new LogicException(\sprintf('Cannot generate lazy ghost: property "%s::$%s" is final or private(set).', $class->name, $name)); + } + + $p = $propertyScopes[$k][4] ?? Hydrator::$propertyScopes[$class->name][$k][4] = new \ReflectionProperty($scope, $name); + + $type = self::exportType($p); + $hooks .= "\n " + .($p->isProtected() ? 'protected' : 'public') + .($p->isProtectedSet() ? ' protected(set)' : '') + ." {$type} \${$name}" + .($p->hasDefaultValue() ? ' = '.VarExporter::export($p->getDefaultValue()) : '') + ." {\n"; + + foreach ($p->getHooks() as $hook => $method) { + if ('get' === $hook) { + $ref = ($method->returnsReference() ? '&' : ''); + $hooks .= " {$ref}get { \$this->initializeLazyObject(); return parent::\${$name}::get(); }\n"; + } elseif ('set' === $hook) { + $parameters = self::exportParameters($method, true); + $arg = '$'.$method->getParameters()[0]->name; + $hooks .= " set({$parameters}) { \$this->initializeLazyObject(); parent::\${$name}::set({$arg}); }\n"; + } else { + throw new LogicException(\sprintf('Cannot generate lazy ghost: hook "%s::%s()" is not supported.', $class->name, $method->name)); + } + } + + $hooks .= " }\n"; + } + + $propertyScopes = self::exportPropertyScopes($class->name, $propertyScopes); + + return <<name} implements \Symfony\Component\VarExporter\LazyObjectInterface + { + use \Symfony\Component\VarExporter\LazyGhostTrait; + + private const LAZY_OBJECT_PROPERTY_SCOPES = {$propertyScopes}; + {$hooks}} + + // Help opcache.preload discover always-needed symbols + class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); + class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); + class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + + EOPHP; + } + + /** + * Helps generate lazy-loading virtual proxies. + * + * @param \ReflectionClass[] $interfaces + * + * @throws LogicException When the class is incompatible with virtual proxies + */ + public static function generateLazyProxy(?\ReflectionClass $class, array $interfaces = []): string + { + if (!class_exists($class?->name ?? \stdClass::class, false)) { + throw new LogicException(\sprintf('Cannot generate lazy proxy: "%s" is not a class.', $class->name)); + } + if ($class?->isFinal()) { + throw new LogicException(\sprintf('Cannot generate lazy proxy: class "%s" is final.', $class->name)); + } + if (\PHP_VERSION_ID >= 80200 && \PHP_VERSION_ID < 80300 && $class?->isReadOnly()) { + throw new LogicException(\sprintf('Cannot generate lazy proxy with PHP < 8.3: class "%s" is readonly.', $class->name)); + } + + $propertyScopes = $class ? Hydrator::$propertyScopes[$class->name] ??= Hydrator::getPropertyScopes($class->name) : []; + $abstractProperties = []; + $hookedProperties = []; + if (\PHP_VERSION_ID >= 80400 && $class) { + foreach ($propertyScopes as $key => [$scope, $name, , $access]) { + $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name; + $flags = $access >> 2; + + if ($k !== $key) { + continue; + } + + if ($flags & \ReflectionProperty::IS_ABSTRACT) { + $abstractProperties[$name] = $propertyScopes[$k][4] ?? Hydrator::$propertyScopes[$class->name][$k][4] = new \ReflectionProperty($scope, $name); + continue; + } + $abstractProperties[$name] = false; + + if (!($access & Hydrator::PROPERTY_HAS_HOOKS) || $flags & \ReflectionProperty::IS_VIRTUAL) { + continue; + } + + if ($flags & (\ReflectionProperty::IS_FINAL | \ReflectionProperty::IS_PRIVATE)) { + throw new LogicException(\sprintf('Cannot generate lazy proxy: property "%s::$%s" is final or private(set).', $class->name, $name)); + } + + $p = $propertyScopes[$k][4] ?? Hydrator::$propertyScopes[$class->name][$k][4] = new \ReflectionProperty($scope, $name); + $hookedProperties[$name] = [$p, $p->getHooks()]; + } + } + + $methodReflectors = [$class?->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) ?? []]; + foreach ($interfaces as $interface) { + if (!$interface->isInterface()) { + throw new LogicException(\sprintf('Cannot generate lazy proxy: "%s" is not an interface.', $interface->name)); + } + $methodReflectors[] = $interface->getMethods(); + + if (\PHP_VERSION_ID >= 80400) { + foreach ($interface->getProperties() as $p) { + $abstractProperties[$p->name] ??= $p; + $hookedProperties[$p->name] ??= [$p, []]; + $hookedProperties[$p->name][1] += $p->getHooks(); + } + } + } + + $hooks = ''; + + foreach (array_filter($abstractProperties) as $name => $p) { + $type = self::exportType($p); + $hooks .= "\n " + .($p->isProtected() ? 'protected' : 'public') + .($p->isProtectedSet() ? ' protected(set)' : '') + ." {$type} \${$name};\n"; + } + + foreach ($hookedProperties as $name => [$p, $methods]) { + $type = self::exportType($p); + $hooks .= "\n " + .($p->isProtected() ? 'protected' : 'public') + .($p->isProtectedSet() ? ' protected(set)' : '') + ." {$type} \${$name} {\n"; + + foreach ($methods as $hook => $method) { + if ('get' === $hook) { + $ref = ($method->returnsReference() ? '&' : ''); + $hooks .= <<lazyObjectState)) { + return (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$p->name}; + } + + return parent::\${$p->name}::get(); + } + + EOPHP; + } elseif ('set' === $hook) { + $parameters = self::exportParameters($method, true); + $arg = '$'.$method->getParameters()[0]->name; + $hooks .= <<lazyObjectState)) { + \$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)(); + \$this->lazyObjectState->realInstance->{$p->name} = {$arg}; + } + + parent::\${$p->name}::set({$arg}); + } + + EOPHP; + } else { + throw new LogicException(\sprintf('Cannot generate lazy proxy: hook "%s::%s()" is not supported.', $class->name, $method->name)); + } + } + + $hooks .= " }\n"; + } + + $extendsInternalClass = false; + if ($parent = $class) { + do { + $extendsInternalClass = \stdClass::class !== $parent->name && $parent->isInternal(); + } while (!$extendsInternalClass && $parent = $parent->getParentClass()); + } + $methodsHaveToBeProxied = $extendsInternalClass; + $methods = []; + $methodReflectors = array_merge(...$methodReflectors); + + foreach ($methodReflectors as $method) { + if ('__get' !== strtolower($method->name) || 'mixed' === ($type = self::exportType($method) ?? 'mixed')) { + continue; + } + $methodsHaveToBeProxied = true; + $trait = new \ReflectionMethod(LazyProxyTrait::class, '__get'); + $body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine()); + $body[0] = str_replace('): mixed', '): '.$type, $body[0]); + $methods['__get'] = strtr(implode('', $body).' }', [ + 'Hydrator' => '\\'.Hydrator::class, + 'Registry' => '\\'.LazyObjectRegistry::class, + ]); + break; + } + + foreach ($methodReflectors as $method) { + if (($method->isStatic() && !$method->isAbstract()) || isset($methods[$lcName = strtolower($method->name)])) { + continue; + } + if ($method->isFinal()) { + if ($extendsInternalClass || $methodsHaveToBeProxied || method_exists(LazyProxyTrait::class, $method->name)) { + throw new LogicException(\sprintf('Cannot generate lazy proxy: method "%s::%s()" is final.', $class->name, $method->name)); + } + continue; + } + if (method_exists(LazyProxyTrait::class, $method->name) || ($method->isProtected() && !$method->isAbstract())) { + continue; + } + + $signature = self::exportSignature($method, true, $args); + $parentCall = $method->isAbstract() ? "throw new \BadMethodCallException('Cannot forward abstract method \"{$method->class}::{$method->name}()\".')" : "parent::{$method->name}({$args})"; + + if ($method->isStatic()) { + $body = " $parentCall;"; + } elseif (str_ends_with($signature, '): never') || str_ends_with($signature, '): void')) { + $body = <<lazyObjectState)) { + (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args}); + } else { + {$parentCall}; + } + EOPHP; + } else { + if (!$methodsHaveToBeProxied && !$method->isAbstract()) { + // Skip proxying methods that might return $this + foreach (preg_split('/[()|&]++/', self::exportType($method) ?? 'static') as $type) { + if (\in_array($type = ltrim($type, '?'), ['static', 'object'], true)) { + continue 2; + } + foreach ([$class, ...$interfaces] as $r) { + if ($r && is_a($r->name, $type, true)) { + continue 3; + } + } + } + } + + $body = <<lazyObjectState)) { + return (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args}); + } + + return {$parentCall}; + EOPHP; + } + $methods[$lcName] = " {$signature}\n {\n{$body}\n }"; + } + + $types = $interfaces = array_unique(array_column($interfaces, 'name')); + $interfaces[] = LazyObjectInterface::class; + $interfaces = implode(', \\', $interfaces); + $parent = $class ? ' extends \\'.$class->name : ''; + array_unshift($types, $class ? 'parent' : ''); + $type = ltrim(implode('&\\', $types), '&'); + + if (!$class) { + $trait = new \ReflectionMethod(LazyProxyTrait::class, 'initializeLazyObject'); + $body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine()); + $body[0] = str_replace('): parent', '): '.$type, $body[0]); + $methods = ['initializeLazyObject' => implode('', $body).' }'] + $methods; + } + $body = $methods ? "\n".implode("\n\n", $methods)."\n" : ''; + $propertyScopes = $class ? self::exportPropertyScopes($class->name, $propertyScopes) : '[]'; + + if ( + $class?->hasMethod('__unserialize') + && !$class->getMethod('__unserialize')->getParameters()[0]->getType() + ) { + // fix contravariance type problem when $class declares a `__unserialize()` method without typehint. + $lazyProxyTraitStatement = <<__doUnserialize(\$data); + } + + EOPHP; + } else { + $lazyProxyTraitStatement = <<class : $function->getNamespaceName().'\\'; + $namespace = substr($namespace, 0, strrpos($namespace, '\\') ?: 0); + foreach ($function->getParameters() as $param) { + $parameters[] = ($param->getAttributes(\SensitiveParameter::class) ? '#[\SensitiveParameter] ' : '') + .($withParameterTypes && $param->hasType() ? self::exportType($param).' ' : '') + .($param->isPassedByReference() ? '&' : '') + .($param->isVariadic() ? '...' : '').'$'.$param->name + .($param->isOptional() && !$param->isVariadic() ? ' = '.self::exportDefault($param, $namespace) : ''); + if ($param->isPassedByReference()) { + $byRefIndex = 1 + $param->getPosition(); + } + $args .= ($param->isVariadic() ? '...$' : '$').$param->name.', '; + } + + if (!$param || !$byRefIndex) { + $args = '...\func_get_args()'; + } elseif ($param->isVariadic()) { + $args = substr($args, 0, -2); + } else { + $args = explode(', ', $args, 1 + $byRefIndex); + $args[$byRefIndex] = \sprintf('...\array_slice(\func_get_args(), %d)', $byRefIndex); + $args = implode(', ', $args); + } + + return implode(', ', $parameters); + } + + public static function exportSignature(\ReflectionFunctionAbstract $function, bool $withParameterTypes = true, ?string &$args = null): string + { + $parameters = self::exportParameters($function, $withParameterTypes, $args); + + $signature = 'function '.($function->returnsReference() ? '&' : '') + .($function->isClosure() ? '' : $function->name).'('.$parameters.')'; + + if ($function instanceof \ReflectionMethod) { + $signature = ($function->isPublic() ? 'public ' : ($function->isProtected() ? 'protected ' : 'private ')) + .($function->isStatic() ? 'static ' : '').$signature; + } + if ($function->hasReturnType()) { + $signature .= ': '.self::exportType($function); + } + + static $getPrototype; + $getPrototype ??= (new \ReflectionMethod(\ReflectionMethod::class, 'getPrototype'))->invoke(...); + + while ($function) { + if ($function->hasTentativeReturnType()) { + return '#[\ReturnTypeWillChange] '.$signature; + } + + try { + $function = $function instanceof \ReflectionMethod && $function->isAbstract() ? false : $getPrototype($function); + } catch (\ReflectionException) { + break; + } + } + + return $signature; + } + + public static function exportType(\ReflectionFunctionAbstract|\ReflectionProperty|\ReflectionParameter $owner, bool $noBuiltin = false, ?\ReflectionType $type = null): ?string + { + if (!$type ??= $owner instanceof \ReflectionFunctionAbstract ? $owner->getReturnType() : $owner->getType()) { + return null; + } + $class = null; + $types = []; + if ($type instanceof \ReflectionUnionType) { + $reflectionTypes = $type->getTypes(); + $glue = '|'; + } elseif ($type instanceof \ReflectionIntersectionType) { + $reflectionTypes = $type->getTypes(); + $glue = '&'; + } else { + $reflectionTypes = [$type]; + $glue = null; + } + + foreach ($reflectionTypes as $type) { + if ($type instanceof \ReflectionIntersectionType) { + if ('' !== $name = '('.self::exportType($owner, $noBuiltin, $type).')') { + $types[] = $name; + } + continue; + } + $name = $type->getName(); + + if ($noBuiltin && $type->isBuiltin()) { + continue; + } + if (\in_array($name, ['parent', 'self'], true) && $class ??= $owner->getDeclaringClass()) { + $name = 'parent' === $name ? ($class->getParentClass() ?: null)?->name ?? 'parent' : $class->name; + } + + $types[] = ($noBuiltin || $type->isBuiltin() || 'static' === $name ? '' : '\\').$name; + } + + if (!$types) { + return ''; + } + if (null === $glue) { + $defaultNull = $owner instanceof \ReflectionParameter && 'NULL' === rtrim(substr(explode('$'.$owner->name.' = ', (string) $owner, 2)[1] ?? '', 0, -2)); + + return (!$noBuiltin && ($type->allowsNull() || $defaultNull) && !\in_array($name, ['mixed', 'null'], true) ? '?' : '').$types[0]; + } + sort($types); + + return implode($glue, $types); + } + + private static function exportPropertyScopes(string $parent, array $propertyScopes): string + { + uksort($propertyScopes, 'strnatcmp'); + foreach ($propertyScopes as $k => $v) { + unset($propertyScopes[$k][4]); + } + $propertyScopes = VarExporter::export($propertyScopes); + $propertyScopes = str_replace(VarExporter::export($parent), 'parent::class', $propertyScopes); + $propertyScopes = preg_replace("/(?|(,)\n( ) |\n |,\n (\]))/", '$1$2', $propertyScopes); + $propertyScopes = str_replace("\n", "\n ", $propertyScopes); + + return $propertyScopes; + } + + private static function exportDefault(\ReflectionParameter $param, $namespace): string + { + $default = rtrim(substr(explode('$'.$param->name.' = ', (string) $param, 2)[1] ?? '', 0, -2)); + + if (\in_array($default, ['', 'NULL'], true)) { + return 'null'; + } + if (str_ends_with($default, "...'") && preg_match("/^'(?:[^'\\\\]*+(?:\\\\.)*+)*+'$/", $default)) { + return VarExporter::export($param->getDefaultValue()); + } + + $regexp = "/(\"(?:[^\"\\\\]*+(?:\\\\.)*+)*+\"|'(?:[^'\\\\]*+(?:\\\\.)*+)*+')/"; + $parts = preg_split($regexp, $default, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + + $regexp = '/([\[\( ]|^)([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z0-9_\x7f-\xff]++)*+)(\(?)(?!: )/'; + $callback = (false !== strpbrk($default, "\\:('") && $class = $param->getDeclaringClass()) + ? fn ($m) => $m[1].match ($m[2]) { + 'new', 'false', 'true', 'null' => $m[2], + 'NULL' => 'null', + 'self' => '\\'.$class->name, + 'namespace\\parent', + 'parent' => ($parent = $class->getParentClass()) ? '\\'.$parent->name : 'parent', + default => self::exportSymbol($m[2], '(' !== $m[3], $namespace), + }.$m[3] + : fn ($m) => $m[1].match ($m[2]) { + 'new', 'false', 'true', 'null', 'self', 'parent' => $m[2], + 'NULL' => 'null', + default => self::exportSymbol($m[2], '(' !== $m[3], $namespace), + }.$m[3]; + + return implode('', array_map(fn ($part) => match ($part[0]) { + '"' => $part, // for internal classes only + "'" => false !== strpbrk($part, "\\\0\r\n") ? '"'.substr(str_replace(['$', "\0", "\r", "\n"], ['\$', '\0', '\r', '\n'], $part), 1, -1).'"' : $part, + default => preg_replace_callback($regexp, $callback, $part), + }, $parts)); + } + + private static function exportSymbol(string $symbol, bool $mightBeRootConst, string $namespace): string + { + if (!$mightBeRootConst + || false === ($ns = strrpos($symbol, '\\')) + || substr($symbol, 0, $ns) !== $namespace + || \defined($symbol) + || !\defined(substr($symbol, $ns + 1)) + ) { + return '\\'.$symbol; + } + + return '\\'.substr($symbol, $ns + 1); + } +} diff --git a/vendor/symfony/var-exporter/README.md b/vendor/symfony/var-exporter/README.md index a34e4c2..7195270 100644 --- a/vendor/symfony/var-exporter/README.md +++ b/vendor/symfony/var-exporter/README.md @@ -1,15 +1,22 @@ VarExporter Component ===================== -The VarExporter component allows exporting any serializable PHP data structure to -plain PHP code. While doing so, it preserves all the semantics associated with -the serialization mechanism of PHP (`__wakeup`, `__sleep`, `Serializable`, -`__serialize`, `__unserialize`). +The VarExporter component provides various tools to deal with the internal state +of objects: -It also provides an instantiator that allows creating and populating objects -without calling their constructor nor any other methods. +- `VarExporter::export()` allows exporting any serializable PHP data structure to + plain PHP code. While doing so, it preserves all the semantics associated with + the serialization mechanism of PHP (`__wakeup`, `__sleep`, `Serializable`, + `__serialize`, `__unserialize`); +- `Instantiator::instantiate()` creates an object and sets its properties without + calling its constructor nor any other methods; +- `Hydrator::hydrate()` can set the properties of an existing object; +- `Lazy*Trait` can make a class behave as a lazy-loading ghost or virtual proxy. -The reason to use this component *vs* `serialize()` or +VarExporter::export() +--------------------- + +The reason to use `VarExporter::export()` *vs* `serialize()` or [igbinary](https://github.com/igbinary/igbinary) is performance: thanks to OPcache, the resulting code is significantly faster and more memory efficient than using `unserialize()` or `igbinary_unserialize()`. @@ -19,15 +26,107 @@ Unlike `var_export()`, this works on any serializable PHP value. It also provides a few improvements over `var_export()`/`serialize()`: * the output is PSR-2 compatible; - * the output can be re-indented without messing up with `\r` or `\n` in the data - * missing classes throw a `ClassNotFoundException` instead of being unserialized to - `PHP_Incomplete_Class` objects; + * the output can be re-indented without messing up with `\r` or `\n` in the data; + * missing classes throw a `ClassNotFoundException` instead of being unserialized + to `PHP_Incomplete_Class` objects; * references involving `SplObjectStorage`, `ArrayObject` or `ArrayIterator` instances are preserved; * `Reflection*`, `IteratorIterator` and `RecursiveIteratorIterator` classes throw an exception when being serialized (their unserialized version is broken anyway, see https://bugs.php.net/76737). +Instantiator and Hydrator +------------------------- + +`Instantiator::instantiate($class)` creates an object of the given class without +calling its constructor nor any other methods. + +`Hydrator::hydrate()` sets the properties of an existing object, including +private and protected ones. For example: + +```php +// Sets the public or protected $object->propertyName property +Hydrator::hydrate($object, ['propertyName' => $propertyValue]); + +// Sets a private property defined on its parent Bar class: +Hydrator::hydrate($object, ["\0Bar\0privateBarProperty" => $propertyValue]); + +// Alternative way to set the private $object->privateBarProperty property +Hydrator::hydrate($object, [], [ + Bar::class => ['privateBarProperty' => $propertyValue], +]); +``` + +`Lazy*Trait` +------------ + +The component provides two lazy-loading patterns: ghost objects and virtual +proxies (see https://martinfowler.com/eaaCatalog/lazyLoad.html for reference). + +Ghost objects work only with concrete and non-internal classes. In the generic +case, they are not compatible with using factories in their initializer. + +Virtual proxies work with concrete, abstract or internal classes. They provide an +API that looks like the actual objects and forward calls to them. They can cause +identity problems because proxies might not be seen as equivalents to the actual +objects they proxy. + +Because of this identity problem, ghost objects should be preferred when +possible. Exceptions thrown by the `ProxyHelper` class can help decide when it +can be used or not. + +Ghost objects and virtual proxies both provide implementations for the +`LazyObjectInterface` which allows resetting them to their initial state or to +forcibly initialize them when needed. Note that resetting a ghost object skips +its read-only properties. You should use a virtual proxy to reset read-only +properties. + +### `LazyGhostTrait` + +By using `LazyGhostTrait` either directly in your classes or by using +`ProxyHelper::generateLazyGhost()`, you can make their instances lazy-loadable. +This works by creating these instances empty and by computing their state only +when accessing a property. + +```php +class FooLazyGhost extends Foo +{ + use LazyGhostTrait; +} + +$foo = FooLazyGhost::createLazyGhost(initializer: function (Foo $instance): void { + // [...] Use whatever heavy logic you need here + // to compute the $dependencies of the $instance + $instance->__construct(...$dependencies); + // [...] Call setters, etc. if needed +}); + +// $foo is now a lazy-loading ghost object. The initializer will +// be called only when and if a *property* is accessed. +``` + +### `LazyProxyTrait` + +Alternatively, `LazyProxyTrait` can be used to create virtual proxies: + +```php +$proxyCode = ProxyHelper::generateLazyProxy(new ReflectionClass(Foo::class)); +// $proxyCode contains the reference to LazyProxyTrait +// and should be dumped into a file in production envs +eval('class FooLazyProxy'.$proxyCode); + +$foo = FooLazyProxy::createLazyProxy(initializer: function (): Foo { + // [...] Use whatever heavy logic you need here + // to compute the $dependencies of the $instance + $instance = new Foo(...$dependencies); + // [...] Call setters, etc. if needed + + return $instance; +}); +// $foo is now a lazy-loading virtual proxy object. The initializer will +// be called only when and if a *method* is called. +``` + Resources --------- diff --git a/vendor/symfony/var-exporter/VarExporter.php b/vendor/symfony/var-exporter/VarExporter.php index 3e2a4cc..22e9b51 100644 --- a/vendor/symfony/var-exporter/VarExporter.php +++ b/vendor/symfony/var-exporter/VarExporter.php @@ -32,12 +32,12 @@ final class VarExporter /** * Exports a serializable PHP value to PHP code. * - * @param bool &$isStaticValue Set to true after execution if the provided value is static, false otherwise - * @param bool &$classes Classes found in the value are added to this list as both keys and values + * @param bool &$isStaticValue Set to true after execution if the provided value is static, false otherwise + * @param array &$foundClasses Classes found in the value are added to this list as both keys and values * * @throws ExceptionInterface When the provided value cannot be serialized */ - public static function export(mixed $value, bool &$isStaticValue = null, array &$foundClasses = []): string + public static function export(mixed $value, ?bool &$isStaticValue = null, array &$foundClasses = []): string { $isStaticValue = true; @@ -82,7 +82,7 @@ final class VarExporter ksort($states); $wakeups = [null]; - foreach ($states as $k => $v) { + foreach ($states as $v) { if (\is_array($v)) { $wakeups[-$v[0]] = $v[1]; } else { diff --git a/vendor/symfony/var-exporter/composer.json b/vendor/symfony/var-exporter/composer.json index 3bf21a6..e7b0fb0 100644 --- a/vendor/symfony/var-exporter/composer.json +++ b/vendor/symfony/var-exporter/composer.json @@ -2,7 +2,7 @@ "name": "symfony/var-exporter", "type": "library", "description": "Allows exporting any serializable PHP data structure to plain PHP code", - "keywords": ["export", "serialize", "instantiate", "hydrate", "construct", "clone"], + "keywords": ["export", "serialize", "instantiate", "hydrate", "construct", "clone", "lazy-loading", "proxy"], "homepage": "https://symfony.com", "license": "MIT", "authors": [ @@ -16,10 +16,13 @@ } ], "require": { - "php": ">=8.0.2" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { - "symfony/var-dumper": "^5.4|^6.0" + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "autoload": { "psr-4": { "Symfony\\Component\\VarExporter\\": "" }, diff --git a/vendor/workerman/phpsocket.io/src/Engine/Transport.php b/vendor/workerman/phpsocket.io/src/Engine/Transport.php index 09f07a5..d5320b5 100644 --- a/vendor/workerman/phpsocket.io/src/Engine/Transport.php +++ b/vendor/workerman/phpsocket.io/src/Engine/Transport.php @@ -41,11 +41,7 @@ class Transport extends Emitter public function onError(string $msg, string $desc = '') { if ($this->listeners('error')) { - $err = [ - 'type' => 'TransportError', - 'description' => $desc, - ]; - $this->emit('error', $err); + $this->emit('error', "TransportError: {$desc}"); } else { echo("ignored transport error $msg $desc\n"); } diff --git a/vendor/workerman/phpsocket.io/src/SocketIO.php b/vendor/workerman/phpsocket.io/src/SocketIO.php index 94c6687..0bcb612 100644 --- a/vendor/workerman/phpsocket.io/src/SocketIO.php +++ b/vendor/workerman/phpsocket.io/src/SocketIO.php @@ -39,7 +39,13 @@ class SocketIO class_alias('PHPSocketIO\Engine\Protocols\SocketIO', 'Protocols\SocketIO'); } if ($port) { - $host = filter_var(trim($opts['host'] ?? '0.0.0.0', '[]'), FILTER_VALIDATE_IP) ?: '0.0.0.0'; + $host = '0.0.0.0'; + if (isset($opts['host'])) { + $ip = trim($opts['host'], '[]'); + if (filter_var($ip, FILTER_VALIDATE_IP)) { + $host = (strpos($ip, ':') !== false) ? "[$ip]" : $ip; + } + } $worker = new Worker('SocketIO://' . $host . ':' . $port, $opts); $worker->name = 'PHPSocketIO'; diff --git a/vendor/yansongda/artful/CHANGELOG.md b/vendor/yansongda/artful/CHANGELOG.md new file mode 100644 index 0000000..3bf3b4f --- /dev/null +++ b/vendor/yansongda/artful/CHANGELOG.md @@ -0,0 +1,84 @@ +## v1.1.3 + +### added + +- feat: EventInterface 增加更多常用方法(#28, #29) + +## v1.1.2 + +### added + +- feat: 增加 radar 插件 headers 的获取 (#25) + +## v1.1.1 + +### chore + +- chore: 升级 `yansongda/supports` 版本以解决潜在的问题(#19) + +## v1.1.0 + +### changed + +- change: http 配置项由 `httpFactory` 修改为 `http`(#17) + +## v1.0.9 + +### fixed + +- fix: 修复 `JsonPacker` 为空时 `packer` 错误的问题(#15) + +## v1.0.8 + +### added + +- feat: `get_radar_body` 返回值由 `?string` 改为 `mixed`(#10) +- feat: 增加 `StartPlugin` 插件(#10) + +## v1.0.7 + +### added + +- feat: `unpack` 支持参数(#8, #9) + +## v1.0.6 + +### chore + +- chore: 依赖由 `guzzlehttp/guzzle` 改为 `guzzlehttp/psr7`(#7) + +## v1.0.5 + +### added + +- feat: 增加 `load()` 第二个参数默认值为 `null`(#6) + +## v1.0.4 + +### added + +- feat: 增加 `registerService()` 第二个参数默认值为 `null`(#5) + +## v1.0.3 + +### added + +- feat: 增加 `InvalidConfigException`(#4) + +## v1.0.2 + +### changed + +- change: 更改 `Exception` code 码(#3) + +## v1.0.1 + +### added + +- feat: 增加 `shortcut` 快捷方式(#2) + +## v1.0.0 + +### init + +- init: 首个版本(#1) \ No newline at end of file diff --git a/vendor/yansongda/artful/CODE_OF_CONDUCT.md b/vendor/yansongda/artful/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..355aea9 --- /dev/null +++ b/vendor/yansongda/artful/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +me@yansongda.cn. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/vendor/yansongda/artful/LICENSE b/vendor/yansongda/artful/LICENSE new file mode 100644 index 0000000..8573b8b --- /dev/null +++ b/vendor/yansongda/artful/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 yansongda + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/yansongda/artful/README.md b/vendor/yansongda/artful/README.md new file mode 100644 index 0000000..80e5585 --- /dev/null +++ b/vendor/yansongda/artful/README.md @@ -0,0 +1,33 @@ +# Artful + +Api RequesT Framework U Like - 你喜欢的 API 请求框架 + +## 特点 + +- Swoole 支持 +- 灵活的插件机制 +- 丰富的事件系统 +- 命名不那么乱七八糟 +- 一次性配置,多次使用 +- 抽离请求逻辑与过程,统一管理 +- 高度抽象的类,免去各种拼json与xml的痛苦 +- 一个文件,一个请求,一个插件,API 之间互不影响 +- 符合 PSR2、PSR3、PSR4、PSR7、PSR11、PSR14、PSR18 等各项标准,你可以各种方便的与你的框架集成 + +## 安装 + +```shell +composer require yansongda/artful:~1.1.0 -vvv +``` + +## 文档 + +[https://artful.yansongda.cn](https://artful.yansongda.cn) + +## 赏一杯咖啡吧 + +![pay](https://cdn.jsdelivr.net/gh/yansongda/pay/web/public/images/pay.jpg) + +## LICENSE + +MIT \ No newline at end of file diff --git a/vendor/yansongda/artful/SECURITY.md b/vendor/yansongda/artful/SECURITY.md new file mode 100644 index 0000000..d175a1e --- /dev/null +++ b/vendor/yansongda/artful/SECURITY.md @@ -0,0 +1,22 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | PHP | Branch | Status | +|:--------:|:--------:|:----------------------------------------------:|:------------:| +| v3.5 | `>= 8.0` | master | 积极开发中 | +| v3.4 | `>= 8.0` | master | EOL,停止维护 | +| v3.0-3.3 | `>= 7.3` | master | EOL,停止维护 | +| v2.x | `>= 7.0` | [v2](https://github.com/yansongda/pay/tree/v2) | 安全支持,不做新功能开发 | +| v1.x | `>= 5.6` | [v1](https://github.com/yansongda/pay/tree/v1) | EOL,停止维护 | + +## Reporting a Vulnerability + +Use this section to tell people how to report a vulnerability. + +Tell them where to go, how often they can expect to get an update on a +reported vulnerability, what to expect if the vulnerability is accepted or +declined, etc. diff --git a/vendor/yansongda/artful/composer.json b/vendor/yansongda/artful/composer.json new file mode 100644 index 0000000..4f63988 --- /dev/null +++ b/vendor/yansongda/artful/composer.json @@ -0,0 +1,63 @@ +{ + "name": "yansongda/artful", + "description": "Artful 是一个简单易用的 API 请求框架 PHP Api RequesT Framwork U Like。", + "keywords": ["artful", "api", "request", "framework"], + "type": "library", + "license": "MIT", + "support": { + "issues": "https://github.com/yansongda/artful/issues", + "source": "https://github.com/yansongda/artful", + "homepage": "https://artful.yansongda.cn" + }, + "authors": [ + { + "name": "yansongda", + "email": "me@yansongda.cn" + } + ], + "require": { + "php": ">=8.0", + "psr/event-dispatcher": "^1.0", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "psr/container": "^1.1 || ^2.0", + "psr/http-client": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "yansongda/supports": "~4.0.10", + "guzzlehttp/psr7": "^2.6" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "mockery/mockery": "^1.4", + "friendsofphp/php-cs-fixer": "^3.44", + "phpstan/phpstan": "^1.0.0 || ^2.0.0", + "monolog/monolog": "^2.2", + "symfony/var-dumper": "^5.1", + "symfony/http-foundation": "^5.2.0", + "symfony/event-dispatcher": "^5.2.0", + "symfony/psr-http-message-bridge": "^2.1", + "hyperf/pimple": "^2.2", + "guzzlehttp/guzzle": "^7.0" + }, + "autoload": { + "psr-4": { + "Yansongda\\Artful\\": "src" + }, + "files": [ + "src/Functions.php" + ] + }, + "autoload-dev": { + "psr-4": { + "Yansongda\\Artful\\Tests\\": "tests" + } + }, + "suggest": { + "illuminate/container": "其它/无框架下使用 SDK,请安装,任选其一", + "hyperf/pimple": "其它/无框架下使用 SDK,请安装,任选其一" + }, + "scripts": { + "test": "./vendor/bin/phpunit -c phpunit.xml --colors=always", + "cs-fix": "php-cs-fixer fix --dry-run --diff 1>&2", + "analyse": "phpstan analyse --memory-limit 300M -l 5 -c phpstan.neon ./src" + } +} diff --git a/vendor/yansongda/artful/phpstan.neon b/vendor/yansongda/artful/phpstan.neon new file mode 100644 index 0000000..9667f4d --- /dev/null +++ b/vendor/yansongda/artful/phpstan.neon @@ -0,0 +1,7 @@ +parameters: + reportUnmatchedIgnoredErrors: false + excludePaths: + ignoreErrors: + - '#.* Illuminate\\Container\\Container.*#' + - '#.* Hyperf\\Utils\\ApplicationContext.*#' + - '#.* think\\Container.*#' diff --git a/vendor/yansongda/artful/src/Artful.php b/vendor/yansongda/artful/src/Artful.php new file mode 100644 index 0000000..cf9ba48 --- /dev/null +++ b/vendor/yansongda/artful/src/Artful.php @@ -0,0 +1,342 @@ +registerServices($config, $container); + + Artful::set(DirectionInterface::class, CollectionDirection::class); + Artful::set(PackerInterface::class, JsonPacker::class); + } + + /** + * @return mixed + * + * @throws ContainerException + * @throws ServiceNotFoundException + */ + public static function __callStatic(string $service, array $config) + { + if (!empty($config)) { + self::config(...$config); + } + + return self::get($service); + } + + /** + * @throws ContainerException + */ + public static function config(array $config = [], null|Closure|ContainerInterface $container = null): bool + { + if (self::hasContainer() && !($config['_force'] ?? false)) { + return false; + } + + new self($config, $container); + + return true; + } + + /** + * @codeCoverageIgnore + * + * @throws ContainerException + */ + public static function set(string $name, mixed $value): void + { + try { + $container = Artful::getContainer(); + + if ($container instanceof LaravelContainer) { + $container->singleton($name, $value instanceof Closure ? $value : static fn () => $value); + + return; + } + + if (method_exists($container, 'set')) { + $container->set(...func_get_args()); + + return; + } + } catch (ContainerNotFoundException $e) { + throw $e; + } catch (Throwable $e) { + throw new ContainerException('容器异常: '.$e->getMessage()); + } + + throw new ContainerException('容器异常: 当前容器类型不支持 `set` 方法'); + } + + /** + * @codeCoverageIgnore + * + * @throws ContainerException + */ + public static function make(string $service, array $parameters = []): mixed + { + try { + $container = Artful::getContainer(); + + if (method_exists($container, 'make')) { + return $container->make(...func_get_args()); + } + } catch (ContainerNotFoundException $e) { + throw $e; + } catch (Throwable $e) { + throw new ContainerException('容器异常: '.$e->getMessage()); + } + + $parameters = array_values($parameters); + + return new $service(...$parameters); + } + + /** + * @throws ServiceNotFoundException + * @throws ContainerException + */ + public static function get(string $service): mixed + { + try { + return Artful::getContainer()->get($service); + } catch (NotFoundExceptionInterface $e) { + throw new ServiceNotFoundException('服务未找到: '.$e->getMessage()); + } catch (ContainerNotFoundException $e) { + throw $e; + } catch (Throwable $e) { + throw new ContainerException('容器异常: '.$e->getMessage()); + } + } + + /** + * @throws ContainerNotFoundException + */ + public static function has(string $service): bool + { + return Artful::getContainer()->has($service); + } + + public static function setContainer(null|Closure|ContainerInterface $container): void + { + self::$container = $container; + } + + /** + * @throws ContainerNotFoundException + */ + public static function getContainer(): ContainerInterface + { + if (self::$container instanceof ContainerInterface) { + return self::$container; + } + + if (self::$container instanceof Closure) { + return (self::$container)(); + } + + throw new ContainerNotFoundException('容器未找到: `getContainer()` 方法调用失败! 或许你应该先 `setContainer()`'); + } + + public static function hasContainer(): bool + { + return self::$container instanceof ContainerInterface || self::$container instanceof Closure; + } + + public static function clear(): void + { + self::$container = null; + } + + /** + * @throws ContainerException + */ + public static function load(string $service, mixed $data = null): void + { + self::registerService($service, $data); + } + + /** + * @throws ContainerException + */ + public static function registerService(string $service, mixed $data = null): void + { + $var = new $service(); + + if ($var instanceof ServiceProviderInterface) { + $var->register($data); + } + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public static function shortcut(string $shortcut, array $params = []): null|Collection|MessageInterface|Rocket + { + if (!class_exists($shortcut) || !in_array(ShortcutInterface::class, class_implements($shortcut))) { + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_INVALID, "参数异常: [{$shortcut}] 未实现 `ShortcutInterface`"); + } + + /* @var ShortcutInterface $shortcutInstance */ + $shortcutInstance = self::get($shortcut); + + return self::artful($shortcutInstance->getPlugins($params), $params); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + */ + public static function artful(array $plugins, array $params): null|Collection|MessageInterface|Rocket + { + Event::dispatch(new Event\ArtfulStart($plugins, $params)); + + self::verifyPlugin($plugins); + + /* @var Pipeline $pipeline */ + $pipeline = self::make(Pipeline::class); + + /* @var Rocket $rocket */ + $rocket = $pipeline + ->send((new Rocket())->setParams($params)->setPayload(new Collection())) + ->through($plugins) + ->via('assembly') + ->then(static fn ($rocket) => self::ignite($rocket)); + + Event::dispatch(new Event\ArtfulEnd($rocket)); + + if (!empty($params['_return_rocket'])) { + return $rocket; + } + + return $rocket->getDestination(); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws InvalidResponseException + * @throws ServiceNotFoundException + */ + public static function ignite(Rocket $rocket): Rocket + { + if (!should_do_http_request($rocket->getDirection())) { + return $rocket; + } + + /* @var HttpClientFactoryInterface $httpFactory */ + $httpFactory = self::get(HttpClientFactoryInterface::class); + /* @var Config $config */ + $config = self::get(ConfigInterface::class); + + if (!$httpFactory instanceof HttpClientFactoryInterface) { + throw new InvalidParamsException(Exception::PARAMS_HTTP_CLIENT_FACTORY_INVALID, '参数异常: 配置的 `HttpClientFactoryInterface` 不符合规范'); + } + + Logger::info('[Artful] 准备请求第三方 API', $rocket->toArray()); + + $http = $httpFactory->create(array_merge($config->get('http', []), $rocket->getPayload()?->get('_http') ?? [])); + + Event::dispatch(new Event\HttpStart($rocket)); + + try { + $response = $http->sendRequest($rocket->getRadar()); + + $rocket->setDestination(clone $response) + ->setDestinationOrigin(clone $response); + } catch (Throwable $e) { + Logger::error('[Artful] 请求第三方 API 出错', ['message' => $e->getMessage(), 'rocket' => $rocket->toArray(), 'trace' => $e->getTrace()]); + + throw new InvalidResponseException(Exception::REQUEST_RESPONSE_ERROR, '响应异常: 请求第三方 API 出错 - '.$e->getMessage(), [], $e); + } + + Logger::info('[Artful] 请求第三方 API 成功', ['rocket' => $rocket->toArray()]); + + Event::dispatch(new Event\HttpEnd($rocket)); + + return $rocket; + } + + /** + * @throws InvalidParamsException + */ + protected static function verifyPlugin(array $plugins): void + { + foreach ($plugins as $plugin) { + if (is_callable($plugin)) { + continue; + } + + if ((is_object($plugin) + || (is_string($plugin) && class_exists($plugin))) + && in_array(PluginInterface::class, class_implements($plugin))) { + continue; + } + + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_INCOMPATIBLE, "参数异常: [{$plugin}] 插件未实现 `PluginInterface`"); + } + } + + /** + * @throws ContainerException + */ + private function registerServices(array $config, null|Closure|ContainerInterface $container = null): void + { + foreach ($this->coreService as $service) { + self::registerService($service, ContainerServiceProvider::class == $service ? $container : $config); + } + } +} diff --git a/vendor/yansongda/artful/src/Contract/ConfigInterface.php b/vendor/yansongda/artful/src/Contract/ConfigInterface.php new file mode 100644 index 0000000..80916d5 --- /dev/null +++ b/vendor/yansongda/artful/src/Contract/ConfigInterface.php @@ -0,0 +1,14 @@ + + */ + public function getListeners(?string $eventName = null): array; + + /** + * Gets the listener priority for a specific event. + * + * Returns null if the event or the listener does not exist. + */ + public function getListenerPriority(string $eventName, callable $listener): ?int; + + /** + * Checks whether an event has any registered listeners. + */ + public function hasListeners(?string $eventName = null): bool; +} diff --git a/vendor/yansongda/artful/src/Contract/HttpClientFactoryInterface.php b/vendor/yansongda/artful/src/Contract/HttpClientFactoryInterface.php new file mode 100644 index 0000000..e4e33f0 --- /dev/null +++ b/vendor/yansongda/artful/src/Contract/HttpClientFactoryInterface.php @@ -0,0 +1,12 @@ +getBody(); + + if (!is_null($result = $packer->unpack($body, $params))) { + return new Collection($result); + } + + throw new InvalidResponseException(Exception::RESPONSE_UNPACK_ERROR, '响应异常: 解包错误', ['body' => $body, 'response' => $response]); + } +} diff --git a/vendor/yansongda/artful/src/Direction/NoHttpRequestDirection.php b/vendor/yansongda/artful/src/Direction/NoHttpRequestDirection.php new file mode 100644 index 0000000..8201c90 --- /dev/null +++ b/vendor/yansongda/artful/src/Direction/NoHttpRequestDirection.php @@ -0,0 +1,17 @@ +{$method}(...$args); + } +} diff --git a/vendor/yansongda/artful/src/Event/ArtfulEnd.php b/vendor/yansongda/artful/src/Event/ArtfulEnd.php new file mode 100755 index 0000000..b59ca80 --- /dev/null +++ b/vendor/yansongda/artful/src/Event/ArtfulEnd.php @@ -0,0 +1,7 @@ +extra = $extra; + + parent::__construct($message, $code, $previous); + } +} diff --git a/vendor/yansongda/artful/src/Exception/InvalidConfigException.php b/vendor/yansongda/artful/src/Exception/InvalidConfigException.php new file mode 100644 index 0000000..838c558 --- /dev/null +++ b/vendor/yansongda/artful/src/Exception/InvalidConfigException.php @@ -0,0 +1,18 @@ +response = $extra; + + parent::__construct($message, $code, $extra, $previous); + } +} diff --git a/vendor/yansongda/artful/src/Exception/ServiceNotFoundException.php b/vendor/yansongda/artful/src/Exception/ServiceNotFoundException.php new file mode 100644 index 0000000..20824d0 --- /dev/null +++ b/vendor/yansongda/artful/src/Exception/ServiceNotFoundException.php @@ -0,0 +1,16 @@ +filter(static fn ($v, $k) => !Str::startsWith($k, '_') && !is_null($v) && (empty($closure) || $closure($k, $v))); +} + +function get_radar_method(?Collection $payload): ?string +{ + $string = $payload?->get('_method') ?? null; + + if (is_null($string)) { + return null; + } + + return strtoupper($string); +} + +function get_radar_url(?Collection $payload): ?string +{ + return $payload?->get('_url') ?? null; +} + +function get_radar_body(?Collection $payload): mixed +{ + return $payload?->get('_body') ?? null; +} + +function get_radar_headers(?Collection $payload): mixed +{ + return $payload?->get('_headers') ?? null; +} diff --git a/vendor/yansongda/artful/src/HttpClientFactory.php b/vendor/yansongda/artful/src/HttpClientFactory.php new file mode 100644 index 0000000..c162f6e --- /dev/null +++ b/vendor/yansongda/artful/src/HttpClientFactory.php @@ -0,0 +1,39 @@ +container->has(HttpClientInterface::class)) { + if (($http = $this->container->get(HttpClientInterface::class)) instanceof ClientInterface) { + return $http; + } + + throw new InvalidParamsException(Exception::PARAMS_HTTP_CLIENT_INVALID, '参数异常: `HttpClient` 不符合 PSR 规范'); + } + + return new Client($options); + } +} diff --git a/vendor/yansongda/artful/src/Logger.php b/vendor/yansongda/artful/src/Logger.php new file mode 100644 index 0000000..2b3c6dd --- /dev/null +++ b/vendor/yansongda/artful/src/Logger.php @@ -0,0 +1,49 @@ +get('logger.enable', false)) { + return; + } + + $class = Artful::get(LoggerInterface::class); + + if ($class instanceof \Psr\Log\LoggerInterface) { + $class->{$method}(...$args); + + return; + } + + throw new InvalidParamsException(Exception::PARAMS_LOGGER_DRIVER_INVALID, '配置异常: 配置的 `LoggerInterface` 不符合 PSR 规范'); + } +} diff --git a/vendor/yansongda/artful/src/Packer/JsonPacker.php b/vendor/yansongda/artful/src/Packer/JsonPacker.php new file mode 100644 index 0000000..39ed979 --- /dev/null +++ b/vendor/yansongda/artful/src/Packer/JsonPacker.php @@ -0,0 +1,26 @@ +isEmpty()) || empty($payload)) { + return ''; + } + + return Collection::wrap($payload)->toJson(); + } + + public function unpack(string $payload, ?array $params = null): ?array + { + return Arr::wrapJson($payload); + } +} diff --git a/vendor/yansongda/artful/src/Packer/QueryPacker.php b/vendor/yansongda/artful/src/Packer/QueryPacker.php new file mode 100644 index 0000000..607049d --- /dev/null +++ b/vendor/yansongda/artful/src/Packer/QueryPacker.php @@ -0,0 +1,22 @@ +query(); + } + + public function unpack(string $payload, ?array $params = null): array + { + return Arr::wrapQuery($payload, !empty($params['_unpack_raw'])); + } +} diff --git a/vendor/yansongda/artful/src/Packer/XmlPacker.php b/vendor/yansongda/artful/src/Packer/XmlPacker.php new file mode 100644 index 0000000..2aeaea9 --- /dev/null +++ b/vendor/yansongda/artful/src/Packer/XmlPacker.php @@ -0,0 +1,22 @@ +toXml(); + } + + public function unpack(string $payload, ?array $params = null): array + { + return Arr::wrapXml($payload); + } +} diff --git a/vendor/yansongda/artful/src/Plugin/AddPayloadBodyPlugin.php b/vendor/yansongda/artful/src/Plugin/AddPayloadBodyPlugin.php new file mode 100644 index 0000000..3affeae --- /dev/null +++ b/vendor/yansongda/artful/src/Plugin/AddPayloadBodyPlugin.php @@ -0,0 +1,33 @@ + $rocket]); + + $packer = get_packer($rocket->getPacker()); + + $rocket->mergePayload(['_body' => $packer->pack(filter_params($rocket->getPayload()))]); + + Logger::info('[AddPayloadBodyPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/artful/src/Plugin/AddRadarPlugin.php b/vendor/yansongda/artful/src/Plugin/AddRadarPlugin.php new file mode 100644 index 0000000..f842f4d --- /dev/null +++ b/vendor/yansongda/artful/src/Plugin/AddRadarPlugin.php @@ -0,0 +1,45 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + $rocket->setRadar(new Request( + get_radar_method($payload) ?? 'POST', + get_radar_url($payload), + get_radar_headers($payload) ?? $this->getHeaders(), + get_radar_body($payload), + )); + + Logger::info('[AddRadarPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function getHeaders(): array + { + return [ + 'User-Agent' => 'yansongda/artful-v1', + 'Content-Type' => 'application/json;charset=utf-8', + ]; + } +} diff --git a/vendor/yansongda/artful/src/Plugin/ParserPlugin.php b/vendor/yansongda/artful/src/Plugin/ParserPlugin.php new file mode 100644 index 0000000..daa9701 --- /dev/null +++ b/vendor/yansongda/artful/src/Plugin/ParserPlugin.php @@ -0,0 +1,45 @@ + $rocket]); + + $response = $rocket->getDestination(); + $direction = get_direction($rocket->getDirection()); + $packer = get_packer($rocket->getPacker()); + $payload = $rocket->getPayload(); + + if (!is_null($response) && !($response instanceof ResponseInterface)) { + throw new InvalidParamsException(Exception::PARAMS_PARSER_DIRECTION_INVALID, '参数异常: 解析插件中 `Rocket` 的 `destination` 只能是 `null` 或者 `ResponseInterface`'); + } + + $rocket->setDestination($direction->guide($packer, $response, $payload?->all() ?? [])); + + Logger::debug('[ParserPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } +} diff --git a/vendor/yansongda/artful/src/Plugin/StartPlugin.php b/vendor/yansongda/artful/src/Plugin/StartPlugin.php new file mode 100644 index 0000000..f32f2ee --- /dev/null +++ b/vendor/yansongda/artful/src/Plugin/StartPlugin.php @@ -0,0 +1,24 @@ + $rocket]); + + $rocket->mergePayload($rocket->getParams()); + + Logger::info('[StartPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/artful/src/Rocket.php b/vendor/yansongda/artful/src/Rocket.php new file mode 100644 index 0000000..97b39fd --- /dev/null +++ b/vendor/yansongda/artful/src/Rocket.php @@ -0,0 +1,180 @@ +radar; + } + + public function setRadar(?RequestInterface $radar): Rocket + { + $this->radar = $radar; + + return $this; + } + + public function getParams(): array + { + return $this->params; + } + + public function setParams(array $params): Rocket + { + $this->params = $params; + + return $this; + } + + public function mergeParams(array $params): Rocket + { + $this->params = array_merge($this->params, $params); + + return $this; + } + + public function getPayload(): ?Collection + { + return $this->payload; + } + + public function setPayload(null|array|Collection $payload): Rocket + { + if (is_array($payload)) { + $payload = new Collection($payload); + } + + $this->payload = $payload; + + return $this; + } + + public function mergePayload(array $payload): Rocket + { + if (empty($this->payload)) { + $this->payload = new Collection(); + } + + $this->payload = $this->payload->merge($payload); + + return $this; + } + + public function exceptPayload(mixed $key): Rocket + { + if (empty($this->payload)) { + return $this; + } + + $this->payload = $this->payload->except($key); + + return $this; + } + + public function getPacker(): string + { + return $this->packer; + } + + public function setPacker(string $packer): Rocket + { + $this->packer = $packer; + + return $this; + } + + public function getDirection(): string + { + return $this->direction; + } + + public function setDirection(string $direction): Rocket + { + $this->direction = $direction; + + return $this; + } + + public function getDestination(): null|Collection|MessageInterface + { + return $this->destination; + } + + public function setDestination(null|Collection|MessageInterface $destination): Rocket + { + $this->destination = $destination; + + return $this; + } + + public function getDestinationOrigin(): null|RequestInterface|ResponseInterface + { + return $this->destinationOrigin; + } + + public function setDestinationOrigin(null|RequestInterface|ResponseInterface $destinationOrigin): Rocket + { + $this->destinationOrigin = $destinationOrigin; + + return $this; + } + + public function toArray(): array + { + $request = $this->getRadar(); + $destination = $this->getDestinationOrigin(); + + return [ + 'radar' => [ + 'url' => $request?->getUri()->__toString(), + 'method' => $request?->getMethod(), + 'headers' => $request?->getHeaders(), + 'body' => (string) $request?->getBody(), + ], + 'params' => $this->getParams(), + 'payload' => $this->getPayload()?->toArray(), + 'packer' => $this->getPacker(), + 'direction' => $this->getDirection(), + 'destination' => $this->getDestination(), + 'destination_origin' => [ + 'status' => $destination instanceof ResponseInterface ? $destination->getStatusCode() : null, + 'headers' => $destination?->getHeaders(), + 'body' => (string) $destination?->getBody(), + ], + ]; + } +} diff --git a/vendor/yansongda/artful/src/Service/ConfigServiceProvider.php b/vendor/yansongda/artful/src/Service/ConfigServiceProvider.php new file mode 100644 index 0000000..3a6ff5e --- /dev/null +++ b/vendor/yansongda/artful/src/Service/ConfigServiceProvider.php @@ -0,0 +1,42 @@ + [ + 'enable' => false, + 'file' => null, + 'identify' => 'yansongda.artful', + 'level' => 'debug', + 'type' => 'daily', + 'max_files' => 30, + ], + 'http' => [ + 'timeout' => 5.0, + 'connect_timeout' => 3.0, + 'headers' => [ + 'User-Agent' => 'yansongda/artful-v1', + ], + ], + ]; + + /** + * @throws ContainerException + */ + public function register(mixed $data = null): void + { + $config = new Config(array_replace_recursive($this->config, $data ?? [])); + + Artful::set(ConfigInterface::class, $config); + } +} diff --git a/vendor/yansongda/artful/src/Service/ContainerServiceProvider.php b/vendor/yansongda/artful/src/Service/ContainerServiceProvider.php new file mode 100644 index 0000000..5083298 --- /dev/null +++ b/vendor/yansongda/artful/src/Service/ContainerServiceProvider.php @@ -0,0 +1,108 @@ + LaravelContainer::class, + 'hyperf' => HyperfContainer::class, + ]; + + /** + * @throws ContainerException + */ + public function register(mixed $data = null): void + { + if ($data instanceof ContainerInterface || $data instanceof Closure) { + Artful::setContainer($data); + + return; + } + + if (Artful::hasContainer()) { + return; + } + + foreach ($this->detectApplication as $framework => $application) { + $method = $framework.'Application'; + + if (class_exists($application) && method_exists($this, $method) && $this->{$method}()) { + return; + } + } + + $this->defaultApplication(); + } + + /** + * @throws ContainerException + * @throws ContainerNotFoundException + */ + protected function laravelApplication(): bool + { + Artful::setContainer(static fn () => LaravelContainer::getInstance()); + + Artful::set(\Yansongda\Artful\Contract\ContainerInterface::class, LaravelContainer::getInstance()); + + if (!Artful::has(ContainerInterface::class)) { + Artful::set(ContainerInterface::class, LaravelContainer::getInstance()); + } + + return true; + } + + /** + * @throws ContainerException + * @throws ContainerNotFoundException + */ + protected function hyperfApplication(): bool + { + if (!HyperfContainer::hasContainer()) { + return false; + } + + Artful::setContainer(static fn () => HyperfContainer::getContainer()); + + Artful::set(\Yansongda\Artful\Contract\ContainerInterface::class, HyperfContainer::getContainer()); + + if (!Artful::has(ContainerInterface::class)) { + Artful::set(ContainerInterface::class, HyperfContainer::getContainer()); + } + + return true; + } + + /** + * @throws ContainerException + * @throws ContainerNotFoundException + */ + protected function defaultApplication(): void + { + if (!class_exists(DefaultContainer::class)) { + throw new ContainerNotFoundException('容器未找到: Init failed! Maybe you should install `hyperf/pimple` first', Exception::CONTAINER_NOT_FOUND); + } + + $container = (new DefaultContainer())(); + + Artful::setContainer($container); + + Artful::set(\Yansongda\Artful\Contract\ContainerInterface::class, $container); + } +} diff --git a/vendor/yansongda/artful/src/Service/EventServiceProvider.php b/vendor/yansongda/artful/src/Service/EventServiceProvider.php new file mode 100644 index 0000000..de89a57 --- /dev/null +++ b/vendor/yansongda/artful/src/Service/EventServiceProvider.php @@ -0,0 +1,24 @@ + false, + 'file' => null, + 'identify' => 'yansongda.artful', + 'level' => 'DEBUG', + 'type' => 'daily', + 'max_files' => 30, + 'formatter' => "%datetime% > %channel%.%level_name% > %message% %context% %extra%\n\n", + ]; + + /** + * @throws ContainerException + * @throws ServiceNotFoundException + */ + public function register(mixed $data = null): void + { + /* @var ConfigInterface $config */ + $config = Artful::get(ConfigInterface::class); + + $this->config = array_merge($this->config, $config->get('logger', [])); + + if (class_exists(Logger::class) && (true === ($this->config['enable'] ?? false))) { + $logger = new Logger($this->config['identify']); + + $logger->pushHandler($this->getDefaultHandler() + ->setFormatter($this->getDefaultFormatter())); + + Artful::set(LoggerInterface::class, $logger); + } + } + + protected function getDefaultFormatter(): LineFormatter + { + return new LineFormatter( + $this->config['formatter'], + null, + false, + true + ); + } + + protected function getDefaultHandler(): AbstractProcessingHandler + { + $file = $this->config['file'] ?? (sys_get_temp_dir().'/logs/'.$this->config['identify'].'.log'); + + return match ($this->config['type']) { + 'single' => new StreamHandler($file, $this->config['level']), + default => new RotatingFileHandler($file, $this->config['max_files'], $this->config['level']), + }; + } +} diff --git a/vendor/yansongda/pay/CHANGELOG.md b/vendor/yansongda/pay/CHANGELOG.md new file mode 100644 index 0000000..76d0cd3 --- /dev/null +++ b/vendor/yansongda/pay/CHANGELOG.md @@ -0,0 +1,667 @@ +## v3.7.18 + +### fixed + +- fix: 微信分账参数可能丢失的问题 (#1108) + +## v3.7.17 + +### fixed + +- fix: 事件缺失与不生效的问题 (#1106) + +## v3.7.16 + +### added + +- feat: 新增微信商户转账查询接口 Shortcut (#1099) +- feat: 微信商家转账支持内置异步通知参数(#1100) + +## v3.7.15 + +### added + +- feat: 新增最新版微信商户转账撤销接口(#1096) + +## v3.7.14 + +### added + +- optimize: 优化私钥证书的字符串读取方式(#1081) + +## v3.7.13 + +### added + +- feat: 新增支付宝APP同步回调验签 (#1061, #1064) + +## v3.7.12 + +### added + +- feat: 支持最新版微信商户转账 (#1058) + +## v3.7.11 + +### added + +- feat: 新增微信分账申请分账账单插件 (#1041) + +## v3.7.10 + +### fixed + +- fix: 未配置微信证书时,自动获取证书后仍然使用之前的微信配置(#1026) + +## v3.7.9 + +### added + +- feat: 新增抖音支付(#1014) + +## v3.7.8 + +### added + +- feat: 新增 v3 付款码服务商模式(#1010) + +## v3.7.7 + +### added + +- feat: 新增江苏银行e融支付(#1002) + +## v3.7.6 + +### fixed + +- fix: 微信关闭订单报解包错误的问题(#1000, #1001) + +## v3.7.5 + +### fixed + +- fix: 支付宝响应空签名时签名验证逻辑错误的问题(#998) + +### optimized + +- optimize: 优化微信 `ResponsePlugin` 插件去除不必要的返回参数(#996) + +### deprecated + +- deprecate: 微信 `StartPlugin` 改为使用 `yansongda/artful` 中的插件(#993) +- deprecate: `get_wechat_config`, `get_alipay_config`, `get_unipay_config` 方法已废弃,使用 `get_provider_config` 方法代替(#994) + +## v3.7.4 + +### optimized + +- optimize: 使用 is_file 代替字符串结尾判断(#982) + +## v3.7.3 + +### fixed + +- fix: 修复商家转账参数缺失的问题(#977) + +## v3.7.2 + +### added + +- feat: 微信V2版本支持普通红包(#973) + +### chore + +- chore: 升级 `yansongda/artful` 到最新版解决 http 配置不生效的问题(#974) + +## v3.7.1 + +### fixed + +- fix: 修复微信付款码 shortcut 支付插件执行顺序错误(#972) + +## v3.7.0 + +### added + +- feat: 支持微信 v3 版付款码支付(#969) + +### changed + +- changed: 微信付款码支付更改为 v3 版(#969) + +## v3.6.5 + +### added + +- feat: 支付宝根证书配置支持直接配置内容(#959) + +## v3.6.4 + +### fixed + +- fix: 修复支付宝授权访问令牌插件参数问题(#954) + +## v3.6.3 + +### optimized + +- optimize: 优化微信错误响应时的处理逻辑(#944) + +## v3.6.2 + +### fixed + +- fix: 修复微信 App 支付参数异常问题(#941) + +## v3.6.1 + +### chore + +- chore: 升级 `yansongda/artful` 到 v1.0.9 修复 JsonPacker 为空时 packer 错误的问题(#937) + +## v3.6.0 + +### added + +- feat: 新增 `InvalidSignException`(#903) +- feat: 新增 `DecryptException`(#906) +- feat: 新增 `decrypt_wechat_contents` 解密微信加密内容(#912) +- feat: `\Yansongda\Pay\Plugin\Wechat\Extend\Complaints\QueryDetailPlugin` 自动解密用户手机号(#912) +- feat: 支持 微信/支付宝 多版本(#918) +- feat: 增加 `HttpClientFactoryInterface` 方法用于工厂模式创建 http client(#921) +- feat: 增加银联 `条码支付综合前置平台-被扫支付` 刷卡支付插件(#922) +- feat: 增加小程序虚拟支付签名、用户签名方法(#924) +- feat: 增加微信发票插件(#927) + +### changed + +- change: 查询API方法由 `find` 改为 `query`,同时参数只支持 array(#897) +- change: cancel/close 的 API 参数只支持 array,不再支持 string(#900, #901) +- change: 微信合单支付去掉独立的 `combine_app_id`,`combine_mch_id` 配置,复用其它配置(#909) +- change: 手机网站支付快捷方式由 wap 改为 h5(#911, #915, #916, #934) +- change: `Pay` 类对外方法由所改变,如果您有自行扩展相关插件,请检查(#926) +- change(internal): 按场景对 支付宝/微信/银联 插件进行分类 && 插件代码优化(#894, #909, #913, #922) +- change(internal): 将 支付/微信/银联 shortcut 从 plugin 文件夹独立出来(#895, #904, #905, #933) +- change(internal): shortcut 完整标明各个插件,不使用 commonPlugin(#886) +- change(internal): DirectionInterface 方法由 `parse` 改为 `guide`(#896) +- change(internal): 错误代码 const 命名规则统一(#902, #903, #906, #909, #926) +- change(internal): 调整 `ProviderInterface` 的返回参数,增加了 `Rocket` 返回(#909) +- change(internal): 将 `call()` 方法重命名为 `shortcut()`(#914) +- change(internal): `mergeCommonPlugins` 不再作为 `AbstractProvider` 的方法(#918) +- change(internal): `AbstractProvider` 默认使用 `HttpClientFactoryInterface` 创建 http client(#921) +- change(internal): 调整 银联 插件文件夹结构(#923) +- change(internal): 替换为 `artful` API 请求框架(#926) +- change(internal): 调整微信代金券插件文件结构(#928) + +## v3.5.3 + +### feat + +- feat: 增加支付宝 分账关系维护/分账查询 插件(#874) + +### optimized + +- optimize: 支付宝公钥使用公共函数获取(#835) + +## v3.5.2 + +### fixed + +- fix: monolog 不存在时报错问题(#834) +- fix: `\Yansongda\Pay\Provider\AbstractProvider::call` 方法返回值类型错误问题(#834) + +## v3.5.1 + +### fixed + +- fix: `destination` 的类型约束去掉 array(#824) + +## v3.5.0 + +### deleted + +- deleted: 移除 `Yansongda\Pay\Direction\ArrayDirection` 类(#818, #819) + +## v3.4.2 + +### changed + +- change: 只支持 hyperf3.x 版本(#815) + +## v3.4.1 + +### optimized + +- optimize: 优化无签名时错误提示(#813) +- optimize: 优化预下单失败时错误提示(#814) + +## v3.4.0 + +### added + +- feat: 增加 `get_direction` 方法获取 `Direction` 对象(#803) + +### changed + +- change: `Exception::INVALID_PARSE` 更改为 `Exception::INVALID_DIRECTION`(#804) +- chore: 最低支持版本变更为 php8.0(#801) + +### optimized + +- optimize: 优化 coding style 代码规范(#802) + +## v3.3.1 + +### fixed + +- fix: 支付宝沙箱地址(#800) + +## v3.3.0 + +### added + +- feat: 支持微信 v2 版本刷卡支付(#753) +- feat: 增加申请代扣协议插件(#767) +- feat: 增加支付中签约插件(#763) +- feat: 增加只签约插件(#765) +- feat: `shortcut` 支持 `_no_common_plugins` 参数不使用通用插件(#771) +- feat: 增加委托代扣 shortcut(#773) + +### deleted + +- delete: 移除废弃的类(#752) + +### fixed + +- fix: 微信代金券 api 参数错误问题(#777) + +### refactor + +- refactor: 重构 ArrayParser 类(#754) +- refactor: coding style(#769) +- refactor: 优化现有微信v2插件代码(#772) +- refactor: 所有参数判断使用 `$payload->has()` 判断是否存在(#778) + +### chore + +- chore: 支持 psr/http-message 2.0 版(#784) + +### changed + +- change: 所有的 `Find*Plugin` 调整为 `Query*Plugin`(#756) +- change: 插件开始装载日志由 `info` 调整为 `debug`(#755) +- change: ParserInterface 签名由 `?ResponseInterface $response` 变更为 `PackerInterface $packer, ?ResponseInterface $response`(#754) +- change: \Yansongda\Pay\Plugin\Wechat\RadarSignPlugin 增加 `__construct(JsonPacker $jsonPacker, XmlPacker $xmlPacker)` 方法(#753) +- change: 所有 `Parser` 更名为 `Direction`(#770, #774) +- change: '_type' 类型统一定义为渠道id,如: 小程序id,公众号id等;增加 '_action' 为操作类型用于 shortcut(#781) +- change: 默认 container 由 `php-di/php-di` 改为 `hyperf/pimple`(#786) + +## v3.2.14 + +### fixed + +- fix: 微信投诉相关插件响应解析错误(#746) + +## v3.2.13 + +### optimized + +- optimize: 微信退款可取消 notify_url(#741) + +## v3.2.12 + +### added + +- feat: 增加获取微信平台公钥证书方法(#733) + +## v3.2.11 + +### docs + +- docs: 增加微信转账注释方便ide识别(#725) + +## v3.2.10 + +### fixed + +- fix: CallbackReceived 事件在获取到回调参数后触发(#716) + +## v3.2.9 + +### fixed + +- fix: 当配置文件出错微信解密失败后报错的问题(#698) + +## v3.2.8 + +### fixed + +- fix: 商家批次单号查询批次单时 query 参数不正确(#690) + +## v3.2.7 + +### fixed + +- fix: 微信批次单号查询批次单时 query 参数不正确(#688) + +## v3.2.6 + +### fixed + +- fix: json 中有 `&` 时解析错误(#687) + +## v3.2.5 + +### fixed + +- fix: 修复支付宝 subject 中存在 + 号回调验签不通过(#684) + +## v3.2.4 + +### added + +- feat: 银联支付(#662) + +## v3.2.3 + +### added + +- feat: 微信 Native 支付支持关联其它类型 appid(#680) + +## v3.2.2 + +### refactor + +- refactor: 优化支付宝 launch 插件代码(#678) + +### deprecated + +- deprecated: 支付宝 `RadarPlugin`, `SignPlugin` 已废弃,使用 `RadarSignPlugin` 代替(#678) +- deprecated: 微信 `SignPlugin` 已废弃,使用 `RadarSignPlugin` 代替(#678) + +## v3.2.1 + +### fixed + +- fix: `wechat_public_cert_path` 未配置时报错的问题(#674) + +### refactor + +- refactor: 优化 `wechat_public_cert_path` 配置(#674) + +## v3.2.0 + +### changed + +- change: Function 增加命名空间(#665) +- change: `get_alipay_config`,`get_wechat_config` 返回类型由 `Config` 改为 `array`(#667) +- change: 支付宝转账查询接口由老版本改为为新版本(#666) + +### deleted + +- delete: Function 中将 `get_wechat_authorization` 方法移除(#664) + +### performance + +- perf: 支付宝中支付宝根证书、应用证书序列号在常驻进程中缓存(#668) + +## v3.1.12 + +### fixed + +- fix: 微信代金券详情 url 不正确(#663) + +### refactor + +- refactor: 优化代码 (#661) + +## v3.1.11 + +### added + +- feat: 微信退款自动增加回调url(#649) + +## v3.1.10 + +### added + +- feat: 支付宝周期扣款签约接口(#644) + +## v3.1.9 + +### fixed + +- fix: 微信服务商模式预下单存在子商户appid时,invoke 时也应该为子商户 appid (#638) + +## v3.1.8 + +### fixed + +- fix: 提前读取响应数据造成数据错误的问题(#633, #634) + +## v3.1.7 + +### fixed + +- fix: 微信内网页支付供应商模式 sub_appid 非必填(#628) + +## v3.1.6 + +### fixed + +- fix: 微信注释中返回类型错误(#630) + +## v3.1.5 + +### added + +- feat: 微信服务商退款及查询退款支持自动 sub_mchid 参数(#619) + +## v3.1.4 + +### added + +- feat: 支持微信投诉API (#614) + +## v3.1.3 + +### added + +- feat: 配置文件增加第三方应用授权token的支持 (#602) + +## v3.1.2 + +### fixed + +- fix: alipay 中 event dispatch provider 是 wechat 的问题 #595 + +## v3.1.1 + +### fixed + +- fix: 设置 container,强制更新 config 后 container 不是设置的 container 的问题 #591 + +## v3.1.0 + +兼容 v3.0 版本,推荐升级(#579) + +### dependency + +- delete: 移除 `php-di/php-di` 依赖。如果您使用的框架非 `hyperf`, `laravel` 或 没有指定 `ContainerInterface`,仍需手动安装 `composer require php-di/php-di` +- delete: 移除 `guzzlehttp/guzzle` 依赖。如果没有指定 `\Yansongda\Pay\Contract\HttpClientInterface` 仍需手动安装 `composer require guzzlehttp/guzzle` +- upgrade: 升级 `yansongda/supports` 到 `~v3.2.0` +- upgrade: 升级 `php` 最低版本到 `7.4.0` + +### fixed + +- fix: 解决 php8.1 下 deprecated 的提示 + +### kernel + +- refactor: 自动识别 `hyperf`, `laravel` 框架,使用相应的 `container` 减少内存占用 +- refactor: 完全支持 `psr11`,可手动传入 `ContainerInterface` 使用 +- changed: `Pay::config(array $config = [], $container = null)` 方法第二个参数增加为 $container,可手动传入 `ContainerInterface`/`Closure`。注意 `Closure` 需最终返回一个 `ContainerInterface` 的实例。 + +## v3.0.27 + +### fixed + +- fix: 添加分账接受人姓名加密字段错误 (#566) + +## v3.0.26 + +### added + +- feat: 支持 psr/log 2.x and 3.x (#562) + +## v3.0.25 + +### fixed + +- fix: 支持分账传递姓名 (#559) + +## v3.0.24 + +### added + +- feat: 支持使用小程序等其他类型转账 (#552) + +## v3.0.23 + +### fixed + +- fix: 未设置微信公钥证书时,加密不生效的问题 (#549) + +## v3.0.22 + +### fixed + +- fix: 微信分账传递姓名时未加密的问题 (#547) + +## v3.0.21 + +### added + +- feat: 微信转账快捷方式与加密方式支持 (#542) + +## v3.0.20 + +### updated + +- chore: 完善支付宝响应错误时的异常信息 (#530) + +## v3.0.19 + +### fixed + +- fix: 支付宝 system.oauth.token 请求参数错误 (#528) + +## v3.0.18 + +### added + +- feat: 电商收付通的退款使用 _type 增加多类型 appid (#518) + +## v3.0.17 + +### added + +- feat: 增加电商收付通的退款相关插件 (#513) + +## v3.0.16 + +### fixed + +- fixed: app 支付调起签名问题 (#1389476) + +## v3.0.15 + +### fixed + +- fixed: 下载对账单时响应解析 (#df27f95) + +## v3.0.14 + +### fixed + +- fixed: app 支付调起签名中参数大小写问题 (#7916fdd) + +## v3.0.13 + +### fixed + +- fixed: app 支付调起签名中时间戳参数大小写问题 (#510) + +## v3.0.12 + +### fixed + +- fixed: 微信小程序支付供应商模式 sub_appid 非必填 (#509) + +## v3.0.11 + +### added + +- feat: 微信 h5 支付支持关联 mini_app_id (#506) + +## v3.0.10 + +### added + +- feat: 服务商批量转账到零钱 (#503) + +## v3.0.9 + +### added + +- feat: 支持直连商户批量转账到零钱 (#501) + +## v3.0.8 + +### fixed + +- fix: 设置 bcscale 时支付宝根证书计算错误的问题 (#492, #494) + +## v3.0.7 + +### fixed + +- fix: 支付宝 wap/web 支付 get 方法时url拼接问题 (#488) + +## v3.0.6 + +### optimized + +- chore: 优化服务商模式小程序下单场景 (#487) + +## v3.0.5 + +### fixed + +- fix: 服务商模式交易查询 (#483) + +## v3.0.4 + +### added + +- feat: 支持服务商模式 (#479) +- feat: 支持微信服务商分账功能 (#480) + +## v3.0.3 + +### added + +- feat: 公钥证书增加 cer 后缀支持 (#d22e29a) + +## v3.0.2 + +### fixed + +- 修复微信支付关闭订单时报错问题 (#475) + +## v3.0.1 + +### fixed + +- 修复微信支付关闭订单时报错问题 (#475) diff --git a/vendor/yansongda/pay/CODE_OF_CONDUCT.md b/vendor/yansongda/pay/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..355aea9 --- /dev/null +++ b/vendor/yansongda/pay/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +me@yansongda.cn. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/vendor/yansongda/pay/LICENSE b/vendor/yansongda/pay/LICENSE new file mode 100644 index 0000000..c6dbf1f --- /dev/null +++ b/vendor/yansongda/pay/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2017 yansongda + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/yansongda/pay/README.md b/vendor/yansongda/pay/README.md new file mode 100644 index 0000000..b50dd80 --- /dev/null +++ b/vendor/yansongda/pay/README.md @@ -0,0 +1,462 @@ +

+ Logo +

+ +

+ + scrutinizer + Tester Status + Code Coverage Status + Coding Style Status + Stable Version + Total Downloads + License +

+ +## 前言 + +v3 版与 v2 版在底层有很大的不同,基础架构做了重新的设计,更易扩展,使用起来更方便。 + +开发了多次支付宝与微信支付后,很自然产生一种反感,惰性又来了,想在网上找相关的轮子,可是一直没有找到一款自己觉得逞心如意的,要么使用起来太难理解,要么文件结构太杂乱,只有自己撸起袖子干了。 + +欢迎 Star,欢迎 PR! + +hyperf 扩展包请 [传送至这里](https://github.com/yansongda/hyperf-pay) + +laravel 扩展包请 [传送至这里](https://github.com/yansongda/laravel-pay) + +yii 扩展包请 [传送至这里](https://github.com/guanguans/yii-pay) + +## 特点 + +- 多租户支持 +- Swoole 支持 +- 灵活的插件机制 +- 丰富的事件系统 +- 命名不那么乱七八糟 +- 隐藏开发者不需要关注的细节 +- 根据支付宝、微信最新 API 开发而成 +- 高度抽象的类,免去各种拼json与xml的痛苦 +- 文件结构清晰易理解,可以随心所欲添加本项目中没有的支付网关 +- 方法使用更优雅,不必再去研究那些奇怪的的方法名或者类名是做啥用的 +- 内置自动获取微信公共证书方法,再也不用再费劲去考虑第一次获取证书的的问题了 +- 符合 PSR2、PSR3、PSR4、PSR7、PSR11、PSR14、PSR18 等各项标准,你可以各种方便的与你的框架集成 + +## 版本计划 + +[https://pay.yansongda.cn/docs/v3/overview/planning](https://pay.yansongda.cn/docs/v3/overview/planning) + +## 详细文档 + +[https://pay.yansongda.cn](https://pay.yansongda.cn) + +## 支持的支付方法 + +yansongda/pay 100% 兼容 支付宝/微信/银联 所有功能(包括服务商功能),只需通过「插件机制」引入即可。 + +同时,SDK 直接支持内置了以下插件,详情请查阅文档。 + +### 支付宝 + +- 电脑支付 +- 手机网站支付 +- APP 支付 +- 刷卡支付 +- 扫码支付 +- 账户转账 +- 小程序支付 +- ... + +### 微信 + +- 公众号支付 +- 小程序支付 +- H5 支付 +- 扫码支付 +- APP 支付 +- 刷卡支付 +- ... + +### 抖音 + +- 小程序支付 +- ... + +### 银联 + +- 手机网站支付 +- 电脑网站支付 +- 刷卡支付 +- 扫码支付 +- ... +- +### 江苏银行(e融支付) + +- 聚合扫码支付(微信,支付宝,银联,e融) +- ... + +## 安装 +```shell +composer require yansongda/pay:~3.7.0 -vvv +``` + +## 深情一撇 + +### 支付宝 +```php + [ + 'default' => [ + // 必填-支付宝分配的 app_id + 'app_id' => '2016082000295641', + // 必填-应用私钥 字符串或路径 + 'app_secret_cert' => '89iZ2iC16H6/6a3YcP+hDZUjiNGQx9cuwi9eJyykvcwhD...', + // 必填-应用公钥证书 路径 + 'app_public_cert_path' => '/Users/yansongda/pay/cert/appCertPublicKey_2016082000295641.crt', + // 必填-支付宝公钥证书 路径 + 'alipay_public_cert_path' => '/Users/yansongda/pay/cert/alipayCertPublicKey_RSA2.crt', + // 必填-支付宝根证书 路径 + 'alipay_root_cert_path' => '/Users/yansongda/pay/cert/alipayRootCert.crt', + 'return_url' => 'https://yansongda.cn/alipay/return', + 'notify_url' => 'https://yansongda.cn/alipay/notify', + // 选填-第三方应用授权token + 'app_auth_token' => '', + // 选填-服务商模式下的服务商 id,当 mode 为 Pay::MODE_SERVICE 时使用该参数 + 'service_provider_id' => '', + // 选填-默认为正常模式。可选为: MODE_NORMAL, MODE_SANDBOX, MODE_SERVICE + 'mode' => Pay::MODE_NORMAL, + ], + ], + 'logger' => [ // optional + 'enable' => false, + 'file' => './logs/alipay.log', + 'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug + 'type' => 'single', // optional, 可选 daily. + 'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天 + ], + 'http' => [ // optional + 'timeout' => 5.0, + 'connect_timeout' => 5.0, + // 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html) + ], + ]; + + public function web() + { + Pay::config($this->config); + + $result = Pay::alipay()->web([ + 'out_trade_no' => ''.time(), + 'total_amount' => '0.01', + 'subject' => 'yansongda 测试 - 1', + ]); + + return $result; + } + + public function returnCallback() + { + Pay::config($this->config); + + $data = Pay::alipay()->callback(); // 是的,验签就这么简单! + + // 订单号:$data->out_trade_no + // 支付宝交易号:$data->trade_no + // 订单总金额:$data->total_amount + } + + public function notifyCallback() + { + Pay::config($this->config); + + try{ + $data = Pay::alipay()->callback(); // 是的,验签就这么简单! + + // 请自行对 trade_status 进行判断及其它逻辑进行判断,在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。 + // 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号; + // 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额); + // 3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email); + // 4、验证app_id是否为该商户本身。 + // 5、其它业务逻辑情况 + } catch (\Throwable $e) { + dd($e); + } + + return Pay::alipay()->success(); + } +} +``` + +### 微信 +```php + [ + 'default' => [ + // 必填-商户号 + 'mch_id' => '', + // 选填-v2商户私钥 + 'mch_secret_key_v2' => '', + // 必填-v3商户秘钥 + 'mch_secret_key' => '', + // 必填-商户私钥 字符串或路径 + 'mch_secret_cert' => '', + // 必填-商户公钥证书路径 + 'mch_public_cert_path' => '', + // 必填 + 'notify_url' => 'https://yansongda.cn/wechat/notify', + // 选填-公众号 的 app_id + 'mp_app_id' => '', + // 选填-小程序 的 app_id + 'mini_app_id' => '', + // 选填-app 的 app_id + 'app_id' => '', + // 选填-服务商模式下,子公众号 的 app_id + 'sub_mp_app_id' => '', + // 选填-服务商模式下,子 app 的 app_id + 'sub_app_id' => '', + // 选填-服务商模式下,子小程序 的 app_id + 'sub_mini_app_id' => '', + // 选填-服务商模式下,子商户id + 'sub_mch_id' => '', + // 选填-微信平台公钥证书路径, optional,强烈建议 php-fpm 模式下配置此参数 + 'wechat_public_cert_path' => [ + '45F59D4DABF31918AFCEC556D5D2C6E376675D57' => __DIR__.'/Cert/wechatpay_45F***D57.pem', + ], + // 选填-默认为正常模式。可选为: MODE_NORMAL, MODE_SERVICE + 'mode' => Pay::MODE_NORMAL, + ] + ], + 'logger' => [ // optional + 'enable' => false, + 'file' => './logs/wechat.log', + 'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug + 'type' => 'single', // optional, 可选 daily. + 'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天 + ], + 'http' => [ // optional + 'timeout' => 5.0, + 'connect_timeout' => 5.0, + // 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html) + ], + ]; + + public function index() + { + Pay::config($this->config); + + $order = [ + 'out_trade_no' => time().'', + 'description' => 'subject-测试', + 'amount' => [ + 'total' => 1, + ], + 'payer' => [ + 'openid' => 'onkVf1FjWS5SBxxxxxxxx', + ], + ]; + + $pay = Pay::wechat()->mp($order); + + // $pay->appId + // $pay->timeStamp + // $pay->nonceStr + // $pay->package + // $pay->signType + } + + public function callback() + { + Pay::config($this->config); + + try{ + $data = Pay::wechat()->callback(); // 是的,验签就这么简单! + } catch (\Throwable $e) { + dd($e); + } + + return Pay::wechat()->success(); + } +} +``` + +### 抖音 +```php + [ + 'default' => [ + // 选填-商户号 + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 产品管理 --> 商户号 + 'mch_id' => '73744242495132490630', + // 必填-支付 Token,用于支付回调签名 + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> Token(令牌) + 'mch_secret_token' => 'douyin_mini_token', + // 必填-支付 SALT,用于支付签名 + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> SALT + 'mch_secret_salt' => 'oDxWDBr4U7FAAQ8hnGDm29i4A6pbTMDKme4WLLvA', + // 必填-小程序 app_id + // 抖音开放平台 --> 应用详情 --> 支付信息 --> 支付设置 --> 小程序appid + 'mini_app_id' => 'tt226e54d3bd581bf801', + // 选填-抖音开放平台服务商id + 'thirdparty_id' => '', + // 选填-抖音支付回调地址 + 'notify_url' => 'https://yansongda.cn/douyin/notify', + ], + ], + 'logger' => [ // optional + 'enable' => false, + 'file' => './logs/alipay.log', + 'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug + 'type' => 'single', // optional, 可选 daily. + 'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天 + ], + 'http' => [ // optional + 'timeout' => 5.0, + 'connect_timeout' => 5.0, + // 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html) + ], + ]; + + public function pay() + { + Pay::config($this->config); + + $result = Pay::douyin()->mini([ + 'out_order_no' => date('YmdHis').mt_rand(1000, 9999), + 'total_amount' => 1, + 'subject' => '闫嵩达 - test - subject - 01', + 'body' => '闫嵩达 - test - body - 01', + 'valid_time' => 600, + 'expand_order_info' => json_encode([ + 'original_delivery_fee' => 15, + 'actual_delivery_fee' => 10 + ]) + ]); + + return $result; + } + + public function callback() + { + Pay::config($this->config); + + try{ + $data = Pay::douyin()->callback(); // 是的,验签就这么简单! + } catch (\Throwable $e) { + dd($e) + } + + return Pay::douyin()->success(); + } +} +``` + +### 江苏银行(e融支付) +```php + [ + 'default' => [ + // 服务代码 + 'svr_code' => '', + // 必填-合作商ID + 'partner_id' => '', + // 必填-公私钥对编号 + 'public_key_code' => '00', + // 必填-商户私钥(加密签名) + 'mch_secret_cert_path' => '', + // 必填-商户公钥证书路径(提供江苏银行进行验证签名用) + 'mch_public_cert_path' => '', + // 必填-江苏银行的公钥(用于解密江苏银行返回的数据) + 'jsb_public_cert_path' => '', + //支付通知地址 + 'notify_url' => '', + // 选填-默认为正常模式。可选为: MODE_NORMAL:正式环境, MODE_SANDBOX:测试环境 + 'mode' => Pay::MODE_NORMAL, + ] + ], + 'logger' => [ // optional + 'enable' => false, + 'file' => './logs/epay.log', + 'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug + 'type' => 'single', // optional, 可选 daily. + 'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天 + ], + 'http' => [ // optional + 'timeout' => 5.0, + 'connect_timeout' => 5.0, + // 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html) + ], + ]; + + public function index() + { + Pay::config($this->config); + + $order = [ + 'outTradeNo' => time().'', + 'proInfo' => 'subject-测试', + 'totalFee'=> 1, + ]; + + $pay = Pay::jsb()->scan($order); + } + + public function notifyCallback() + { + Pay::config($this->config); + + try{ + $data = Pay::jsb()->callback(); // 是的,验签就这么简单! + } catch (\Throwable $e) { + dd($e); + } + + return Pay::jsb()->success(); + } +} +``` + +## 代码贡献 + +由于测试及使用环境的限制,本项目中只开发了「支付宝」、「微信支付」、「抖音支付」、「银联」、「江苏银行」的相关支付网关。 + +如果您有其它支付网关的需求,或者发现本项目中需要改进的代码,**_欢迎 Fork 并提交 PR!_** + +## 赏一杯咖啡吧 + +![pay](https://cdn.jsdelivr.net/gh/yansongda/pay/web/public/images/pay.jpg) + +## LICENSE + +MIT diff --git a/vendor/yansongda/pay/SECURITY.md b/vendor/yansongda/pay/SECURITY.md new file mode 100644 index 0000000..d175a1e --- /dev/null +++ b/vendor/yansongda/pay/SECURITY.md @@ -0,0 +1,22 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | PHP | Branch | Status | +|:--------:|:--------:|:----------------------------------------------:|:------------:| +| v3.5 | `>= 8.0` | master | 积极开发中 | +| v3.4 | `>= 8.0` | master | EOL,停止维护 | +| v3.0-3.3 | `>= 7.3` | master | EOL,停止维护 | +| v2.x | `>= 7.0` | [v2](https://github.com/yansongda/pay/tree/v2) | 安全支持,不做新功能开发 | +| v1.x | `>= 5.6` | [v1](https://github.com/yansongda/pay/tree/v1) | EOL,停止维护 | + +## Reporting a Vulnerability + +Use this section to tell people how to report a vulnerability. + +Tell them where to go, how often they can expect to get an update on a +reported vulnerability, what to expect if the vulnerability is accepted or +declined, etc. diff --git a/vendor/yansongda/pay/composer.json b/vendor/yansongda/pay/composer.json new file mode 100644 index 0000000..114a3aa --- /dev/null +++ b/vendor/yansongda/pay/composer.json @@ -0,0 +1,63 @@ +{ + "name": "yansongda/pay", + "description": "可能是我用过的最优雅的 Alipay 和 WeChat 的支付 SDK 扩展包了", + "keywords": ["alipay", "wechat", "pay"], + "type": "library", + "license": "MIT", + "support": { + "issues": "https://github.com/yansongda/pay/issues", + "source": "https://github.com/yansongda/pay", + "homepage": "https://pay.yansongda.cn" + }, + "authors": [ + { + "name": "yansongda", + "email": "me@yansongda.cn" + } + ], + "require": { + "php": ">=8.0", + "ext-openssl": "*", + "ext-simplexml":"*", + "ext-libxml": "*", + "ext-json": "*", + "ext-bcmath": "*", + "yansongda/artful": "~1.1.3", + "yansongda/supports": "~4.0.10" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "mockery/mockery": "^1.4", + "friendsofphp/php-cs-fixer": "^3.44", + "phpstan/phpstan": "^1.0.0 || ^2.0.0", + "monolog/monolog": "^2.2", + "symfony/var-dumper": "^5.1", + "symfony/http-foundation": "^5.2.0", + "symfony/event-dispatcher": "^5.2.0", + "symfony/psr-http-message-bridge": "^2.1", + "hyperf/pimple": "^2.2", + "guzzlehttp/guzzle": "^7.0", + "jetbrains/phpstorm-attributes": "^1.1" + }, + "conflict": { + "hyperf/framework": "<3.0" + }, + "autoload": { + "psr-4": { + "Yansongda\\Pay\\": "src" + }, + "files": [ + "src/Functions.php" + ] + }, + "autoload-dev": { + "psr-4": { + "Yansongda\\Pay\\Tests\\": "tests" + } + }, + "scripts": { + "test": "./vendor/bin/phpunit -c phpunit.xml --colors=always", + "cs-fix": "php-cs-fixer fix --dry-run --diff 1>&2", + "analyse": "phpstan analyse --memory-limit 300M -l 5 -c phpstan.neon ./src" + } +} diff --git a/vendor/yansongda/pay/phpstan.neon b/vendor/yansongda/pay/phpstan.neon new file mode 100644 index 0000000..9667f4d --- /dev/null +++ b/vendor/yansongda/pay/phpstan.neon @@ -0,0 +1,7 @@ +parameters: + reportUnmatchedIgnoredErrors: false + excludePaths: + ignoreErrors: + - '#.* Illuminate\\Container\\Container.*#' + - '#.* Hyperf\\Utils\\ApplicationContext.*#' + - '#.* think\\Container.*#' diff --git a/vendor/yansongda/pay/src/Contract/ProviderInterface.php b/vendor/yansongda/pay/src/Contract/ProviderInterface.php new file mode 100644 index 0000000..018d715 --- /dev/null +++ b/vendor/yansongda/pay/src/Contract/ProviderInterface.php @@ -0,0 +1,28 @@ +provider = $provider; + $this->contents = $contents; + $this->params = $params; + + parent::__construct($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Event/HttpEnd.php b/vendor/yansongda/pay/src/Event/HttpEnd.php new file mode 100755 index 0000000..951788a --- /dev/null +++ b/vendor/yansongda/pay/src/Event/HttpEnd.php @@ -0,0 +1,9 @@ +provider = $provider; + $this->name = $name; + $this->params = $params; + + parent::__construct($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Event/PayEnd.php b/vendor/yansongda/pay/src/Event/PayEnd.php new file mode 100755 index 0000000..682a2c6 --- /dev/null +++ b/vendor/yansongda/pay/src/Event/PayEnd.php @@ -0,0 +1,9 @@ +plugins = $plugins; + $this->params = $params; + + parent::__construct($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Exception/DecryptException.php b/vendor/yansongda/pay/src/Exception/DecryptException.php new file mode 100644 index 0000000..f2a869d --- /dev/null +++ b/vendor/yansongda/pay/src/Exception/DecryptException.php @@ -0,0 +1,19 @@ +extra = $extra; + + parent::__construct($message, $code, $previous); + } +} diff --git a/vendor/yansongda/pay/src/Exception/InvalidSignException.php b/vendor/yansongda/pay/src/Exception/InvalidSignException.php new file mode 100644 index 0000000..d75b7ca --- /dev/null +++ b/vendor/yansongda/pay/src/Exception/InvalidSignException.php @@ -0,0 +1,23 @@ +callback = $extra; + + parent::__construct($message, $code, $extra, $previous); + } +} diff --git a/vendor/yansongda/pay/src/Functions.php b/vendor/yansongda/pay/src/Functions.php new file mode 100644 index 0000000..b9a435d --- /dev/null +++ b/vendor/yansongda/pay/src/Functions.php @@ -0,0 +1,656 @@ + $payload?->get('_service_url') ?? $payload?->get('_url') ?? null, + Pay::MODE_SANDBOX => $payload?->get('_sandbox_url') ?? $payload?->get('_url') ?? null, + default => $payload?->get('_url') ?? null, + }; +} + +/** + * @throws ContainerException + * @throws ServiceNotFoundException + */ +function get_provider_config(string $provider, array $params = []): array +{ + /** @var ConfigInterface $config */ + $config = Pay::get(ConfigInterface::class); + + return $config->get($provider, [])[get_tenant($params)] ?? []; +} + +/** + * @throws ContainerException + * @throws ServiceNotFoundException + */ +#[Deprecated(reason: '自 v3.7.5 开始废弃', replacement: 'get_provider_config')] +function get_alipay_config(array $params = []): array +{ + $alipay = Pay::get(ConfigInterface::class)->get('alipay'); + + return $alipay[get_tenant($params)] ?? []; +} + +function get_alipay_url(array $config, ?Collection $payload): string +{ + $url = get_radar_url($config, $payload) ?? ''; + + if (str_starts_with($url, 'http')) { + return $url; + } + + return Alipay::URL[$config['mode'] ?? Pay::MODE_NORMAL]; +} + +/** + * @throws InvalidConfigException + * @throws InvalidSignException + */ +function verify_alipay_sign(array $config, string $contents, string $sign): void +{ + if (empty($sign)) { + throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 验证支付宝签名失败-支付宝签名为空', func_get_args()); + } + + $public = $config['alipay_public_cert_path'] ?? null; + + if (empty($public)) { + throw new InvalidConfigException(Exception::CONFIG_ALIPAY_INVALID, '配置异常: 缺少支付宝配置 -- [alipay_public_cert_path]'); + } + + $result = 1 === openssl_verify( + $contents, + base64_decode($sign), + get_public_cert($public), + OPENSSL_ALGO_SHA256 + ); + + if (!$result) { + throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证支付宝签名失败', func_get_args()); + } +} + +/** + * @throws ContainerException + * @throws ServiceNotFoundException + */ +#[Deprecated(reason: '自 v3.7.5 开始废弃', replacement: 'get_provider_config')] +function get_wechat_config(array $params = []): array +{ + $wechat = Pay::get(ConfigInterface::class)->get('wechat'); + + return $wechat[get_tenant($params)] ?? []; +} + +function get_wechat_method(?Collection $payload): string +{ + return get_radar_method($payload) ?? 'POST'; +} + +/** + * @throws InvalidParamsException + */ +function get_wechat_url(array $config, ?Collection $payload): string +{ + $url = get_radar_url($config, $payload); + + if (empty($url)) { + throw new InvalidParamsException(Exception::PARAMS_WECHAT_URL_MISSING, '参数异常: 微信 `_url` 或 `_service_url` 参数缺失:你可能用错插件顺序,应该先使用 `业务插件`'); + } + + if (str_starts_with($url, 'http')) { + return $url; + } + + return Wechat::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url; +} + +/** + * @throws InvalidParamsException + */ +function get_wechat_body(?Collection $payload): mixed +{ + $body = get_radar_body($payload); + + if (is_null($body)) { + throw new InvalidParamsException(Exception::PARAMS_WECHAT_BODY_MISSING, '参数异常: 微信 `_body` 参数缺失:你可能用错插件顺序,应该先使用 `AddPayloadBodyPlugin`'); + } + + return $body; +} + +function get_wechat_type_key(array $params): string +{ + $key = ($params['_type'] ?? 'mp').'_app_id'; + + if ('app_app_id' === $key) { + $key = 'app_id'; + } + + return $key; +} + +/** + * @throws InvalidConfigException + */ +function get_wechat_sign(array $config, string $contents): string +{ + $privateKey = $config['mch_secret_cert'] ?? null; + + if (empty($privateKey)) { + throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mch_secret_cert]'); + } + + $privateKey = get_private_cert($privateKey); + + openssl_sign($contents, $sign, $privateKey, 'sha256WithRSAEncryption'); + + return base64_encode($sign); +} + +/** + * @throws InvalidConfigException + */ +function get_wechat_sign_v2(array $config, array $payload, bool $upper = true): string +{ + $key = $config['mch_secret_key_v2'] ?? null; + + if (empty($key)) { + throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mch_secret_key_v2]'); + } + + ksort($payload); + + $buff = ''; + + foreach ($payload as $k => $v) { + $buff .= ('sign' != $k && '' != $v && !is_array($v)) ? $k.'='.$v.'&' : ''; + } + + $sign = md5($buff.'key='.$key); + + return $upper ? strtoupper($sign) : $sign; +} + +/** + * @throws ContainerException + * @throws DecryptException + * @throws InvalidConfigException + * @throws InvalidParamsException + * @throws InvalidSignException + * @throws ServiceNotFoundException + */ +function verify_wechat_sign(ResponseInterface|ServerRequestInterface $message, array $params): void +{ + if ($message instanceof ServerRequestInterface && 'localhost' === $message->getUri()->getHost()) { + return; + } + + $wechatSerial = $message->getHeaderLine('Wechatpay-Serial'); + $timestamp = $message->getHeaderLine('Wechatpay-Timestamp'); + $random = $message->getHeaderLine('Wechatpay-Nonce'); + $sign = $message->getHeaderLine('Wechatpay-Signature'); + $body = (string) $message->getBody(); + + $content = $timestamp."\n".$random."\n".$body."\n"; + $public = get_provider_config('wechat', $params)['wechat_public_cert_path'][$wechatSerial] ?? null; + + if (empty($sign)) { + throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 微信签名为空', ['headers' => $message->getHeaders(), 'body' => $body]); + } + + $public = get_public_cert( + empty($public) ? reload_wechat_public_certs($params, $wechatSerial) : $public + ); + + $result = 1 === openssl_verify( + $content, + base64_decode($sign), + $public, + 'sha256WithRSAEncryption' + ); + + if (!$result) { + throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证微信签名失败', ['headers' => $message->getHeaders(), 'body' => $body]); + } +} + +/** + * @throws InvalidConfigException + * @throws InvalidSignException + */ +function verify_wechat_sign_v2(array $config, array $destination): void +{ + $sign = $destination['sign'] ?? null; + + if (empty($sign)) { + throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 微信签名为空', $destination); + } + + $key = $config['mch_secret_key_v2'] ?? null; + + if (empty($key)) { + throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mch_secret_key_v2]'); + } + + if (get_wechat_sign_v2($config, $destination) !== $sign) { + throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证微信签名失败', $destination); + } +} + +function encrypt_wechat_contents(string $contents, string $publicKey): ?string +{ + if (openssl_public_encrypt($contents, $encrypted, get_public_cert($publicKey), OPENSSL_PKCS1_OAEP_PADDING)) { + return base64_encode($encrypted); + } + + return null; +} + +function decrypt_wechat_contents(string $encrypted, array $config): ?string +{ + if (openssl_private_decrypt(base64_decode($encrypted), $decrypted, get_private_cert($config['mch_secret_cert'] ?? ''), OPENSSL_PKCS1_OAEP_PADDING)) { + return $decrypted; + } + + return null; +} + +/** + * @throws ContainerException + * @throws DecryptException + * @throws InvalidConfigException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ +function reload_wechat_public_certs(array $params, ?string $serialNo = null): string +{ + $data = Pay::wechat()->pay( + [StartPlugin::class, WechatPublicCertsPlugin::class, AddPayloadBodyPlugin::class, AddPayloadSignaturePlugin::class, AddRadarPlugin::class, ResponsePlugin::class, ParserPlugin::class], + $params + )->get('data', []); + + $wechatConfig = get_provider_config('wechat', $params); + + foreach ($data as $item) { + $certs[$item['serial_no']] = decrypt_wechat_resource($item['encrypt_certificate'], $wechatConfig)['ciphertext'] ?? ''; + } + + Pay::get(ConfigInterface::class)->set( + 'wechat.'.get_tenant($params).'.wechat_public_cert_path', + ((array) ($wechatConfig['wechat_public_cert_path'] ?? [])) + ($certs ?? []), + ); + + if (!is_null($serialNo) && empty($certs[$serialNo])) { + throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 获取微信 wechat_public_cert_path 配置失败'); + } + + return $certs[$serialNo] ?? ''; +} + +/** + * @throws ContainerException + * @throws DecryptException + * @throws InvalidConfigException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ +function get_wechat_public_certs(array $params = [], ?string $path = null): void +{ + reload_wechat_public_certs($params); + + $config = get_provider_config('wechat', $params); + + if (empty($path)) { + var_dump($config['wechat_public_cert_path']); + + return; + } + + foreach ($config['wechat_public_cert_path'] as $serialNo => $cert) { + file_put_contents($path.'/'.$serialNo.'.crt', $cert); + } +} + +/** + * @throws InvalidConfigException + * @throws DecryptException + */ +function decrypt_wechat_resource(array $resource, array $config): array +{ + $ciphertext = base64_decode($resource['ciphertext'] ?? ''); + $secret = $config['mch_secret_key'] ?? null; + + if (strlen($ciphertext) <= Wechat::AUTH_TAG_LENGTH_BYTE) { + throw new DecryptException(Exception::DECRYPT_WECHAT_CIPHERTEXT_PARAMS_INVALID, '加解密异常: ciphertext 位数过短'); + } + + if (is_null($secret) || Wechat::MCH_SECRET_KEY_LENGTH_BYTE != strlen($secret)) { + throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mch_secret_key]'); + } + + $resource['ciphertext'] = match ($resource['algorithm'] ?? '') { + 'AEAD_AES_256_GCM' => decrypt_wechat_resource_aes_256_gcm($ciphertext, $secret, $resource['nonce'] ?? '', $resource['associated_data'] ?? ''), + default => throw new DecryptException(Exception::DECRYPT_WECHAT_DECRYPTED_METHOD_INVALID, '加解密异常: algorithm 不支持'), + }; + + return $resource; +} + +/** + * @throws DecryptException + */ +function decrypt_wechat_resource_aes_256_gcm(string $ciphertext, string $secret, string $nonce, string $associatedData): array|string +{ + $decrypted = openssl_decrypt( + substr($ciphertext, 0, -Wechat::AUTH_TAG_LENGTH_BYTE), + 'aes-256-gcm', + $secret, + OPENSSL_RAW_DATA, + $nonce, + substr($ciphertext, -Wechat::AUTH_TAG_LENGTH_BYTE), + $associatedData + ); + + if (false === $decrypted) { + throw new DecryptException(Exception::DECRYPT_WECHAT_ENCRYPTED_DATA_INVALID, '加解密异常: 解密失败,请检查微信 mch_secret_key 是否正确'); + } + + if ('certificate' !== $associatedData) { + $decrypted = json_decode($decrypted, true); + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new DecryptException(Exception::DECRYPT_WECHAT_ENCRYPTED_DATA_INVALID, '加解密异常: 待解密数据非正常数据'); + } + } + + return $decrypted; +} + +/** + * @throws ContainerException + * @throws DecryptException + * @throws InvalidConfigException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ +function get_wechat_serial_no(array $params): string +{ + if (!empty($params['_serial_no'])) { + return $params['_serial_no']; + } + + $config = get_provider_config('wechat', $params); + + if (empty($config['wechat_public_cert_path'])) { + reload_wechat_public_certs($params); + + $config = get_provider_config('wechat', $params); + } + + mt_srand(); + + return strval(array_rand($config['wechat_public_cert_path'])); +} + +/** + * @throws InvalidParamsException + */ +function get_wechat_public_key(array $config, string $serialNo): string +{ + $publicKey = $config['wechat_public_cert_path'][$serialNo] ?? null; + + if (empty($publicKey)) { + throw new InvalidParamsException(Exception::PARAMS_WECHAT_SERIAL_NOT_FOUND, '参数异常: 微信公钥序列号未找到 - '.$serialNo); + } + + return $publicKey; +} + +/** + * @throws InvalidConfigException + */ +function get_wechat_miniprogram_pay_sign(array $config, string $url, string $payload): string +{ + if (empty($config['mini_app_key_virtual_pay'])) { + throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mini_app_key_virtual_pay]'); + } + + return hash_hmac('sha256', $url.'&'.$payload, $config['mini_app_key_virtual_pay']); +} + +function get_wechat_miniprogram_user_sign(string $sessionKey, string $payload): string +{ + return hash_hmac('sha256', $payload, $sessionKey); +} + +/** + * @throws ContainerException + * @throws ServiceNotFoundException + */ +#[Deprecated(reason: '自 v3.7.5 开始废弃', replacement: 'get_provider_config')] +function get_unipay_config(array $params = []): array +{ + $unipay = Pay::get(ConfigInterface::class)->get('unipay'); + + return $unipay[get_tenant($params)] ?? []; +} + +/** + * @throws InvalidConfigException + * @throws InvalidSignException + */ +function verify_unipay_sign(array $config, string $contents, string $sign, ?string $signPublicKeyCert = null): void +{ + if (empty($sign)) { + throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 银联签名为空', func_get_args()); + } + + if (empty($signPublicKeyCert) && empty($public = $config['unipay_public_cert_path'] ?? null)) { + throw new InvalidConfigException(Exception::CONFIG_UNIPAY_INVALID, '配置异常: 缺少银联配置 -- [unipay_public_cert_path]'); + } + + $result = 1 === openssl_verify( + hash('sha256', $contents), + base64_decode($sign), + get_public_cert($signPublicKeyCert ?? $public ?? ''), + 'sha256' + ); + + if (!$result) { + throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证银联签名失败', func_get_args()); + } +} + +/** + * @throws InvalidParamsException + */ +function get_unipay_url(array $config, ?Collection $payload): string +{ + $url = get_radar_url($config, $payload); + + if (empty($url)) { + throw new InvalidParamsException(Exception::PARAMS_UNIPAY_URL_MISSING, '参数异常: 银联 `_url` 参数缺失:你可能用错插件顺序,应该先使用 `业务插件`'); + } + + if (str_starts_with($url, 'http')) { + return $url; + } + + return Unipay::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url; +} + +/** + * @throws InvalidParamsException + */ +function get_unipay_body(?Collection $payload): string +{ + $body = get_radar_body($payload); + + if (is_null($body)) { + throw new InvalidParamsException(Exception::PARAMS_UNIPAY_BODY_MISSING, '参数异常: 银联 `_body` 参数缺失:你可能用错插件顺序,应该先使用 `AddPayloadBodyPlugin`'); + } + + return $body; +} + +/** + * @throws InvalidConfigException + */ +function get_unipay_sign_qra(array $config, array $payload): string +{ + $key = $config['mch_secret_key'] ?? null; + + if (empty($key)) { + throw new InvalidConfigException(Exception::CONFIG_UNIPAY_INVALID, '配置异常: 缺少银联配置 -- [mch_secret_key]'); + } + + ksort($payload); + + $buff = ''; + + foreach ($payload as $k => $v) { + $buff .= ('sign' != $k && '' != $v && !is_array($v)) ? $k.'='.$v.'&' : ''; + } + + return strtoupper(md5($buff.'key='.$key)); +} + +/** + * @throws InvalidConfigException + * @throws InvalidSignException + */ +function verify_unipay_sign_qra(array $config, array $destination): void +{ + $sign = $destination['sign'] ?? null; + + if (empty($sign)) { + throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 银联签名为空', $destination); + } + + $key = $config['mch_secret_key'] ?? null; + + if (empty($key)) { + throw new InvalidConfigException(Exception::CONFIG_UNIPAY_INVALID, '配置异常: 缺少银联配置 -- [mch_secret_key]'); + } + + if (get_unipay_sign_qra($config, $destination) !== $sign) { + throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证银联签名失败', $destination); + } +} + +function get_jsb_url(array $config, ?Collection $payload): string +{ + $url = get_radar_url($config, $payload) ?? ''; + + if (str_starts_with($url, 'http')) { + return $url; + } + + return Jsb::URL[$config['mode'] ?? Pay::MODE_NORMAL]; +} + +/** + * @throws InvalidConfigException + * @throws InvalidSignException + */ +function verify_jsb_sign(array $config, string $content, string $sign): void +{ + if (empty($sign)) { + throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 江苏银行签名为空', func_get_args()); + } + + $publicCert = $config['jsb_public_cert_path'] ?? null; + + if (empty($publicCert)) { + throw new InvalidConfigException(Exception::CONFIG_JSB_INVALID, '配置异常: 缺少配置参数 -- [jsb_public_cert_path]'); + } + + $result = 1 === openssl_verify( + $content, + base64_decode($sign), + get_public_cert($publicCert) + ); + + if (!$result) { + throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证江苏银行签名失败', func_get_args()); + } +} + +/** + * @throws InvalidParamsException + */ +function get_douyin_url(array $config, ?Collection $payload): string +{ + $url = get_radar_url($config, $payload); + + if (empty($url)) { + throw new InvalidParamsException(Exception::PARAMS_DOUYIN_URL_MISSING, '参数异常: 抖音 `_url` 参数缺失:你可能用错插件顺序,应该先使用 `业务插件`'); + } + + if (str_starts_with($url, 'http')) { + return $url; + } + + return Douyin::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url; +} diff --git a/vendor/yansongda/pay/src/Pay.php b/vendor/yansongda/pay/src/Pay.php new file mode 100644 index 0000000..b0af14f --- /dev/null +++ b/vendor/yansongda/pay/src/Pay.php @@ -0,0 +1,119 @@ +plugins, $event->params)); + } + + public static function artfulEnd(ArtfulEnd $event): void + { + Event::dispatch(new PayEnd($event->rocket)); + } + + public static function httpStart(HttpStart $event): void + { + Event::dispatch(new Event\HttpStart($event->rocket)); + } + + public static function httpEnd(HttpEnd $event): void + { + Event::dispatch(new Event\HttpEnd($event->rocket)); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/AddPayloadSignaturePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/AddPayloadSignaturePlugin.php new file mode 100644 index 0000000..dd3614f --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/AddPayloadSignaturePlugin.php @@ -0,0 +1,68 @@ + $rocket]); + + $rocket->mergePayload(['sign' => $this->getSign($rocket)]); + + Logger::info('[Alipay][AddPayloadSignaturePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + /** + * @throws ContainerException + * @throws InvalidConfigException + * @throws ServiceNotFoundException + */ + protected function getSign(Rocket $rocket): string + { + $privateKey = $this->getPrivateKey($rocket->getParams()); + + $content = $rocket->getPayload()->sortKeys()->toString(); + + openssl_sign($content, $sign, $privateKey, OPENSSL_ALGO_SHA256); + + return base64_encode($sign); + } + + /** + * @throws ContainerException + * @throws InvalidConfigException + * @throws ServiceNotFoundException + */ + protected function getPrivateKey(array $params): string + { + $privateKey = get_provider_config('alipay', $params)['app_secret_cert'] ?? null; + + if (is_null($privateKey)) { + throw new InvalidConfigException(Exception::CONFIG_ALIPAY_INVALID, '配置异常: 缺少支付宝配置 -- [app_secret_cert]'); + } + + return get_private_cert($privateKey); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/AddRadarPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/AddRadarPlugin.php new file mode 100644 index 0000000..cc99de1 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/AddRadarPlugin.php @@ -0,0 +1,55 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('alipay', $params); + $payload = $rocket->getPayload(); + + $rocket->setRadar(new Request( + // 这里因为支付宝的 payload 里不包含 _method,所以需要取 params 中的 + get_radar_method(new Collection($params)) ?? 'POST', + get_alipay_url($config, $payload), + $this->getHeaders(), + // 不能用 packer,支付宝接收的是 x-www-form-urlencoded 返回的又是 json,packer 用的是返回. + $payload?->query() ?? '', + )); + + Logger::info('[Alipay][AddRadarPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function getHeaders(): array + { + return [ + 'Content-Type' => 'application/x-www-form-urlencoded', + 'User-Agent' => 'yansongda/pay-v3', + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/AppCallbackPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/AppCallbackPlugin.php new file mode 100644 index 0000000..ab9129a --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/AppCallbackPlugin.php @@ -0,0 +1,55 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('alipay', $params); + + if (empty($params['alipay_trade_app_pay_response'])) { + throw new InvalidParamsException(Exception::PARAMS_CALLBACK_REQUEST_INVALID); + } + + $value = filter_params($params['alipay_trade_app_pay_response']); + + verify_alipay_sign($config, $value->toJson(), $params['sign'] ?? ''); + + $rocket->setPayload($params) + ->setDirection(NoHttpRequestDirection::class) + ->setDestination($rocket->getPayload()); + + Logger::info('[Alipay][AppCallbackPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/CallbackPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/CallbackPlugin.php new file mode 100644 index 0000000..cb6c850 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/CallbackPlugin.php @@ -0,0 +1,48 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('alipay', $params); + + $value = filter_params($params, fn ($k, $v) => '' !== $v && 'sign' != $k && 'sign_type' != $k); + + verify_alipay_sign($config, $value->sortKeys()->toString(), $params['sign'] ?? ''); + + $rocket->setPayload($params) + ->setDirection(NoHttpRequestDirection::class) + ->setDestination($rocket->getPayload()); + + Logger::info('[Alipay][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/FormatPayloadBizContentPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/FormatPayloadBizContentPlugin.php new file mode 100644 index 0000000..4c637aa --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/FormatPayloadBizContentPlugin.php @@ -0,0 +1,33 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + $rocket->setPayload( + filter_params($payload->all(), fn ($k, $v) => '' !== $v && 'sign' != $k) + ->merge([ + 'biz_content' => json_encode(filter_params($payload->get('biz_content', []))), + ]) + ); + + Logger::info('[Alipay][FormatPayloadBizContentPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/PCreditPayInstallment/AppPayPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/PCreditPayInstallment/AppPayPlugin.php new file mode 100644 index 0000000..9fbbc62 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/PCreditPayInstallment/AppPayPlugin.php @@ -0,0 +1,32 @@ + $rocket]); + + $rocket->setDirection(ResponseDirection::class) + ->mergePayload([ + 'method' => 'alipay.trade.app.pay', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Fund][PCreditPayInstallment][AppPayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/PCreditPayInstallment/H5PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/PCreditPayInstallment/H5PayPlugin.php new file mode 100644 index 0000000..d468db4 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/PCreditPayInstallment/H5PayPlugin.php @@ -0,0 +1,48 @@ + $rocket]); + + $this->loadAlipayServiceProvider($rocket); + + $rocket->setDirection(ResponseDirection::class) + ->mergePayload([ + 'method' => 'alipay.trade.wap.pay', + 'biz_content' => array_merge( + [ + 'product_code' => 'QUICK_WAP_WAY', + ], + $rocket->getParams(), + ), + ]); + + Logger::info('[Alipay][Fund][PCreditPayInstallment][H5PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/PCreditPayInstallment/PosPayPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/PCreditPayInstallment/PosPayPlugin.php new file mode 100644 index 0000000..e18f134 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/PCreditPayInstallment/PosPayPlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $this->loadAlipayServiceProvider($rocket); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.pay', + 'biz_content' => array_merge( + [ + 'product_code' => 'FACE_TO_FACE_PAYMENT', + 'scene' => 'bar_code', + ], + $rocket->getParams(), + ), + ]); + + Logger::info('[Alipay][Fund][PCreditPayInstallment][PosPayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/PCreditPayInstallment/ScanPayPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/PCreditPayInstallment/ScanPayPlugin.php new file mode 100644 index 0000000..35c7d11 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/PCreditPayInstallment/ScanPayPlugin.php @@ -0,0 +1,41 @@ + $rocket]); + + $this->loadAlipayServiceProvider($rocket); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.precreate', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Fund][PCreditPayInstallment][ScanPayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Query/OnsettlePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Query/OnsettlePlugin.php new file mode 100644 index 0000000..2926941 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Query/OnsettlePlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.order.onsettle.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Fund][Royalty][Query][OnsettlePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Query/RatePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Query/RatePlugin.php new file mode 100644 index 0000000..aeea5e8 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Query/RatePlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.royalty.rate.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Fund][Royalty][Query][RatePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Query/SettlePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Query/SettlePlugin.php new file mode 100644 index 0000000..94c8c7e --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Query/SettlePlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.order.settle.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Fund][Royalty][Query][SettlePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Relation/BindPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Relation/BindPlugin.php new file mode 100644 index 0000000..2498da6 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Relation/BindPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.royalty.relation.bind', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Fund][Royalty][Relation][BindPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Relation/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Relation/QueryPlugin.php new file mode 100644 index 0000000..3af441e --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Relation/QueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.royalty.relation.batchquery', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Fund][Royalty][Relation][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Relation/UnbindPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Relation/UnbindPlugin.php new file mode 100644 index 0000000..376fc62 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Relation/UnbindPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.royalty.relation.unbind', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Fund][Royalty][Relation][UnbindPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Request/SettleOrderPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Request/SettleOrderPlugin.php new file mode 100644 index 0000000..d01b340 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Royalty/Request/SettleOrderPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.order.settle', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Fund][Royalty][Request][SettleOrderPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/ApplyReceiptPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/ApplyReceiptPlugin.php new file mode 100644 index 0000000..83a650f --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/ApplyReceiptPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.data.bill.ereceipt.apply', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Fund][Transfer][ApplyReceiptPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/Bill/QueryUrlPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/Bill/QueryUrlPlugin.php new file mode 100644 index 0000000..8cb1edd --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/Bill/QueryUrlPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.data.dataservice.bill.downloadurl.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Fund][Transfer][Bill][QueryUrlPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/Fund/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/Fund/QueryPlugin.php new file mode 100644 index 0000000..c030417 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/Fund/QueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.fund.trans.common.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Fund][Transfer][Fund][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/Fund/TransferPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/Fund/TransferPlugin.php new file mode 100644 index 0000000..d7a4033 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/Fund/TransferPlugin.php @@ -0,0 +1,37 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.fund.trans.uni.transfer', + 'biz_content' => array_merge( + [ + 'biz_scene' => 'DIRECT_TRANSFER', + 'product_code' => 'TRANS_ACCOUNT_NO_PWD', + ], + $rocket->getParams(), + ), + ]); + + Logger::info('[Alipay][Fund][Transfer][Fund][TransferPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/QueryAccountPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/QueryAccountPlugin.php new file mode 100644 index 0000000..4297e88 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/QueryAccountPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.fund.account.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Fund][Transfer][QueryAccountPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/QueryReceiptPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/QueryReceiptPlugin.php new file mode 100644 index 0000000..5efdcfd --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/QueryReceiptPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.data.bill.ereceipt.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Fund][Transfer][QueryReceiptPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/RefundPlugin.php new file mode 100644 index 0000000..f7e41ea --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Fund/Transfer/RefundPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.fund.trans.refund', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Fund][Transfer][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Marketing/Redpack/AppPayPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Marketing/Redpack/AppPayPlugin.php new file mode 100644 index 0000000..2df8f13 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Marketing/Redpack/AppPayPlugin.php @@ -0,0 +1,38 @@ + $rocket]); + + $rocket->setDirection(ResponseDirection::class) + ->mergePayload([ + 'method' => 'alipay.fund.trans.app.pay', + 'biz_content' => array_merge( + [ + 'product_code' => 'STD_RED_PACKET', + 'biz_scene' => 'PERSONAL_PAY', + ], + $rocket->getParams(), + ), + ]); + + Logger::info('[Alipay][Marketing][Redpack][AppPayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Marketing/Redpack/WebPayPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Marketing/Redpack/WebPayPlugin.php new file mode 100644 index 0000000..4c38b18 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Marketing/Redpack/WebPayPlugin.php @@ -0,0 +1,37 @@ + $rocket]); + + $rocket->setDirection(ResponseDirection::class) + ->mergePayload([ + 'method' => 'alipay.fund.trans.page.pay', + 'biz_content' => array_merge( + [ + 'product_code' => 'STD_APP_TRANSFER', + ], + $rocket->getParams() + ), + ]); + + Logger::info('[Alipay][Marketing][Redpack][WebPayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Authorization/AuthPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Authorization/AuthPlugin.php new file mode 100644 index 0000000..4d0dbf9 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Authorization/AuthPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.user.info.auth', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Member][Authorization][AuthPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Authorization/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Authorization/QueryPlugin.php new file mode 100644 index 0000000..5d4f40f --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Authorization/QueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.open.auth.userauth.relationship.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Member][Authorization][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Authorization/TokenPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Authorization/TokenPlugin.php new file mode 100644 index 0000000..a267e80 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Authorization/TokenPlugin.php @@ -0,0 +1,32 @@ + $rocket]); + + $rocket->mergePayload(array_merge( + [ + 'method' => 'alipay.system.oauth.token', + ], + $rocket->getParams(), + )); + + Logger::info('[Alipay][Member][Authorization][TokenPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Certification/CertifyPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Certification/CertifyPlugin.php new file mode 100644 index 0000000..d5e8bb4 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Certification/CertifyPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.user.certify.open.certify', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Member][Certification][CertifyPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Certification/InitPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Certification/InitPlugin.php new file mode 100644 index 0000000..4e97cab --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Certification/InitPlugin.php @@ -0,0 +1,35 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.user.certify.open.initialize', + 'biz_content' => array_merge( + [ + 'product_code' => 'FACE', + ], + $rocket->getParams(), + ), + ]); + + Logger::info('[Alipay][Member][Certification][AppInitPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Certification/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Certification/QueryPlugin.php new file mode 100644 index 0000000..b43fdd9 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Certification/QueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.user.certify.open.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Member][Certification][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/DetailPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/DetailPlugin.php new file mode 100644 index 0000000..ab71f87 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/DetailPlugin.php @@ -0,0 +1,33 @@ + $rocket]); + + $params = $rocket->getParams(); + + $rocket->mergePayload([ + 'auth_token' => $params['auth_token'] ?? $params['_auth_token'] ?? '', + 'method' => 'alipay.user.info.share', + 'biz_content' => [], + ]); + + Logger::info('[Alipay][Member][DetailPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceCheck/AppInitPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceCheck/AppInitPlugin.php new file mode 100644 index 0000000..f3242bb --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceCheck/AppInitPlugin.php @@ -0,0 +1,35 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'datadigital.fincloud.generalsaas.face.check.initialize', + 'biz_content' => array_merge( + [ + 'biz_code' => 'DATA_DIGITAL_BIZ_CODE_FACE_CHECK_LIVE', + ], + $rocket->getParams(), + ), + ]); + + Logger::info('[Alipay][Member][FaceCheck][AppInitPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceCheck/AppQueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceCheck/AppQueryPlugin.php new file mode 100644 index 0000000..81ce033 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceCheck/AppQueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'datadigital.fincloud.generalsaas.face.check.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Member][FaceCheck][AppQueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceVerification/AppInitPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceVerification/AppInitPlugin.php new file mode 100644 index 0000000..5eb4eb8 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceVerification/AppInitPlugin.php @@ -0,0 +1,36 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'datadigital.fincloud.generalsaas.face.verification.initialize', + 'biz_content' => array_merge( + [ + 'biz_code' => 'DATA_DIGITAL_BIZ_CODE_FACE_VERIFICATION', + 'identity_type' => 'CERT_INFO', + ], + $rocket->getParams(), + ), + ]); + + Logger::info('[Alipay][Member][FaceVerification][AppInitPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceVerification/AppQueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceVerification/AppQueryPlugin.php new file mode 100644 index 0000000..aec240b --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceVerification/AppQueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'datadigital.fincloud.generalsaas.face.verification.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Member][FaceVerification][AppQueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceVerification/H5InitPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceVerification/H5InitPlugin.php new file mode 100644 index 0000000..3cd5ac2 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceVerification/H5InitPlugin.php @@ -0,0 +1,35 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'datadigital.fincloud.generalsaas.face.certify.initialize', + 'biz_content' => array_merge( + [ + 'biz_code' => 'FUTURE_TECH_BIZ_FACE_SDK', + ], + $rocket->getParams(), + ), + ]); + + Logger::info('[Alipay][Member][FaceVerification][H5InitPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceVerification/H5QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceVerification/H5QueryPlugin.php new file mode 100644 index 0000000..7dda061 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceVerification/H5QueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'datadigital.fincloud.generalsaas.face.certify.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Member][FaceVerification][H5QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceVerification/H5VerifyPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceVerification/H5VerifyPlugin.php new file mode 100644 index 0000000..572c8b3 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceVerification/H5VerifyPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'datadigital.fincloud.generalsaas.face.certify.verify', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Member][FaceVerification][H5VerifyPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceVerification/ServerVerifyPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceVerification/ServerVerifyPlugin.php new file mode 100644 index 0000000..358294f --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/FaceVerification/ServerVerifyPlugin.php @@ -0,0 +1,35 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'datadigital.fincloud.generalsaas.face.source.certify', + 'biz_content' => array_merge( + [ + 'cert_type' => 'IDENTITY_CARD', + ], + $rocket->getParams() + ), + ]); + + Logger::info('[Alipay][Member][FaceVerification][ServerVerifyPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Ocr/AppInitPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Ocr/AppInitPlugin.php new file mode 100644 index 0000000..88a55e0 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Ocr/AppInitPlugin.php @@ -0,0 +1,35 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'datadigital.fincloud.generalsaas.ocr.mobile.initialize', + 'biz_content' => array_merge( + [ + 'biz_code' => 'DATA_DIGITAL_BIZ_CODE_OCR', + ], + $rocket->getParams(), + ), + ]); + + Logger::info('[Alipay][Member][Ocr][AppInitPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Ocr/DetectPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Ocr/DetectPlugin.php new file mode 100644 index 0000000..d8e1d92 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Ocr/DetectPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'datadigital.fincloud.generalsaas.ocr.common.detect', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Member][Ocr][DetectPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Ocr/ServerDetectPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Ocr/ServerDetectPlugin.php new file mode 100644 index 0000000..eaa5518 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Member/Ocr/ServerDetectPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'datadigital.fincloud.generalsaas.ocr.server.detect', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Member][Ocr][ServerDetectPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Bill/QueryUrlPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Bill/QueryUrlPlugin.php new file mode 100644 index 0000000..0bbe513 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Bill/QueryUrlPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.data.dataservice.bill.downloadurl.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Agreement][Bill][QueryUrlPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Pay/AppPayPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Pay/AppPayPlugin.php new file mode 100644 index 0000000..87807cd --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Pay/AppPayPlugin.php @@ -0,0 +1,32 @@ + $rocket]); + + $rocket->setDirection(ResponseDirection::class) + ->mergePayload([ + 'method' => 'alipay.trade.app.pay', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Agreement][Pay][AppPayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Pay/CancelPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Pay/CancelPlugin.php new file mode 100644 index 0000000..1ce0c4c --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Pay/CancelPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.cancel', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Agreement][Pay][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Pay/ClosePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Pay/ClosePlugin.php new file mode 100644 index 0000000..f609d43 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Pay/ClosePlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.close', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Agreement][Pay][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Pay/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Pay/PayPlugin.php new file mode 100644 index 0000000..d15a01b --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Pay/PayPlugin.php @@ -0,0 +1,46 @@ + $rocket]); + + $this->loadAlipayServiceProvider($rocket); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.pay', + 'biz_content' => array_merge( + [ + 'product_code' => 'GENERAL_WITHHOLDING', + ], + $rocket->getParams(), + ), + ]); + + Logger::info('[Alipay][Pay][Agreement][Pay][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Pay/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Pay/QueryPlugin.php new file mode 100644 index 0000000..a4d6dbf --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Pay/QueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Agreement][Pay][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Pay/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Pay/RefundPlugin.php new file mode 100644 index 0000000..6e1d259 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Pay/RefundPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.refund', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Agreement][Pay][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Sign/ModifyPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Sign/ModifyPlugin.php new file mode 100644 index 0000000..a86adf9 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Sign/ModifyPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.user.agreement.executionplan.modify', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Agreement][Sign][ModifyPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Sign/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Sign/QueryPlugin.php new file mode 100644 index 0000000..7d3dedb --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Sign/QueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.user.agreement.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Agreement][Sign][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Sign/SignPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Sign/SignPlugin.php new file mode 100644 index 0000000..25eb408 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Sign/SignPlugin.php @@ -0,0 +1,32 @@ + $rocket]); + + $rocket->setDirection(ResponseDirection::class) + ->mergePayload([ + 'method' => 'alipay.user.agreement.page.sign', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Agreement][Sign][SignPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Sign/UnsignPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Sign/UnsignPlugin.php new file mode 100644 index 0000000..d5fae2e --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Agreement/Sign/UnsignPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.user.agreement.unsign', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Agreement][Sign][UnsignPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/App/ClosePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/App/ClosePlugin.php new file mode 100644 index 0000000..c5cc0c9 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/App/ClosePlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.close', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][App][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/App/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/App/PayPlugin.php new file mode 100644 index 0000000..4438aa7 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/App/PayPlugin.php @@ -0,0 +1,43 @@ + $rocket]); + + $this->loadAlipayServiceProvider($rocket); + + $rocket->setDirection(ResponseDirection::class) + ->mergePayload([ + 'method' => 'alipay.trade.app.pay', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][App][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/App/QueryBillUrlPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/App/QueryBillUrlPlugin.php new file mode 100644 index 0000000..de0761e --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/App/QueryBillUrlPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.data.dataservice.bill.downloadurl.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][App][QueryBillUrlPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/App/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/App/QueryPlugin.php new file mode 100644 index 0000000..9370dc9 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/App/QueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][App][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/App/QueryRefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/App/QueryRefundPlugin.php new file mode 100644 index 0000000..4d0bbf6 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/App/QueryRefundPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.fastpay.refund.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][App][QueryRefundPlugin] 通用插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/App/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/App/RefundPlugin.php new file mode 100644 index 0000000..f4e658b --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/App/RefundPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.refund', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][App][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Auth/AppFreezePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Auth/AppFreezePlugin.php new file mode 100644 index 0000000..b2585b5 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Auth/AppFreezePlugin.php @@ -0,0 +1,37 @@ + $rocket]); + + $rocket->setDirection(ResponseDirection::class) + ->mergePayload([ + 'method' => 'alipay.fund.auth.order.app.freeze', + 'biz_content' => array_merge( + [ + 'product_code' => 'PREAUTH_PAY', + ], + $rocket->getParams() + ), + ]); + + Logger::info('[Alipay][Pay][Authorization][Auth][AppFreezePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Auth/CancelPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Auth/CancelPlugin.php new file mode 100644 index 0000000..0470b73 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Auth/CancelPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.fund.auth.operation.cancel', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Authorization][Auth][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Auth/PosFreezePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Auth/PosFreezePlugin.php new file mode 100644 index 0000000..9f5aa38 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Auth/PosFreezePlugin.php @@ -0,0 +1,36 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.fund.auth.order.freeze', + 'biz_content' => array_merge( + [ + 'auth_code_type' => 'bar_code', + 'product_code' => 'PREAUTH_PAY', + ], + $rocket->getParams() + ), + ]); + + Logger::info('[Alipay][Pay][Authorization][Auth][PosFreezePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Auth/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Auth/QueryPlugin.php new file mode 100644 index 0000000..1a496ef --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Auth/QueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.fund.auth.operation.detail.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Authorization][Auth][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Auth/ScanFreezePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Auth/ScanFreezePlugin.php new file mode 100644 index 0000000..113e3b0 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Auth/ScanFreezePlugin.php @@ -0,0 +1,35 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.fund.auth.order.voucher.create', + 'biz_content' => array_merge( + [ + 'product_code' => 'PREAUTH_PAY', + ], + $rocket->getParams() + ), + ]); + + Logger::info('[Alipay][Pay][Authorization][Auth][ScanFreezePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Auth/UnfreezePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Auth/UnfreezePlugin.php new file mode 100644 index 0000000..d77e846 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Auth/UnfreezePlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.fund.auth.order.unfreeze', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Authorization][Auth][AppFreezePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Bill/QueryUrlPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Bill/QueryUrlPlugin.php new file mode 100644 index 0000000..6f20870 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Bill/QueryUrlPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.data.dataservice.bill.downloadurl.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Authorization][Bill][QueryUrlPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Pay/ClosePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Pay/ClosePlugin.php new file mode 100644 index 0000000..70a2fa7 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Pay/ClosePlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.close', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Authorization][Pay][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Pay/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Pay/PayPlugin.php new file mode 100644 index 0000000..fdf77bf --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Pay/PayPlugin.php @@ -0,0 +1,46 @@ + $rocket]); + + $this->loadAlipayServiceProvider($rocket); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.pay', + 'biz_content' => array_merge( + [ + 'product_code' => 'PREAUTH_PAY', + ], + $rocket->getParams(), + ), + ]); + + Logger::info('[Alipay][Pay][Authorization][Pay][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Pay/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Pay/QueryPlugin.php new file mode 100644 index 0000000..b0070f8 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Pay/QueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Authorization][Pay][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Pay/QueryRefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Pay/QueryRefundPlugin.php new file mode 100644 index 0000000..1878430 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Pay/QueryRefundPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.fastpay.refund.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Authorization][Pay][QueryRefundPlugin] 通用插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Pay/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Pay/RefundPlugin.php new file mode 100644 index 0000000..730d360 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Pay/RefundPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.refund', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Authorization][Pay][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Pay/SyncPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Pay/SyncPlugin.php new file mode 100644 index 0000000..8c205b6 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Authorization/Pay/SyncPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.orderinfo.sync', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Authorization][Pay][SyncPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Face/InitPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Face/InitPlugin.php new file mode 100644 index 0000000..2c6057a --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Face/InitPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'zoloz.authentication.smilepay.initialize', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Face][InitPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Face/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Face/QueryPlugin.php new file mode 100644 index 0000000..5fff49b --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Face/QueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'zoloz.authentication.customer.ftoken.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Face][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/H5/ClosePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/H5/ClosePlugin.php new file mode 100644 index 0000000..6a95844 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/H5/ClosePlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.close', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][H5][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/H5/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/H5/PayPlugin.php new file mode 100644 index 0000000..3fdcb15 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/H5/PayPlugin.php @@ -0,0 +1,43 @@ + $rocket]); + + $this->loadAlipayServiceProvider($rocket); + + $rocket->setDirection(ResponseDirection::class) + ->mergePayload([ + 'method' => 'alipay.trade.wap.pay', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][H5][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/H5/QueryBillUrlPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/H5/QueryBillUrlPlugin.php new file mode 100644 index 0000000..8377b72 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/H5/QueryBillUrlPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.data.dataservice.bill.downloadurl.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][H5][QueryBillUrlPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/H5/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/H5/QueryPlugin.php new file mode 100644 index 0000000..0032459 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/H5/QueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][H5][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/H5/QueryRefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/H5/QueryRefundPlugin.php new file mode 100644 index 0000000..1e42b56 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/H5/QueryRefundPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.fastpay.refund.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][H5][QueryRefundPlugin] 通用插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/H5/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/H5/RefundPlugin.php new file mode 100644 index 0000000..066bb6e --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/H5/RefundPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.refund', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][H5][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/CancelPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/CancelPlugin.php new file mode 100644 index 0000000..68a0496 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/CancelPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.cancel', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Mini][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/ClosePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/ClosePlugin.php new file mode 100644 index 0000000..86d3955 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/ClosePlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.close', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Mini][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/PayPlugin.php new file mode 100644 index 0000000..407e776 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/PayPlugin.php @@ -0,0 +1,46 @@ + $rocket]); + + $this->loadAlipayServiceProvider($rocket); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.create', + 'biz_content' => array_merge( + [ + 'product_code' => 'JSAPI_PAY', + ], + $rocket->getParams(), + ), + ]); + + Logger::info('[Alipay][Pay][Mini][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/QueryBillUrlPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/QueryBillUrlPlugin.php new file mode 100644 index 0000000..e8dd2ea --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/QueryBillUrlPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.data.dataservice.bill.downloadurl.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Mini][QueryBillUrlPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/QueryPlugin.php new file mode 100644 index 0000000..61c0c0f --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/QueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Mini][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/QueryRefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/QueryRefundPlugin.php new file mode 100644 index 0000000..8f690c0 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/QueryRefundPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.fastpay.refund.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Mini][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/RefundPlugin.php new file mode 100644 index 0000000..c927370 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Mini/RefundPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.refund', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Mini][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/CancelPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/CancelPlugin.php new file mode 100644 index 0000000..4324d12 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/CancelPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.cancel', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Pos][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/ClosePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/ClosePlugin.php new file mode 100644 index 0000000..1bb49c3 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/ClosePlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.close', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Pos][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/PayPlugin.php new file mode 100644 index 0000000..6fdfa80 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/PayPlugin.php @@ -0,0 +1,46 @@ + $rocket]); + + $this->loadAlipayServiceProvider($rocket); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.pay', + 'biz_content' => array_merge( + [ + 'scene' => 'bar_code', + ], + $rocket->getParams(), + ), + ]); + + Logger::info('[Alipay][Pay][Pos][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/QueryBillUrlPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/QueryBillUrlPlugin.php new file mode 100644 index 0000000..6857612 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/QueryBillUrlPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.data.dataservice.bill.downloadurl.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Pos][QueryBillUrlPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/QueryPlugin.php new file mode 100644 index 0000000..0e54469 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/QueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Pos][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/QueryRefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/QueryRefundPlugin.php new file mode 100644 index 0000000..66844dd --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/QueryRefundPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.fastpay.refund.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Pos][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/RefundPlugin.php new file mode 100644 index 0000000..b245616 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Pos/RefundPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.refund', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Pos][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/CancelPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/CancelPlugin.php new file mode 100644 index 0000000..b8cf716 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/CancelPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.cancel', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Scan][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/ClosePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/ClosePlugin.php new file mode 100644 index 0000000..a35a0e5 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/ClosePlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.close', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Scan][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/CreatePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/CreatePlugin.php new file mode 100644 index 0000000..c74b41d --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/CreatePlugin.php @@ -0,0 +1,41 @@ + $rocket]); + + $this->loadAlipayServiceProvider($rocket); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.create', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Scan][CreatePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/PayPlugin.php new file mode 100644 index 0000000..4ceecda --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/PayPlugin.php @@ -0,0 +1,41 @@ + $rocket]); + + $this->loadAlipayServiceProvider($rocket); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.precreate', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Scan][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/QueryBillUrlPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/QueryBillUrlPlugin.php new file mode 100644 index 0000000..7700491 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/QueryBillUrlPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.data.dataservice.bill.downloadurl.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Scan][QueryBillUrlPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/QueryPlugin.php new file mode 100644 index 0000000..95b498e --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/QueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Scan][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/QueryRefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/QueryRefundPlugin.php new file mode 100644 index 0000000..95c3ad2 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/QueryRefundPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.fastpay.refund.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Scan][QueryRefundPlugin] 通用插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/RefundPlugin.php new file mode 100644 index 0000000..79c145f --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Scan/RefundPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.refund', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Scan][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Web/ClosePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Web/ClosePlugin.php new file mode 100644 index 0000000..22cd791 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Web/ClosePlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.close', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Web][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Web/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Web/PayPlugin.php new file mode 100644 index 0000000..6de7c2e --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Web/PayPlugin.php @@ -0,0 +1,48 @@ + $rocket]); + + $this->loadAlipayServiceProvider($rocket); + + $rocket->setDirection(ResponseDirection::class) + ->mergePayload([ + 'method' => 'alipay.trade.page.pay', + 'biz_content' => array_merge( + [ + 'product_code' => 'FAST_INSTANT_TRADE_PAY', + ], + $rocket->getParams() + ), + ]); + + Logger::info('[Alipay][Pay][Web][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Web/QueryBillUrlPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Web/QueryBillUrlPlugin.php new file mode 100644 index 0000000..a61b972 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Web/QueryBillUrlPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.data.dataservice.bill.downloadurl.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Web][QueryBillUrlPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Web/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Web/QueryPlugin.php new file mode 100644 index 0000000..a4f9237 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Web/QueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Web][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Web/QueryRefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Web/QueryRefundPlugin.php new file mode 100644 index 0000000..c0f319f --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Web/QueryRefundPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.fastpay.refund.query', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Web][QueryRefundPlugin] 通用插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Web/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Web/RefundPlugin.php new file mode 100644 index 0000000..70fb333 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/Pay/Web/RefundPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'method' => 'alipay.trade.refund', + 'biz_content' => $rocket->getParams(), + ]); + + Logger::info('[Alipay][Pay][Web][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/ResponseHtmlPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/ResponseHtmlPlugin.php new file mode 100644 index 0000000..ad6af68 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/ResponseHtmlPlugin.php @@ -0,0 +1,71 @@ + $rocket]); + + $radar = $rocket->getRadar(); + + $response = 'GET' === $radar->getMethod() + ? $this->buildRedirect($radar->getUri()->__toString(), $rocket->getPayload()) + : $this->buildHtml($radar->getUri()->__toString(), $rocket->getPayload()); + + $rocket->setDestination($response); + + Logger::info('[Alipay][ResponseHtmlPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + protected function buildRedirect(string $endpoint, Collection $payload): Response + { + $url = $endpoint.(!str_contains($endpoint, '?') ? '?' : '&').$payload->query(); + + $content = sprintf( + ' + + + + + + Redirecting to %1$s + + + Redirecting to %1$s. + + ', + htmlspecialchars($url, ENT_QUOTES) + ); + + return new Response(302, ['Location' => $url], $content); + } + + protected function buildHtml(string $endpoint, Collection $payload): Response + { + $sHtml = "
"; + foreach ($payload->all() as $key => $val) { + $val = str_replace("'", ''', $val); + $sHtml .= ""; + } + $sHtml .= "
"; + $sHtml .= ""; + + return new Response(200, [], $sHtml); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/ResponseInvokeStringPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/ResponseInvokeStringPlugin.php new file mode 100644 index 0000000..d08ccf8 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/ResponseInvokeStringPlugin.php @@ -0,0 +1,31 @@ + $rocket]); + + $response = new Response(200, [], Arr::query($rocket->getPayload()->all())); + + $rocket->setDestination($response); + + Logger::info('[Alipay][ResponseInvokeStringPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/ResponsePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/ResponsePlugin.php new file mode 100644 index 0000000..36f2870 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/ResponsePlugin.php @@ -0,0 +1,51 @@ + $rocket]); + + $destination = $rocket->getDestination(); + $payload = $rocket->getPayload(); + $resultKey = str_replace('.', '_', $payload->get('method')).'_response'; + + if (should_do_http_request($rocket->getDirection()) && $destination instanceof Collection) { + $sign = $destination->get('sign', ''); + $response = $destination->get($resultKey, $destination->all()); + + if (empty($sign) && '10000' !== ($response['code'] ?? 'null')) { + throw new InvalidResponseException(Exception::RESPONSE_BUSINESS_CODE_WRONG, '支付宝网关响应异常: '.($response['sub_msg'] ?? $response['msg'] ?? '未知错误,请查看支付宝原始响应'), $rocket->getDestination()); + } + + $rocket->setDestination(new Collection(array_merge( + ['_sign' => $sign], + $response + ))); + } + + Logger::info('[Alipay][ResponsePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/StartPlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/StartPlugin.php new file mode 100644 index 0000000..187a762 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/StartPlugin.php @@ -0,0 +1,210 @@ + $rocket]); + + $rocket->mergePayload($this->getPayload($rocket->getParams())); + + Logger::info('[Alipay][StartPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + /** + * @throws ContainerException + * @throws ServiceNotFoundException + * @throws InvalidConfigException + */ + protected function getPayload(array $params): array + { + $tenant = get_tenant($params); + $config = get_provider_config('alipay', $params); + + return [ + 'app_id' => $config['app_id'] ?? '', + 'method' => '', + 'format' => 'JSON', + 'return_url' => $this->getReturnUrl($params, $config), + 'charset' => 'utf-8', + 'sign_type' => 'RSA2', + 'sign' => '', + 'timestamp' => date('Y-m-d H:i:s'), + 'version' => '1.0', + 'notify_url' => $this->getNotifyUrl($params, $config), + 'app_auth_token' => $this->getAppAuthToken($params, $config), + 'app_cert_sn' => $this->getAppCertSn($tenant, $config), + 'alipay_root_cert_sn' => $this->getAlipayRootCertSn($tenant, $config), + 'biz_content' => [], + ]; + } + + protected function getReturnUrl(array $params, array $config): string + { + if (!empty($params['_return_url'])) { + return $params['_return_url']; + } + + return $config['return_url'] ?? ''; + } + + protected function getNotifyUrl(array $params, array $config): string + { + if (!empty($params['_notify_url'])) { + return $params['_notify_url']; + } + + return $config['notify_url'] ?? ''; + } + + protected function getAppAuthToken(array $params, array $config): string + { + if (!empty($params['_app_auth_token'])) { + return $params['_app_auth_token']; + } + + return $config['app_auth_token'] ?? ''; + } + + /** + * @throws ContainerException + * @throws InvalidConfigException + * @throws ServiceNotFoundException + */ + protected function getAppCertSn(string $tenant, array $config): string + { + if (!empty($config['app_public_cert_sn'])) { + return $config['app_public_cert_sn']; + } + + $path = $config['app_public_cert_path'] ?? null; + + if (is_null($path)) { + throw new InvalidConfigException(Exception::CONFIG_ALIPAY_INVALID, '配置异常: 缺少支付宝配置 -- [app_public_cert_path]'); + } + + $ssl = openssl_x509_parse(get_public_cert($path)); + + if (false === $ssl) { + throw new InvalidConfigException(Exception::CONFIG_ALIPAY_INVALID, '配置异常: 解析 `app_public_cert_path` 失败'); + } + + $result = $this->getCertSn($ssl['issuer'] ?? [], $ssl['serialNumber'] ?? ''); + + Pay::get(ConfigInterface::class)->set('alipay.'.$tenant.'.app_public_cert_sn', $result); + + return $result; + } + + /** + * @throws ContainerException + * @throws InvalidConfigException + * @throws ServiceNotFoundException + */ + protected function getAlipayRootCertSn(string $tenant, array $config): string + { + if (!empty($config['alipay_root_cert_sn'])) { + return $config['alipay_root_cert_sn']; + } + + $path = $config['alipay_root_cert_path'] ?? null; + + if (is_null($path)) { + throw new InvalidConfigException(Exception::CONFIG_ALIPAY_INVALID, '配置异常: 缺少支付宝配置 -- [alipay_root_cert_path]'); + } + + $sn = ''; + $exploded = explode('-----END CERTIFICATE-----', get_public_cert($path)); + + foreach ($exploded as $cert) { + if (empty(trim($cert))) { + continue; + } + + $ssl = openssl_x509_parse($cert.'-----END CERTIFICATE-----'); + + if (false === $ssl) { + throw new InvalidConfigException(Exception::CONFIG_ALIPAY_INVALID, '配置异常: 解析 `alipay_root_cert` 失败'); + } + + $detail = $this->formatCert($ssl); + + if ('sha1WithRSAEncryption' == $detail['signatureTypeLN'] || 'sha256WithRSAEncryption' == $detail['signatureTypeLN']) { + $sn .= $this->getCertSn($detail['issuer'], $detail['serialNumber']).'_'; + } + } + + $result = substr($sn, 0, -1); + + Pay::get(ConfigInterface::class)->set('alipay.'.$tenant.'.alipay_root_cert_sn', $result); + + return $result; + } + + protected function getCertSn(array $issuer, string $serialNumber): string + { + return md5($this->array2string(array_reverse($issuer)).$serialNumber); + } + + protected function array2string(array $array): string + { + $string = []; + + foreach ($array as $key => $value) { + $string[] = $key.'='.$value; + } + + return implode(',', $string); + } + + protected function formatCert(array $ssl): array + { + if (str_starts_with($ssl['serialNumber'] ?? '', '0x')) { + $ssl['serialNumber'] = $this->hex2dec($ssl['serialNumberHex'] ?? ''); + } + + return $ssl; + } + + protected function hex2dec(string $hex): string + { + $dec = '0'; + $len = strlen($hex); + + for ($i = 1; $i <= $len; ++$i) { + $dec = bcadd( + $dec, + bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i), 0), 0), + 0 + ); + } + + return $dec; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Alipay/V2/VerifySignaturePlugin.php b/vendor/yansongda/pay/src/Plugin/Alipay/V2/VerifySignaturePlugin.php new file mode 100644 index 0000000..5e667e5 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Alipay/V2/VerifySignaturePlugin.php @@ -0,0 +1,57 @@ + $rocket]); + + if (!should_do_http_request($rocket->getDirection())) { + return $rocket; + } + + $destination = $rocket->getDestination(); + + if ((!$destination instanceof Collection) || empty($result = $destination->except('_sign')->all())) { + throw new InvalidParamsException(Exception::RESPONSE_EMPTY, '参数异常: 支付宝验证签名时待验签参数不正确', $destination); + } + + $config = get_provider_config('alipay', $rocket->getParams()); + + verify_alipay_sign($config, json_encode($result, JSON_UNESCAPED_UNICODE), $destination->get('_sign', '')); + + Logger::info('[Alipay][VerifySignaturePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/AddPayloadSignaturePlugin.php b/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/AddPayloadSignaturePlugin.php new file mode 100644 index 0000000..7a83f97 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/AddPayloadSignaturePlugin.php @@ -0,0 +1,94 @@ + $rocket]); + + $config = get_provider_config('douyin', $rocket->getParams()); + $payload = $rocket->getPayload(); + + $rocket->mergePayload(['sign' => $this->getSign($config, filter_params($payload))]); + + Logger::info('[Douyin][V1][Pay][AddPayloadSignaturePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + /** + * @throws InvalidConfigException + */ + protected function getSign(array $config, Collection $payload): string + { + $salt = $config['mch_secret_salt'] ?? null; + + if (empty($salt)) { + throw new InvalidConfigException(Exception::CONFIG_DOUYIN_INVALID, '配置异常: 缺少抖音配置 -- [mch_secret_salt]'); + } + + foreach ($payload as $key => $value) { + if (is_string($value)) { + $value = trim($value); + } + + if (in_array($key, ['other_settle_params', 'app_id', 'sign', 'thirdparty_id']) || empty($value) || 'null' === $value) { + continue; + } + + if (is_array($value)) { + $value = $this->arrayToString($value); + } + + $signData[] = $value; + } + + $signData[] = $salt; + + sort($signData, SORT_STRING); + + return md5(implode('&', $signData)); + } + + protected function arrayToString(array $value): string + { + $isJsonArray = isset($value[0]); + $keys = array_keys($value); + + if ($isJsonArray) { + sort($keys); + } + + foreach ($keys as $key) { + $val = $value[$key]; + + $result[] = is_array($val) ? $this->arrayToString($val) : (($isJsonArray ? '' : $key.':').trim(strval($val))); + } + + $result = '['.implode(' ', $result ?? []).']'; + + return ($isJsonArray ? '' : 'map').$result; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/AddRadarPlugin.php b/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/AddRadarPlugin.php new file mode 100644 index 0000000..b7a020c --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/AddRadarPlugin.php @@ -0,0 +1,55 @@ + $rocket]); + + $params = $rocket->getParams(); + $payload = $rocket->getPayload(); + $config = get_provider_config('douyin', $params); + + $rocket->setRadar(new Request( + get_radar_method($payload), + get_douyin_url($config, $payload), + $this->getHeaders(), + get_radar_body($payload), + )); + + Logger::info('[Douyin][V1][Pay][AddRadarPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function getHeaders(): array + { + return [ + 'User-Agent' => 'yansongda/pay-v3', + 'Content-Type' => 'application/json; charset=utf-8', + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/CallbackPlugin.php b/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/CallbackPlugin.php new file mode 100644 index 0000000..c348f48 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/CallbackPlugin.php @@ -0,0 +1,74 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('douyin', $params); + + $value = filter_params($params, fn ($k, $v) => '' !== $v && 'msg_signature' != $k && 'type' != $k); + + $this->verifySign($config, $value->all(), $params['msg_signature'] ?? ''); + + $rocket->setPayload($params) + ->setDirection(NoHttpRequestDirection::class) + ->setDestination($rocket->getPayload()); + + Logger::info('[Douyin][V1][Pay][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + /** + * @throws InvalidConfigException + * @throws InvalidSignException + */ + protected function verifySign(array $config, array $contents, string $sign): void + { + if (empty($sign)) { + throw new InvalidSignException(Exception::SIGN_EMPTY, '签名异常: 验证抖音签名失败-抖音签名为空', func_get_args()); + } + + $contents['token'] = $config['mch_secret_token'] ?? null; + + if (empty($contents['token'])) { + throw new InvalidConfigException(Exception::CONFIG_DOUYIN_INVALID, '配置异常: 缺少抖音配置 -- [mch_secret_token]'); + } + + sort($contents, SORT_STRING); + $data = trim(implode('', $contents)); + + $result = $sign === sha1($data); + + if (!$result) { + throw new InvalidSignException(Exception::SIGN_ERROR, '签名异常: 验证抖音签名失败', func_get_args()); + } + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php new file mode 100644 index 0000000..3926149 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/Mini/PayPlugin.php @@ -0,0 +1,72 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $params = $rocket->getParams(); + $config = get_provider_config('douyin', $params); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 抖音小程序下单,参数为空'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/create_order', + 'app_id' => $config['mini_app_id'] ?? '', + 'notify_url' => $payload->get('notify_url') ?? $this->getNotifyUrl($config), + ], + $data ?? [], + )); + + Logger::info('[Douyin][V1][Pay][Mini][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'thirdparty_id' => $payload->get('thirdparty_id', $config['thirdparty_id'] ?? ''), + ]; + } + + protected function getNotifyUrl(array $config): ?string + { + return empty($config['notify_url']) ? null : $config['notify_url']; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/Mini/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/Mini/QueryPlugin.php new file mode 100644 index 0000000..3dbca6d --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/Mini/QueryPlugin.php @@ -0,0 +1,66 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $params = $rocket->getParams(); + $config = get_provider_config('douyin', $params); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 抖音小程序查询订单,参数为空'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/query_order', + 'app_id' => $config['mini_app_id'] ?? '', + ], + $data ?? [], + )); + + Logger::info('[Douyin][V1][Pay][Mini][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'thirdparty_id' => $payload->get('thirdparty_id', $config['thirdparty_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/Mini/QueryRefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/Mini/QueryRefundPlugin.php new file mode 100644 index 0000000..8f15e3a --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/Mini/QueryRefundPlugin.php @@ -0,0 +1,66 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $params = $rocket->getParams(); + $config = get_provider_config('douyin', $params); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 抖音小程序查询退款订单,参数为空'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/query_refund', + 'app_id' => $config['mini_app_id'] ?? '', + ], + $data ?? [], + )); + + Logger::info('[Douyin][V1][Pay][Mini][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'thirdparty_id' => $payload->get('thirdparty_id', $config['thirdparty_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/Mini/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/Mini/RefundPlugin.php new file mode 100644 index 0000000..7186461 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/Mini/RefundPlugin.php @@ -0,0 +1,72 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $params = $rocket->getParams(); + $config = get_provider_config('douyin', $params); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 抖音小程序退款订单,参数为空'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'api/apps/ecpay/v1/create_refund', + 'app_id' => $config['mini_app_id'] ?? '', + 'notify_url' => $payload->get('notify_url') ?? $this->getNotifyUrl($config), + ], + $data ?? [], + )); + + Logger::info('[Douyin][V1][Pay][Mini][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'thirdparty_id' => $payload->get('thirdparty_id', $config['thirdparty_id'] ?? ''), + ]; + } + + protected function getNotifyUrl(array $config): ?string + { + return empty($config['notify_url']) ? null : $config['notify_url']; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/ResponsePlugin.php b/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/ResponsePlugin.php new file mode 100644 index 0000000..df4939a --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Douyin/V1/Pay/ResponsePlugin.php @@ -0,0 +1,51 @@ + $rocket]); + + $this->validateResponse($rocket); + + Logger::info('[Douyin][V1][Pay][ResponsePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + /** + * @throws InvalidResponseException + */ + protected function validateResponse(Rocket $rocket): void + { + $destination = $rocket->getDestination(); + $response = $rocket->getDestinationOrigin(); + + if ($response instanceof ResponseInterface + && ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300)) { + throw new InvalidResponseException(Exception::RESPONSE_CODE_WRONG, '抖音返回状态码异常,请检查参数是否错误', $destination); + } + + if (0 !== $destination->get('err_no')) { + throw new InvalidResponseException(Exception::RESPONSE_BUSINESS_CODE_WRONG, '抖音返回业务异常: '.$destination->get('err_tips'), $destination); + } + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Jsb/AddPayloadSignPlugin.php b/vendor/yansongda/pay/src/Plugin/Jsb/AddPayloadSignPlugin.php new file mode 100644 index 0000000..a3c0295 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Jsb/AddPayloadSignPlugin.php @@ -0,0 +1,65 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('jsb', $params); + $payload = $rocket->getPayload(); + + if (empty($payload) || $payload->isEmpty()) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 缺少支付必要参数。可能插件用错顺序,应该先使用 `业务插件`'); + } + + $privateCertPath = $config['mch_secret_cert_path'] ?? ''; + + if (empty($privateCertPath)) { + throw new InvalidConfigException(Exception::CONFIG_JSB_INVALID, '配置异常: 缺少配置参数 -- [mch_secret_cert_path]'); + } + + $rocket->mergePayload([ + 'signType' => 'RSA', + 'sign' => $this->getSignature(get_private_cert($privateCertPath), $payload), + ]); + + Logger::info('[Jsb][AddPayloadSignPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function getSignature(string $pkey, Collection $payload): string + { + $content = $payload->sortKeys()->toString(); + + openssl_sign($content, $signature, $pkey); + + return base64_encode($signature); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Jsb/AddRadarPlugin.php b/vendor/yansongda/pay/src/Plugin/Jsb/AddRadarPlugin.php new file mode 100644 index 0000000..870382a --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Jsb/AddRadarPlugin.php @@ -0,0 +1,68 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('jsb', $params); + $payload = $rocket->getPayload(); + + $rocket->setRadar(new Request( + strtoupper($params['_method'] ?? 'POST'), + get_jsb_url($config, $payload), + $this->getHeaders(), + $this->getBody($payload), + )); + + Logger::info('[Jsb][AddRadarPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function getHeaders(): array + { + return [ + 'Content-Type' => 'text/html', + 'User-Agent' => 'yansongda/pay-v3', + ]; + } + + protected function getBody(Collection $payload): string + { + $sign = $payload->get('sign'); + $signType = $payload->get('signType'); + + $payload->forget('sign'); + $payload->forget('signType'); + + $payload = $payload->sortKeys(); + + $payload->set('sign', $sign); + $payload->set('signType', $signType); + + return $payload->toString(); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Jsb/CallbackPlugin.php b/vendor/yansongda/pay/src/Plugin/Jsb/CallbackPlugin.php new file mode 100644 index 0000000..bb9940d --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Jsb/CallbackPlugin.php @@ -0,0 +1,70 @@ + $rocket]); + + $this->formatRequestAndParams($rocket); + + $params = $rocket->getParams(); + $config = get_provider_config('jsb', $params); + + $payload = $rocket->getPayload(); + $signature = $payload->get('sign'); + + $payload->forget('sign'); + $payload->forget('signType'); + + verify_jsb_sign($config, $payload->sortKeys()->toString(), $signature); + + $rocket->setDirection(NoHttpRequestDirection::class) + ->setDestination($rocket->getPayload()); + + Logger::info('[Jsb][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + /** + * @throws InvalidParamsException + */ + protected function formatRequestAndParams(Rocket $rocket): void + { + $request = $rocket->getParams()['request'] ?? null; + + if (!$request instanceof Collection) { + throw new InvalidParamsException(Exception::PARAMS_CALLBACK_REQUEST_INVALID); + } + + $rocket->setPayload($request)->setParams($rocket->getParams()['params'] ?? []); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Jsb/Pay/Scan/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Jsb/Pay/Scan/PayPlugin.php new file mode 100644 index 0000000..dc9392d --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Jsb/Pay/Scan/PayPlugin.php @@ -0,0 +1,49 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('jsb', $params); + $backUrl = $rocket->getPayload()['notify_url'] ?? $config['notify_url'] ?? null; + + if (!$backUrl) { + throw new InvalidConfigException(Exception::CONFIG_JSB_INVALID, '配置异常: 缺少配置参数 -- [notify_url]'); + } + + $rocket->mergePayload([ + 'service' => 'atPay', + 'backUrl' => $backUrl, + ]); + + Logger::info('[Jsb][Pay][Scan][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Jsb/Pay/Scan/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Jsb/Pay/Scan/QueryPlugin.php new file mode 100644 index 0000000..47b4e89 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Jsb/Pay/Scan/QueryPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'deviceNo' => '1234567890', + 'service' => 'payCheck', + ]); + + Logger::info('[Jsb][Pay][Scan][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Jsb/Pay/Scan/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Jsb/Pay/Scan/RefundPlugin.php new file mode 100644 index 0000000..e770810 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Jsb/Pay/Scan/RefundPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + 'deviceNo' => '1234567890', + 'service' => 'payRefund', + ]); + + Logger::info('[Jsb][Pay][Scan][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Jsb/ResponsePlugin.php b/vendor/yansongda/pay/src/Plugin/Jsb/ResponsePlugin.php new file mode 100644 index 0000000..023ba95 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Jsb/ResponsePlugin.php @@ -0,0 +1,52 @@ + $rocket]); + + $this->validateResponse($rocket); + + Logger::info('[Jsb][ResponsePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + /** + * @throws InvalidResponseException + */ + protected function validateResponse(Rocket $rocket): void + { + $destination = $rocket->getDestination(); + $destinationOrigin = $rocket->getDestinationOrigin(); + + if ($destinationOrigin instanceof ResponseInterface + && ($destinationOrigin->getStatusCode() < 200 || $destinationOrigin->getStatusCode() >= 300)) { + throw new InvalidResponseException(Exception::RESPONSE_CODE_WRONG, '江苏银行返回状态码异常,请检查参数是否错误', $rocket->getDestination()); + } + + if ($destination instanceof Collection && '000000' !== $destination->get('respCode')) { + throw new InvalidResponseException(Exception::RESPONSE_BUSINESS_CODE_WRONG, sprintf('江苏银行返回错误: respCode:%s respMsg:%s', $destination->get('respCode'), $destination->get('respMsg')), $rocket->getDestination()); + } + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Jsb/StartPlugin.php b/vendor/yansongda/pay/src/Plugin/Jsb/StartPlugin.php new file mode 100644 index 0000000..22a1b12 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Jsb/StartPlugin.php @@ -0,0 +1,49 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('jsb', $params); + + $rocket->setPacker(QueryPacker::class) + ->mergePayload(array_merge($params, [ + 'createData' => date('Ymd'), + 'createTime' => date('His'), + 'bizDate' => date('Ymd'), + 'msgId' => Str::uuidV4(), + 'svrCode' => $config['svr_code'] ?? '', + 'partnerId' => $config['partner_id'] ?? '', + 'channelNo' => 'm', + 'publicKeyCode' => $config['public_key_code'] ?? '', + 'version' => 'v1.0.0', + 'charset' => 'utf-8', + ])); + + Logger::info('[Jsb][StartPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Jsb/VerifySignaturePlugin.php b/vendor/yansongda/pay/src/Plugin/Jsb/VerifySignaturePlugin.php new file mode 100644 index 0000000..635cbf1 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Jsb/VerifySignaturePlugin.php @@ -0,0 +1,76 @@ + $rocket]); + + if (should_do_http_request($rocket->getDirection())) { + $params = $rocket->getParams(); + $config = get_provider_config('jsb', $params); + + $body = (string) $rocket->getDestinationOrigin()->getBody(); + $signatureData = $this->getSignatureData($body); + + verify_jsb_sign($config, $signatureData['data'] ?? '', $signatureData['sign'] ?? ''); + } + + Logger::info('[Jsb][VerifySignaturePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + private function getSignatureData(string $body): array + { + if (Str::contains($body, '&-&')) { + $beginIndex = strpos($body, '&signType='); + $endIndex = strpos($body, '&-&'); + $data = substr($body, 0, $beginIndex).substr($body, $endIndex); + + $signIndex = strpos($body, '&sign='); + $signature = substr($body, $signIndex + strlen('&sign='), $endIndex - ($signIndex + strlen('&sign='))); + } else { + $result = Arr::wrapQuery($body, true); + $result = Collection::wrap($result); + $signature = $result->get('sign'); + $result->forget('sign'); + $result->forget('signType'); + $data = $result->sortKeys()->toString(); + } + + return [ + 'sign' => $signature, + 'data' => $data, + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/AddRadarPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/AddRadarPlugin.php new file mode 100644 index 0000000..4b5b569 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/AddRadarPlugin.php @@ -0,0 +1,55 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $payload = $rocket->getPayload(); + + $rocket->setRadar(new Request( + get_radar_method($payload) ?? 'POST', + get_unipay_url($config, $payload), + $this->getHeaders(), + get_unipay_body($payload), + )); + + Logger::info('[Unipay][AddRadarPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function getHeaders(): array + { + return [ + 'User-Agent' => 'yansongda/pay-v3', + 'Content-Type' => 'application/x-www-form-urlencoded;charset=utf-8', + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/AddPayloadSignaturePlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/AddPayloadSignaturePlugin.php new file mode 100644 index 0000000..62a9585 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/AddPayloadSignaturePlugin.php @@ -0,0 +1,63 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $payload = $rocket->getPayload(); + + if (empty($payload) || $payload->isEmpty()) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 银联支付必要参数缺失。可能插件用错顺序,应该先使用 `业务插件`'); + } + + $rocket->mergePayload([ + 'signature' => $this->getSignature($config['certs']['pkey'] ?? '', filter_params($rocket->getPayload(), fn ($k, $v) => 'signature' != $k)), + ]); + + Logger::info('[Unipay][AddPayloadSignaturePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + /** + * @throws InvalidParamsException + */ + protected function getSignature(string $pkey, Collection $payload): string + { + if (empty($pkey)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 银联支付配置文件中未找到 `certs.pkey` 配置项。可能插件用错顺序,应该先使用 `StartPlugin`'); + } + + $content = $payload->sortKeys()->toString(); + + openssl_sign(hash('sha256', $content), $sign, $pkey, 'sha256'); + + return base64_encode($sign); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/CallbackPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/CallbackPlugin.php new file mode 100644 index 0000000..2fc127b --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/CallbackPlugin.php @@ -0,0 +1,49 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + + $rocket->setPayload($params); + + $collection = filter_params($params)->except('signature')->sortKeys(); + + verify_unipay_sign($config, $collection->toString(), $params['signature'] ?? '', $params['signPubKeyCert'] ?? null); + + $rocket->setDirection(NoHttpRequestDirection::class) + ->setDestination($rocket->getPayload()); + + Logger::info('[Unipay][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/H5/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/H5/PayPlugin.php new file mode 100644 index 0000000..991670b --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/H5/PayPlugin.php @@ -0,0 +1,56 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $payload = $rocket->getPayload(); + + $rocket->setDirection(ResponseDirection::class) + ->mergePayload([ + '_url' => 'gateway/api/frontTransReq.do', + 'encoding' => 'utf-8', + 'signature' => '', + 'bizType' => $payload?->get('bizType') ?? '000201', + 'accessType' => $payload?->get('accessType') ?? '0', + 'currencyCode' => '156', + 'merId' => $config['mch_id'] ?? '', + 'channelType' => $payload?->get('channelType') ?? '07', + 'signMethod' => '01', + 'txnType' => $payload?->get('txnType') ?? '01', + 'txnSubType' => $payload?->get('txnSubType') ?? '01', + 'frontUrl' => $payload?->get('frontUrl') ?? $config['return_url'] ?? '', + 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '', + 'version' => '5.1.0', + ]); + + Logger::info('[Unipay][Pay][H5][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/CancelPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/CancelPlugin.php new file mode 100644 index 0000000..6c82829 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/CancelPlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $payload = $rocket->getPayload(); + + $rocket->setPacker(QueryPacker::class) + ->mergePayload([ + '_url' => 'gateway/api/backTransReq.do', + 'encoding' => 'utf-8', + 'signature' => '', + 'bizType' => $payload?->get('bizType') ?? '000000', + 'accessType' => $payload?->get('accessType') ?? '0', + 'merId' => $config['mch_id'] ?? '', + 'channelType' => $payload?->get('channelType') ?? '08', + 'signMethod' => '01', + 'txnType' => $payload?->get('txnType') ?? '31', + 'txnSubType' => $payload?->get('txnSubType') ?? '00', + 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '', + 'version' => '5.1.0', + ]); + + Logger::info('[Unipay][Pay][QrCode][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/PosPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/PosPlugin.php new file mode 100644 index 0000000..7d361bf --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/PosPlugin.php @@ -0,0 +1,55 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $payload = $rocket->getPayload(); + + $rocket->setPacker(QueryPacker::class) + ->mergePayload([ + '_url' => 'gateway/api/backTransReq.do', + 'encoding' => 'utf-8', + 'signature' => '', + 'bizType' => $payload?->get('bizType') ?? '000000', + 'accessType' => $payload?->get('accessType') ?? '0', + 'merId' => $config['mch_id'] ?? '', + 'currencyCode' => '156', + 'channelType' => $payload?->get('channelType') ?? '08', + 'signMethod' => '01', + 'txnType' => $payload?->get('txnType') ?? '01', + 'txnSubType' => $payload?->get('txnSubType') ?? '06', + 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '', + 'version' => '5.1.0', + ]); + + Logger::info('[Unipay][Pay][QrCode][PosPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/PosPreAuthPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/PosPreAuthPlugin.php new file mode 100644 index 0000000..b3f3f0f --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/PosPreAuthPlugin.php @@ -0,0 +1,55 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $payload = $rocket->getPayload(); + + $rocket->setPacker(QueryPacker::class) + ->mergePayload([ + '_url' => 'gateway/api/backTransReq.do', + 'encoding' => 'utf-8', + 'signature' => '', + 'bizType' => $payload?->get('bizType') ?? '000201', + 'accessType' => $payload?->get('accessType') ?? '0', + 'merId' => $config['mch_id'] ?? '', + 'currencyCode' => '156', + 'channelType' => $payload?->get('channelType') ?? '08', + 'signMethod' => '01', + 'txnType' => $payload?->get('txnType') ?? '02', + 'txnSubType' => $payload?->get('txnSubType') ?? '04', + 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '', + 'version' => '5.1.0', + ]); + + Logger::info('[Unipay][Pay][QrCode][PosPreAuthPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/QueryPlugin.php new file mode 100644 index 0000000..2594d39 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/QueryPlugin.php @@ -0,0 +1,53 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $payload = $rocket->getPayload(); + + $rocket->setPacker(QueryPacker::class) + ->mergePayload([ + '_url' => 'gateway/api/backTransReq.do', + '_sandbox_url' => 'https://101.231.204.80:5000/gateway/api/backTransReq.do', + 'encoding' => 'utf-8', + 'signature' => '', + 'bizType' => $payload?->get('bizType') ?? '000000', + 'accessType' => $payload?->get('accessType') ?? '0', + 'merId' => $config['mch_id'] ?? '', + 'signMethod' => '01', + 'txnType' => $payload?->get('txnType') ?? '00', + 'txnSubType' => $payload?->get('txnSubType') ?? '00', + 'version' => '5.1.0', + ]); + + Logger::info('[Unipay][Pay][QrCode][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/RefundPlugin.php new file mode 100644 index 0000000..ff445db --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/RefundPlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $payload = $rocket->getPayload(); + + $rocket->setPacker(QueryPacker::class) + ->mergePayload([ + '_url' => 'gateway/api/backTransReq.do', + 'encoding' => 'utf-8', + 'signature' => '', + 'bizType' => $payload?->get('bizType') ?? '000000', + 'accessType' => $payload?->get('accessType') ?? '0', + 'merId' => $config['mch_id'] ?? '', + 'channelType' => $payload?->get('channelType') ?? '08', + 'signMethod' => '01', + 'txnType' => $payload?->get('txnType') ?? '04', + 'txnSubType' => $payload?->get('txnSubType') ?? '00', + 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '', + 'version' => '5.1.0', + ]); + + Logger::info('[Unipay][QrCode][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/ScanFeePlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/ScanFeePlugin.php new file mode 100644 index 0000000..a7de9cc --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/ScanFeePlugin.php @@ -0,0 +1,55 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $payload = $rocket->getPayload(); + + $rocket->setPacker(QueryPacker::class) + ->mergePayload([ + '_url' => 'gateway/api/backTransReq.do', + 'signature' => '', + 'bizType' => $payload?->get('bizType') ?? '000601', + 'encoding' => 'utf-8', + 'accessType' => $payload?->get('accessType') ?? '0', + 'merId' => $config['mch_id'] ?? '', + 'currencyCode' => '156', + 'channelType' => $payload?->get('channelType') ?? '07', + 'signMethod' => '01', + 'txnType' => $payload?->get('txnType') ?? '13', + 'txnSubType' => $payload?->get('txnSubType') ?? '08', + 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '', + 'version' => '5.1.0', + ]); + + Logger::info('[Unipay][Pay][QrCode][ScanFeePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/ScanPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/ScanPlugin.php new file mode 100644 index 0000000..5a0e241 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/ScanPlugin.php @@ -0,0 +1,55 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $payload = $rocket->getPayload(); + + $rocket->setPacker(QueryPacker::class) + ->mergePayload([ + '_url' => 'gateway/api/backTransReq.do', + 'encoding' => 'utf-8', + 'signature' => '', + 'bizType' => $payload?->get('bizType') ?? '000000', + 'accessType' => $payload?->get('accessType') ?? '0', + 'merId' => $config['mch_id'] ?? '', + 'currencyCode' => '156', + 'channelType' => $payload?->get('channelType') ?? '08', + 'signMethod' => '01', + 'txnType' => $payload?->get('txnType') ?? '01', + 'txnSubType' => $payload?->get('txnSubType') ?? '07', + 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '', + 'version' => '5.1.0', + ]); + + Logger::info('[Unipay][Pay][QrCode][ScanPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/ScanPreAuthPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/ScanPreAuthPlugin.php new file mode 100644 index 0000000..ee49436 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/ScanPreAuthPlugin.php @@ -0,0 +1,55 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $payload = $rocket->getPayload(); + + $rocket->setPacker(QueryPacker::class) + ->mergePayload([ + '_url' => 'gateway/api/backTransReq.do', + 'encoding' => 'utf-8', + 'signature' => '', + 'bizType' => $payload?->get('bizType') ?? '000000', + 'accessType' => $payload?->get('accessType') ?? '0', + 'merId' => $config['mch_id'] ?? '', + 'currencyCode' => '156', + 'channelType' => $payload?->get('channelType') ?? '08', + 'signMethod' => '01', + 'txnType' => $payload?->get('txnType') ?? '02', + 'txnSubType' => $payload?->get('txnSubType') ?? '05', + 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '', + 'version' => '5.1.0', + ]); + + Logger::info('[Unipay][Pay][QrCode][ScanPreAuthPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/ScanPreOrderPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/ScanPreOrderPlugin.php new file mode 100644 index 0000000..0c44b8b --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/QrCode/ScanPreOrderPlugin.php @@ -0,0 +1,56 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $payload = $rocket->getPayload(); + + $rocket->setPacker(QueryPacker::class) + ->mergePayload([ + '_url' => 'gateway/api/order.do', + 'encoding' => 'utf-8', + 'signature' => '', + 'bizType' => $payload?->get('bizType') ?? '000000', + 'accessType' => $payload?->get('accessType') ?? '0', + 'merId' => $config['mch_id'] ?? '', + 'currencyCode' => '156', + 'channelType' => $payload?->get('channelType') ?? '08', + 'signMethod' => '01', + 'txnType' => $payload?->get('txnType') ?? '01', + 'txnSubType' => $payload?->get('txnSubType') ?? '01', + 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '', + 'version' => '5.1.0', + 'frontUrl' => $payload?->get('frontUrl') ?? $config['return_url'] ?? null, + ]); + + Logger::info('[Unipay][Pay][QrCode][ScanPreOrderPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/Web/CancelPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/Web/CancelPlugin.php new file mode 100644 index 0000000..4379948 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/Web/CancelPlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $payload = $rocket->getPayload(); + + $rocket->setPacker(QueryPacker::class) + ->mergePayload([ + '_url' => 'gateway/api/backTransReq.do', + 'encoding' => 'utf-8', + 'signature' => '', + 'bizType' => $payload?->get('bizType') ?? '000000', + 'accessType' => $payload?->get('accessType') ?? '0', + 'merId' => $config['mch_id'] ?? '', + 'channelType' => $payload?->get('channelType') ?? '07', + 'signMethod' => '01', + 'txnType' => $payload?->get('txnType') ?? '31', + 'txnSubType' => $payload?->get('txnSubType') ?? '00', + 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '', + 'version' => '5.1.0', + ]); + + Logger::info('[Unipay][Pay][Web][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/Web/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/Web/PayPlugin.php new file mode 100644 index 0000000..df20095 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/Web/PayPlugin.php @@ -0,0 +1,58 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $payload = $rocket->getPayload(); + + $rocket->setPacker(QueryPacker::class) + ->setDirection(ResponseDirection::class) + ->mergePayload([ + '_url' => 'gateway/api/frontTransReq.do', + 'encoding' => 'utf-8', + 'signature' => '', + 'bizType' => $payload?->get('bizType') ?? '000201', + 'accessType' => $payload?->get('accessType') ?? '0', + 'merId' => $config['mch_id'] ?? '', + 'currencyCode' => '156', + 'channelType' => $payload?->get('channelType') ?? '07', + 'signMethod' => '01', + 'txnType' => $payload?->get('txnType') ?? '01', + 'txnSubType' => $payload?->get('txnSubType') ?? '01', + 'frontUrl' => $payload?->get('frontUrl') ?? $config['return_url'] ?? '', + 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '', + 'version' => '5.1.0', + ]); + + Logger::info('[Unipay][Pay][Web][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/Web/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/Web/QueryPlugin.php new file mode 100644 index 0000000..e57e372 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/Web/QueryPlugin.php @@ -0,0 +1,52 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $payload = $rocket->getPayload(); + + $rocket->setPacker(QueryPacker::class) + ->mergePayload([ + '_url' => 'gateway/api/queryTrans.do', + 'encoding' => 'utf-8', + 'signature' => '', + 'bizType' => $payload?->get('bizType') ?? '000000', + 'accessType' => $payload?->get('accessType') ?? '0', + 'merId' => $config['mch_id'] ?? '', + 'signMethod' => '01', + 'txnType' => $payload?->get('txnType') ?? '00', + 'txnSubType' => $payload?->get('txnSubType') ?? '00', + 'version' => '5.1.0', + ]); + + Logger::info('[Unipay][Pay][Web][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/Web/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/Web/RefundPlugin.php new file mode 100644 index 0000000..ab7b0b5 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/Pay/Web/RefundPlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $payload = $rocket->getPayload(); + + $rocket->setPacker(QueryPacker::class) + ->mergePayload([ + '_url' => 'gateway/api/backTransReq.do', + 'encoding' => 'utf-8', + 'signature' => '', + 'bizType' => $payload?->get('bizType') ?? '000000', + 'accessType' => $payload?->get('accessType') ?? '0', + 'merId' => $config['mch_id'] ?? '', + 'channelType' => $payload?->get('channelType') ?? '07', + 'signMethod' => '01', + 'txnType' => $payload?->get('txnType') ?? '04', + 'txnSubType' => $payload?->get('txnSubType') ?? '00', + 'backUrl' => $payload?->get('backUrl') ?? $config['notify_url'] ?? '', + 'version' => '5.1.0', + ]); + + Logger::info('[Unipay][Pay][Web][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/ResponseHtmlPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/ResponseHtmlPlugin.php new file mode 100644 index 0000000..339deb0 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/ResponseHtmlPlugin.php @@ -0,0 +1,48 @@ + $rocket]); + + $radar = $rocket->getRadar(); + $payload = $rocket->getPayload(); + + $response = $this->buildHtml($radar->getUri()->__toString(), filter_params($payload)); + + $rocket->setDestination($response); + + Logger::info('[Unipay][ResponseHtmlPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + protected function buildHtml(string $endpoint, Collection $payload): Response + { + $sHtml = "
"; + foreach ($payload->all() as $key => $val) { + $sHtml .= ""; + } + $sHtml .= "
"; + $sHtml .= ""; + + return new Response(200, [], $sHtml); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/StartPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/StartPlugin.php new file mode 100644 index 0000000..ed3252c --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/StartPlugin.php @@ -0,0 +1,91 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $tenant = get_tenant($params); + + $rocket->mergePayload(array_merge($params, [ + '_unpack_raw' => true, + 'certId' => $this->getCertId($tenant, $config), + ])); + + Logger::info('[Unipay][StartPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + /** + * @throws ContainerException + * @throws InvalidConfigException + * @throws ServiceNotFoundException + */ + public function getCertId(string $tenant, array $config): string + { + if (!empty($config['certs']['cert_id'])) { + return $config['certs']['cert_id']; + } + + $certs = $this->getCerts($config); + $ssl = openssl_x509_parse($certs['cert'] ?? ''); + + if (false === $ssl) { + throw new InvalidConfigException(Exception::CONFIG_UNIPAY_INVALID, '配置异常: 解析银联 `mch_cert_path` 失败,请检查参数是否正确'); + } + + $certs['cert_id'] = $ssl['serialNumber'] ?? ''; + + Pay::get(ConfigInterface::class)->set('unipay.'.$tenant.'.certs', $certs); + + return $certs['cert_id']; + } + + /** + * @return array ['cert' => 公钥, 'pkey' => 私钥, 'extracerts' => array] + * + * @throws InvalidConfigException + */ + protected function getCerts(array $config): array + { + $path = $config['mch_cert_path'] ?? null; + $password = $config['mch_cert_password'] ?? null; + + if (is_null($path) || is_null($password)) { + throw new InvalidConfigException(Exception::CONFIG_UNIPAY_INVALID, '配置异常: 缺少银联配置 -- [mch_cert_path] or [mch_cert_password]'); + } + + if (false === openssl_pkcs12_read(file_get_contents($path), $certs, $password)) { + throw new InvalidConfigException(Exception::CONFIG_UNIPAY_INVALID, '配置异常: 读取银联 `mch_cert_path` 失败,请确认参数是否正确'); + } + + return $certs; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Open/VerifySignaturePlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Open/VerifySignaturePlugin.php new file mode 100644 index 0000000..aca6db4 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Open/VerifySignaturePlugin.php @@ -0,0 +1,60 @@ + $rocket]); + + if (!should_do_http_request($rocket->getDirection())) { + return $rocket; + } + + $destination = $rocket->getDestination(); + + if (!$destination instanceof Collection) { + return $rocket; + } + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + + verify_unipay_sign( + $config, + $destination->except('signature')->sortKeys()->toString(), + $destination->get('signature', ''), + $destination->get('signPubKeyCert') + ); + + Logger::info('[Unipay][VerifySignaturePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Qra/AddPayloadSignaturePlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/AddPayloadSignaturePlugin.php new file mode 100644 index 0000000..7c205e7 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/AddPayloadSignaturePlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $payload = $rocket->getPayload(); + + if (empty($payload) || $payload->isEmpty()) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 银联支付必要参数缺失。可能插件用错顺序,应该先使用 `业务插件`'); + } + + $rocket->mergePayload(['sign' => get_unipay_sign_qra($config, filter_params($payload)->all())]); + + Logger::info('[Unipay][Qra][AddPayloadSignaturePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Qra/CallbackPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/CallbackPlugin.php new file mode 100644 index 0000000..3cc00ff --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/CallbackPlugin.php @@ -0,0 +1,49 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $destination = filter_params($params); + + if (isset($params['status']) && 0 == $params['status']) { + verify_unipay_sign_qra($config, $destination->all()); + } + + $rocket->setPayload($params) + ->setDirection(NoHttpRequestDirection::class) + ->setDestination($destination); + + Logger::info('[Unipay][Qra][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Pos/CancelPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Pos/CancelPlugin.php new file mode 100644 index 0000000..ecfe1b9 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Pos/CancelPlugin.php @@ -0,0 +1,50 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + + $rocket->setPacker(XmlPacker::class) + ->mergePayload([ + '_url' => 'https://qra.95516.com/pay/gateway', + 'service' => 'unified.micropay.reverse', + 'charset' => 'UTF-8', + 'sign_type' => 'MD5', + 'mch_id' => $config['mch_id'] ?? '', + 'nonce_str' => Str::random(32), + ]); + + Logger::info('[Unipay][Qra][Pos][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Pos/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Pos/PayPlugin.php new file mode 100644 index 0000000..5ae7815 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Pos/PayPlugin.php @@ -0,0 +1,50 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + + $rocket->setPacker(XmlPacker::class) + ->mergePayload([ + '_url' => 'https://qra.95516.com/pay/gateway', + 'service' => 'unified.trade.micropay', + 'charset' => 'UTF-8', + 'sign_type' => 'MD5', + 'mch_id' => $config['mch_id'] ?? '', + 'nonce_str' => Str::random(32), + ]); + + Logger::info('[Unipay][Qra][Pos][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Pos/QueryOpenIdPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Pos/QueryOpenIdPlugin.php new file mode 100644 index 0000000..d6dffd2 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Pos/QueryOpenIdPlugin.php @@ -0,0 +1,50 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + + $rocket->setPacker(XmlPacker::class) + ->mergePayload([ + '_url' => 'https://qra.95516.com/pay/gateway', + 'service' => 'unified.tools.authcodetoopenid', + 'charset' => 'UTF-8', + 'sign_type' => 'MD5', + 'mch_id' => $config['mch_id'] ?? '', + 'nonce_str' => Str::random(32), + ]); + + Logger::info('[Unipay][Qra][Pos][QueryOpenIdPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Pos/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Pos/QueryPlugin.php new file mode 100644 index 0000000..c8aebab --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Pos/QueryPlugin.php @@ -0,0 +1,50 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + + $rocket->setPacker(XmlPacker::class) + ->mergePayload([ + '_url' => 'https://qra.95516.com/pay/gateway', + 'service' => 'unified.trade.query', + 'charset' => 'UTF-8', + 'sign_type' => 'MD5', + 'mch_id' => $config['mch_id'] ?? '', + 'nonce_str' => Str::random(32), + ]); + + Logger::info('[Unipay][Qra][Pos][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Pos/QueryRefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Pos/QueryRefundPlugin.php new file mode 100644 index 0000000..7d9280a --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Pos/QueryRefundPlugin.php @@ -0,0 +1,50 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + + $rocket->setPacker(XmlPacker::class) + ->mergePayload([ + '_url' => 'https://qra.95516.com/pay/gateway', + 'service' => 'unified.trade.refundquery', + 'charset' => 'UTF-8', + 'sign_type' => 'MD5', + 'mch_id' => $config['mch_id'] ?? '', + 'nonce_str' => Str::random(32), + ]); + + Logger::info('[Unipay][Qra][Pos][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Pos/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Pos/RefundPlugin.php new file mode 100644 index 0000000..58096b9 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Pos/RefundPlugin.php @@ -0,0 +1,50 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + + $rocket->setPacker(XmlPacker::class) + ->mergePayload([ + '_url' => 'https://qra.95516.com/pay/gateway', + 'service' => 'unified.trade.refund', + 'charset' => 'UTF-8', + 'sign_type' => 'MD5', + 'mch_id' => $config['mch_id'] ?? '', + 'nonce_str' => Str::random(32), + ]); + + Logger::info('[Unipay][Qra][Pos][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Scan/ClosePlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Scan/ClosePlugin.php new file mode 100644 index 0000000..c6e486f --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Scan/ClosePlugin.php @@ -0,0 +1,50 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + + $rocket->setPacker(XmlPacker::class) + ->mergePayload([ + '_url' => 'https://qra.95516.com/pay/gateway', + 'service' => 'unified.trade.close', + 'charset' => 'UTF-8', + 'sign_type' => 'MD5', + 'mch_id' => $config['mch_id'] ?? '', + 'nonce_str' => Str::random(32), + ]); + + Logger::info('[Unipay][Qra][Scan][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Scan/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Scan/PayPlugin.php new file mode 100644 index 0000000..ea253de --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Scan/PayPlugin.php @@ -0,0 +1,52 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + $payload = $rocket->getPayload(); + + $rocket->setPacker(XmlPacker::class) + ->mergePayload([ + '_url' => 'https://qra.95516.com/pay/gateway', + 'service' => 'unified.trade.native', + 'charset' => 'UTF-8', + 'sign_type' => 'MD5', + 'mch_id' => $config['mch_id'] ?? '', + 'notify_url' => $payload?->get('notify_url') ?? $config['notify_url'] ?? '', + 'nonce_str' => Str::random(32), + ]); + + Logger::info('[Unipay][Qra][Scan][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Scan/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Scan/QueryPlugin.php new file mode 100644 index 0000000..bff614d --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Scan/QueryPlugin.php @@ -0,0 +1,50 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + + $rocket->setPacker(XmlPacker::class) + ->mergePayload([ + '_url' => 'https://qra.95516.com/pay/gateway', + 'service' => 'unified.trade.query', + 'charset' => 'UTF-8', + 'sign_type' => 'MD5', + 'mch_id' => $config['mch_id'] ?? '', + 'nonce_str' => Str::random(32), + ]); + + Logger::info('[Unipay][Qra][Scan][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Scan/QueryRefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Scan/QueryRefundPlugin.php new file mode 100644 index 0000000..9458bbe --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Scan/QueryRefundPlugin.php @@ -0,0 +1,50 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + + $rocket->setPacker(XmlPacker::class) + ->mergePayload([ + '_url' => 'https://qra.95516.com/pay/gateway', + 'service' => 'unified.trade.refundquery', + 'charset' => 'UTF-8', + 'sign_type' => 'MD5', + 'mch_id' => $config['mch_id'] ?? '', + 'nonce_str' => Str::random(32), + ]); + + Logger::info('[Unipay][Qra][Scan][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Scan/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Scan/RefundPlugin.php new file mode 100644 index 0000000..d0c57b8 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/Scan/RefundPlugin.php @@ -0,0 +1,50 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('unipay', $params); + + $rocket->setPacker(XmlPacker::class) + ->mergePayload([ + '_url' => 'https://qra.95516.com/pay/gateway', + 'service' => 'unified.trade.refund', + 'charset' => 'UTF-8', + 'sign_type' => 'MD5', + 'mch_id' => $config['mch_id'] ?? '', + 'nonce_str' => Str::random(32), + ]); + + Logger::info('[Unipay][Qra][Scan][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Qra/StartPlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/StartPlugin.php new file mode 100644 index 0000000..bc0339a --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/StartPlugin.php @@ -0,0 +1,26 @@ + $rocket]); + + $params = $rocket->getParams(); + + $rocket->mergePayload($params); + + Logger::info('[Unipay][Qra][StartPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Unipay/Qra/VerifySignaturePlugin.php b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/VerifySignaturePlugin.php new file mode 100644 index 0000000..5f86622 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Unipay/Qra/VerifySignaturePlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $config = get_provider_config('unipay', $rocket->getParams()); + + if (!should_do_http_request($rocket->getDirection())) { + return $rocket; + } + + verify_unipay_sign_qra($config, $rocket->getDestination()?->all() ?? []); + + Logger::info('[Unipay][Qra][VerifySignaturePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/AddRadarPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/AddRadarPlugin.php new file mode 100644 index 0000000..9b085ef --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/AddRadarPlugin.php @@ -0,0 +1,76 @@ + $rocket]); + + $params = $rocket->getParams(); + $payload = $rocket->getPayload(); + $config = get_provider_config('wechat', $params); + + $rocket->setRadar(new Request( + get_wechat_method($payload), + get_wechat_url($config, $payload), + $this->getHeaders($payload), + get_wechat_body($payload), + )); + + Logger::info('[Wechat][AddRadarPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function getHeaders(?Collection $payload): array + { + $headers = [ + 'Accept' => 'application/json, text/plain, application/x-gzip', + 'User-Agent' => 'yansongda/pay-v3', + 'Content-Type' => 'application/json; charset=utf-8', + ]; + + // 当 body 里有加密内容时,需要传递此参数用于微信区分 + if (!empty($serialNo = $payload?->get('_serial_no'))) { + $headers['Wechatpay-Serial'] = $serialNo; + } + + if (!empty($authorization = $payload?->get('_authorization'))) { + $headers['Authorization'] = $authorization; + } + + if (!empty($contentType = $payload?->get('_content_type'))) { + $headers['Content-Type'] = $contentType; + } + + if (!empty($accept = $payload?->get('_accept'))) { + $headers['Accept'] = $accept; + } + + return $headers; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/ResponsePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/ResponsePlugin.php new file mode 100644 index 0000000..f284dc1 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/ResponsePlugin.php @@ -0,0 +1,46 @@ + $rocket]); + + $this->validateResponse($rocket); + + Logger::info('[Wechat][ResponsePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + /** + * @throws InvalidResponseException + */ + protected function validateResponse(Rocket $rocket): void + { + $response = $rocket->getDestinationOrigin(); + + if ($response instanceof ResponseInterface + && ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300)) { + throw new InvalidResponseException(Exception::RESPONSE_CODE_WRONG, '微信返回状态码异常,请检查参数是否错误', $rocket->getDestination()); + } + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/StartPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/StartPlugin.php new file mode 100644 index 0000000..101deee --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/StartPlugin.php @@ -0,0 +1,26 @@ + $rocket]); + + $rocket->mergePayload($rocket->getParams()); + + Logger::info('[Wechat][StartPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V2/AddPayloadSignaturePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V2/AddPayloadSignaturePlugin.php new file mode 100644 index 0000000..9bc0d31 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V2/AddPayloadSignaturePlugin.php @@ -0,0 +1,40 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + + $rocket->mergePayload([ + 'sign' => get_wechat_sign_v2($config, filter_params($rocket->getPayload())->all()), + ]); + + Logger::info('[Wechat][V2][AddPayloadSignaturePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V2/Papay/Direct/ApplyPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V2/Papay/Direct/ApplyPlugin.php new file mode 100644 index 0000000..36bc989 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V2/Papay/Direct/ApplyPlugin.php @@ -0,0 +1,53 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + $rocket->setPacker(XmlPacker::class) + ->mergePayload([ + '_url' => 'pay/pappayapply', + '_content_type' => 'application/xml', + 'appid' => $config[get_wechat_type_key($params)] ?? '', + 'mch_id' => $config['mch_id'] ?? '', + 'nonce_str' => Str::random(32), + 'sign_type' => 'MD5', + 'notify_url' => $payload?->get('notify_url') ?? $config['notify_url'] ?? '', + ]); + + Logger::info('[Wechat][V2][Papay][Direct][ApplyPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V2/Papay/Direct/ContractOrderPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V2/Papay/Direct/ContractOrderPlugin.php new file mode 100644 index 0000000..fb7c1d5 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V2/Papay/Direct/ContractOrderPlugin.php @@ -0,0 +1,56 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + $rocket->setPacker(XmlPacker::class) + ->mergePayload([ + '_url' => 'pay/contractorder', + '_content_type' => 'application/xml', + 'appid' => $config[get_wechat_type_key($params)] ?? '', + 'mch_id' => $config['mch_id'] ?? '', + 'contract_appid' => $config[get_wechat_type_key($params)] ?? '', + 'contract_mchid' => $config['mch_id'] ?? '', + 'nonce_str' => Str::random(32), + 'sign_type' => 'MD5', + 'notify_url' => $payload?->get('notify_url') ?? $config['notify_url'] ?? '', + 'contract_notify_url' => $payload?->get('contract_notify_url') ?? $config['notify_url'] ?? '', + ]); + + Logger::info('[Wechat][V2][Papay][Direct][ContractOrderPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V2/Papay/Direct/MiniOnlyContractPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V2/Papay/Direct/MiniOnlyContractPlugin.php new file mode 100644 index 0000000..7f30d7a --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V2/Papay/Direct/MiniOnlyContractPlugin.php @@ -0,0 +1,52 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + $rocket->setDirection(NoHttpRequestDirection::class) + ->mergePayload([ + 'appid' => $config[get_wechat_type_key($params)] ?? '', + 'mch_id' => $config['mch_id'] ?? '', + 'notify_url' => $payload?->get('notify_url') ?? $config['notify_url'] ?? '', + 'timestamp' => time(), + ]); + + Logger::info('[Wechat][V2][Papay][Direct][OnlyContractPlugin] 插件装载完毕', ['rocket' => $rocket]); + + /** @var Rocket $rocket */ + $rocket = $next($rocket); + + $rocket->setDestination($rocket->getPayload()); + + return $rocket; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V2/Pay/App/InvokePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V2/Pay/App/InvokePlugin.php new file mode 100644 index 0000000..b30e2cf --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V2/Pay/App/InvokePlugin.php @@ -0,0 +1,97 @@ + $rocket]); + + $destination = $rocket->getDestination(); + $prepayId = $destination?->get('prepay_id') ?? null; + + if (is_null($prepayId)) { + Logger::error('[Wechat][V2][Pay][App][InvokePlugin] 预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + + throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('message') ?? '预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + } + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + $rocket->setDestination($this->getInvokeConfig($payload, $config, $prepayId)); + + Logger::info('[Wechat][V2][Pay][App][InvokePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + /** + * @throws InvalidConfigException + * @throws Throwable 生成随机串失败 + */ + protected function getInvokeConfig(?Collection $payload, array $config, string $prepayId): Config + { + $invokeConfig = new Config([ + 'appid' => $this->getAppId($payload, $config), + 'partnerid' => $this->getPartnerId($payload, $config), + 'prepayid' => $prepayId, + 'package' => 'Sign=WXPay', + 'noncestr' => Str::random(32), + 'timestamp' => time().'', + ]); + + $invokeConfig->set('sign', get_wechat_sign_v2($config, $invokeConfig->all())); + + return $invokeConfig; + } + + protected function getAppId(?Collection $payload, array $config): string + { + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + return $payload?->get('_invoke_appid') ?? $config['sub_app_id'] ?? ''; + } + + return $payload?->get('_invoke_appid') ?? $config['app_id'] ?? ''; + } + + protected function getPartnerId(?Collection $payload, array $config): string + { + return $payload?->get('_invoke_partnerid') ?? $config['mch_id'] ?? ''; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V2/Pay/Mini/InvokePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V2/Pay/Mini/InvokePlugin.php new file mode 100644 index 0000000..992fc72 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V2/Pay/Mini/InvokePlugin.php @@ -0,0 +1,91 @@ + $rocket]); + + $destination = $rocket->getDestination(); + $prepayId = $destination?->get('prepay_id') ?? null; + + if (is_null($prepayId)) { + Logger::error('[Wechat][V2][Pay][Mini][InvokePlugin] 预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + + throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('message') ?? '预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + } + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + $rocket->setDestination($this->getInvokeConfig($payload, $config, $prepayId)); + + Logger::info('[Wechat][V2][Pay][Mini][InvokePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + /** + * @throws InvalidConfigException + * @throws Throwable 生成随机串失败 + */ + protected function getInvokeConfig(?Collection $payload, array $config, string $prepayId): Config + { + $invokeConfig = new Config([ + 'appId' => $this->getAppId($payload, $config), + 'timeStamp' => time().'', + 'nonceStr' => Str::random(32), + 'package' => 'prepay_id='.$prepayId, + 'signType' => 'MD5', + ]); + + $invokeConfig->set('paySign', get_wechat_sign_v2($config, $invokeConfig->all())); + + return $invokeConfig; + } + + protected function getAppId(?Collection $payload, array $config): string + { + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + return $payload?->get('_invoke_appid') ?? $config['sub_mini_app_id'] ?? ''; + } + + return $payload?->get('_invoke_appid') ?? $config['mini_app_id'] ?? ''; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V2/Pay/Pos/CancelPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V2/Pay/Pos/CancelPlugin.php new file mode 100644 index 0000000..831cf4b --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V2/Pay/Pos/CancelPlugin.php @@ -0,0 +1,51 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + + $rocket->setPacker(XmlPacker::class) + ->mergePayload([ + '_url' => 'secapi/pay/reverse', + '_content_type' => 'application/xml', + 'appid' => $config[get_wechat_type_key($params)] ?? '', + 'mch_id' => $config['mch_id'] ?? '', + 'nonce_str' => Str::random(32), + 'sign_type' => 'MD5', + ]); + + Logger::info('[Wechat][V2][Pay][Pos][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V2/Pay/Pos/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V2/Pay/Pos/PayPlugin.php new file mode 100644 index 0000000..e3961f0 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V2/Pay/Pos/PayPlugin.php @@ -0,0 +1,51 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + + $rocket->setPacker(XmlPacker::class) + ->mergePayload([ + '_url' => 'pay/micropay', + '_content_type' => 'application/xml', + 'appid' => $config[get_wechat_type_key($params)] ?? '', + 'mch_id' => $config['mch_id'] ?? '', + 'nonce_str' => Str::random(32), + 'sign_type' => 'MD5', + ]); + + Logger::info('[Wechat][V2][Pay][Pos][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V2/Pay/Pos/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V2/Pay/Pos/QueryPlugin.php new file mode 100644 index 0000000..0262578 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V2/Pay/Pos/QueryPlugin.php @@ -0,0 +1,51 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + + $rocket->setPacker(XmlPacker::class) + ->mergePayload([ + '_url' => 'pay/orderquery', + '_content_type' => 'application/xml', + 'appid' => $config[get_wechat_type_key($params)] ?? '', + 'mch_id' => $config['mch_id'] ?? '', + 'nonce_str' => Str::random(32), + 'sign_type' => 'MD5', + ]); + + Logger::info('[Wechat][V2][Pay][Pos][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V2/Pay/Redpack/SendPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V2/Pay/Redpack/SendPlugin.php new file mode 100644 index 0000000..453c6c2 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V2/Pay/Redpack/SendPlugin.php @@ -0,0 +1,81 @@ + $rocket]); + $payload = $rocket->getPayload(); + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config, $params); + } + + $rocket->setPacker(XmlPacker::class) + ->mergePayload(array_merge( + [ + '_url' => 'mmpaymkttransfers/sendredpack', + '_content_type' => 'application/xml', + 'nonce_str' => Str::random(32), + '_http' => [ + 'ssl_key' => $config['mch_secret_cert'], + 'cert' => $config['mch_public_cert_path'], + ], + ], + $data ?? $this->normal($config, $params) + )); + + Logger::info('[Wechat][V2][Pay][Pos][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config, array $params): array + { + return [ + 'wxappid' => $config[get_wechat_type_key($params)] ?? '', + 'mch_id' => $config['mch_id'] ?? '', + ]; + } + + protected function service(Collection $payload, array $config, array $params): array + { + $wechatTypeKey = get_wechat_type_key($params); + + return [ + 'wxappid' => $config[$wechatTypeKey] ?? '', + 'mch_id' => $config['mch_id'] ?? '', + 'sub_mch_id' => $payload->get('sub_mch_id', $config['sub_mch_id'] ?? ''), + 'msgappid' => $config['sub_'.$wechatTypeKey], + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V2/VerifySignaturePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V2/VerifySignaturePlugin.php new file mode 100644 index 0000000..9c7031e --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V2/VerifySignaturePlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + + if (!should_do_http_request($rocket->getDirection())) { + return $rocket; + } + + verify_wechat_sign_v2($config, $rocket->getDestination()?->all() ?? []); + + Logger::info('[Wechat][V2][VerifySignaturePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/AddPayloadSignaturePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/AddPayloadSignaturePlugin.php new file mode 100644 index 0000000..06d365b --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/AddPayloadSignaturePlugin.php @@ -0,0 +1,99 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + $payload = $rocket->getPayload(); + + $timestamp = time(); + $random = Str::random(32); + $signContent = $this->getSignatureContent($config, $payload, $timestamp, $random); + $signature = $this->getSignature($config, $timestamp, $random, $signContent); + + $rocket->mergePayload(['_authorization' => $signature]); + + Logger::info('[Wechat][V3][AddPayloadSignaturePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + /** + * @throws InvalidParamsException + */ + protected function getSignatureContent(array $config, ?Collection $payload, int $timestamp, string $random): string + { + $url = get_wechat_url($config, $payload); + $urlPath = parse_url($url, PHP_URL_PATH); + $urlQuery = parse_url($url, PHP_URL_QUERY); + + return get_wechat_method($payload)."\n" + .$urlPath.(empty($urlQuery) ? '' : '?'.$urlQuery)."\n" + .$timestamp."\n" + .$random."\n" + .get_wechat_body($payload)."\n"; + } + + /** + * @throws InvalidConfigException + */ + protected function getSignature(array $config, int $timestamp, string $random, string $contents): string + { + $mchPublicCertPath = $config['mch_public_cert_path'] ?? null; + + if (empty($mchPublicCertPath)) { + throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 缺少微信配置 -- [mch_public_cert_path]'); + } + + $ssl = openssl_x509_parse(get_public_cert($mchPublicCertPath)); + + if (empty($ssl['serialNumberHex'])) { + throw new InvalidConfigException(Exception::CONFIG_WECHAT_INVALID, '配置异常: 解析微信配置 [mch_public_cert_path] 出错'); + } + + $auth = sprintf( + 'mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', + $config['mch_id'] ?? '', + $random, + $timestamp, + $ssl['serialNumberHex'], + get_wechat_sign($config, $contents), + ); + + return 'WECHATPAY2-SHA256-RSA2048 '.$auth; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/CallbackPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/CallbackPlugin.php new file mode 100644 index 0000000..107924c --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/CallbackPlugin.php @@ -0,0 +1,76 @@ + $rocket]); + + $this->init($rocket); + + $params = $rocket->getParams(); + + /* @phpstan-ignore-next-line */ + verify_wechat_sign($rocket->getDestinationOrigin(), $params); + + $body = json_decode((string) $rocket->getDestination()->getBody(), true); + + $rocket->setDirection(NoHttpRequestDirection::class)->setPayload(new Collection($body)); + + $body['resource'] = decrypt_wechat_resource($body['resource'] ?? [], get_provider_config('wechat', $params)); + + $rocket->setDestination(new Collection($body)); + + Logger::info('[Wechat][V3][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + /** + * @throws InvalidParamsException + */ + protected function init(Rocket $rocket): void + { + $request = $rocket->getParams()['_request'] ?? null; + $params = $rocket->getParams()['_params'] ?? []; + + if (!$request instanceof ServerRequestInterface) { + throw new InvalidParamsException(Exception::PARAMS_CALLBACK_REQUEST_INVALID, '参数异常: 微信回调参数不正确'); + } + + $rocket->setDestination(clone $request) + ->setDestinationOrigin($request) + ->setParams($params); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/CompletePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/CompletePlugin.php new file mode 100644 index 0000000..9e6aeb6 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/CompletePlugin.php @@ -0,0 +1,52 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + $payload = $rocket->getPayload(); + $complaintId = $payload?->get('complaint_id') ?? null; + + if (empty($complaintId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 反馈处理完成,参数缺少 `complaint_id`'); + } + + $rocket->setPayload([ + '_method' => 'POST', + '_url' => 'v3/merchant-service/complaints-v2/'.$complaintId.'/complete', + '_service_url' => 'v3/merchant-service/complaints-v2/'.$complaintId.'/complete', + 'complainted_mchid' => $payload->get('complainted_mchid') ?? $config['mch_id'], + ]); + + Logger::info('[Wechat][Extend][Complaints][CompletePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/DeleteCallbackPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/DeleteCallbackPlugin.php new file mode 100644 index 0000000..4a29260 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/DeleteCallbackPlugin.php @@ -0,0 +1,32 @@ + $rocket]); + + $rocket->setPayload([ + '_method' => 'DELETE', + '_url' => 'v3/merchant-service/complaint-notifications', + '_service_url' => 'v3/merchant-service/complaint-notifications', + ]); + + Logger::info('[Wechat][Extend][Complaints][DeleteCallbackPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/QueryCallbackPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/QueryCallbackPlugin.php new file mode 100644 index 0000000..aa636ce --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/QueryCallbackPlugin.php @@ -0,0 +1,32 @@ + $rocket]); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/merchant-service/complaint-notifications', + '_service_url' => 'v3/merchant-service/complaint-notifications', + ]); + + Logger::info('[Wechat][Extend][Complaints][QueryCallbackPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/QueryDetailPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/QueryDetailPlugin.php new file mode 100644 index 0000000..5764716 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/QueryDetailPlugin.php @@ -0,0 +1,72 @@ + $rocket]); + + $complaintId = $rocket->getPayload()?->get('complaint_id') ?? null; + + if (empty($complaintId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询投诉单详情,参数缺少 `complaint_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/merchant-service/complaints-v2/'.$complaintId, + '_service_url' => 'v3/merchant-service/complaints-v2/'.$complaintId, + ]); + + Logger::info('[Wechat][Extend][Complaints][QueryDetailPlugin] 插件装载完毕', ['rocket' => $rocket]); + + /** @var Rocket $rocket */ + $rocket = $next($rocket); + + Logger::debug('[Wechat][Extend][Complaints][QueryDetailPlugin] 插件开始后置装载', ['rocket' => $rocket]); + + $destination = $rocket->getDestination(); + + if ($destination instanceof Collection && !empty($payerPhone = $destination->get('payer_phone'))) { + $decryptPayerPhone = decrypt_wechat_contents($payerPhone, get_provider_config('wechat', $rocket->getParams())); + + if (empty($decryptPayerPhone)) { + throw new InvalidConfigException(Exception::DECRYPT_WECHAT_ENCRYPTED_CONTENTS_INVALID, '参数异常: 查询投诉单详情,参数 `payer_phone` 解密失败'); + } + + $destination->set('payer_phone', $decryptPayerPhone); + } + + Logger::debug('[Wechat][Extend][Complaints][QueryDetailPlugin] 插件后置装载完毕', ['rocket' => $rocket]); + + return $rocket; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/QueryImagePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/QueryImagePlugin.php new file mode 100644 index 0000000..ab22edb --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/QueryImagePlugin.php @@ -0,0 +1,43 @@ + $rocket]); + + $mediaId = $rocket->getPayload()?->get('media_id') ?? null; + + if (empty($mediaId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 图片请求接口,参数缺少 `media_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/merchant-service/images/'.$mediaId, + '_service_url' => 'v3/merchant-service/images/'.$mediaId, + ]); + + Logger::info('[Wechat][Extend][Complaints][QueryImagePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/QueryNegotiationPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/QueryNegotiationPlugin.php new file mode 100644 index 0000000..43a771e --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/QueryNegotiationPlugin.php @@ -0,0 +1,56 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $complaintId = $payload?->get('complaint_id') ?? null; + + if (empty($complaintId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询投诉单协商历史,参数缺少 `complaint_id`'); + } + + $query = $this->normal($payload); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/merchant-service/complaints-v2/'.$complaintId.'/negotiation-historys'.$query, + '_service_url' => 'v3/merchant-service/complaints-v2/'.$complaintId.'/negotiation-historys'.$query, + ]); + + Logger::info('[Wechat][Extend][Complaints][QueryNegotiationPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(Collection $payload): string + { + $query = filter_params($payload)->except('complaint_id')->query(); + + return empty($query) ? '' : '?'.$query; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/QueryPlugin.php new file mode 100644 index 0000000..190eb3d --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/QueryPlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询投诉单列表,缺少必要参数'); + } + + $query = filter_params($payload)->query(); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/merchant-service/complaints-v2?'.$query, + '_service_url' => 'v3/merchant-service/complaints-v2?'.$query, + ]); + + Logger::info('[Wechat][Extend][Complaints][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/ResponsePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/ResponsePlugin.php new file mode 100644 index 0000000..6ed6050 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/ResponsePlugin.php @@ -0,0 +1,52 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + $payload = $rocket->getPayload(); + $complaintId = $payload?->get('complaint_id') ?? null; + + if (empty($complaintId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 回复用户,参数缺少 `complaint_id`'); + } + + $rocket->mergePayload([ + '_method' => 'POST', + '_url' => 'v3/merchant-service/complaints-v2/'.$complaintId.'/response', + '_service_url' => 'v3/merchant-service/complaints-v2/'.$complaintId.'/response', + 'complainted_mchid' => $payload->get('complainted_mchid', $config['mch_id']), + ])->exceptPayload('complaint_id'); + + Logger::info('[Wechat][Extend][Complaints][ResponsePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/SetCallbackPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/SetCallbackPlugin.php new file mode 100644 index 0000000..cc366fa --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/SetCallbackPlugin.php @@ -0,0 +1,35 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + $rocket->setPayload([ + '_method' => 'POST', + '_url' => 'v3/merchant-service/complaint-notifications', + '_service_url' => 'v3/merchant-service/complaint-notifications', + 'url' => $payload?->get('url', '') ?? '', + ]); + + Logger::info('[Wechat][Extend][Complaints][SetCallbackPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/UpdateCallbackPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/UpdateCallbackPlugin.php new file mode 100644 index 0000000..06a41e5 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/UpdateCallbackPlugin.php @@ -0,0 +1,35 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + $rocket->setPayload([ + '_method' => 'PUT', + '_url' => 'v3/merchant-service/complaint-notifications', + '_service_url' => 'v3/merchant-service/complaint-notifications', + 'url' => $payload?->get('url', '') ?? '', + ]); + + Logger::info('[Wechat][Extend][Complaints][UpdateCallbackPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/UpdateRefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/UpdateRefundPlugin.php new file mode 100644 index 0000000..6dcf317 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/Complaints/UpdateRefundPlugin.php @@ -0,0 +1,43 @@ + $rocket]); + + $complaintId = $rocket->getPayload()?->get('complaint_id') ?? null; + + if (empty($complaintId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 更新退款审批结果,参数缺少 `complaint_id`'); + } + + $rocket->mergePayload([ + '_method' => 'POST', + '_url' => 'v3/merchant-service/complaints-v2/'.$complaintId.'/update-refund-progress', + '_service_url' => 'v3/merchant-service/complaints-v2/'.$complaintId.'/update-refund-progress', + ])->exceptPayload('complaint_id'); + + Logger::info('[Wechat][Extend][Complaints][UpdateRefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/AddReceiverPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/AddReceiverPlugin.php new file mode 100644 index 0000000..f3877c5 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/AddReceiverPlugin.php @@ -0,0 +1,134 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 缺少添加分账接收方参数'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $params, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/profitsharing/receivers/add', + '_service_url' => 'v3/profitsharing/receivers/add', + ], + $data ?? $this->normal($payload, $params, $config), + )); + + Logger::info('[Wechat][Extend][ProfitSharing][AddReceiverPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + /** + * @throws ContainerException + * @throws DecryptException + * @throws InvalidConfigException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + protected function normal(Collection $payload, array $params, array $config): array + { + $data = [ + 'appid' => $config[get_wechat_type_key($params)] ?? '', + ]; + + if (!$payload->has('name')) { + return $data; + } + + return array_merge($data, $this->encryptSensitiveData($params, $config, $payload)); + } + + /** + * @throws ContainerException + * @throws DecryptException + * @throws InvalidConfigException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + protected function service(Collection $payload, array $params, array $config): array + { + $wechatTypeKey = get_wechat_type_key($params); + + $data = [ + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + 'appid' => $config[$wechatTypeKey] ?? '', + ]; + + if ('PERSONAL_SUB_OPENID' === $payload->get('type')) { + $data['sub_appid'] = $config['sub_'.$wechatTypeKey] ?? ''; + } + + if (!$payload->has('name')) { + return $data; + } + + return array_merge($data, $this->encryptSensitiveData($params, $config, $payload)); + } + + /** + * @throws ContainerException + * @throws DecryptException + * @throws InvalidConfigException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + protected function encryptSensitiveData(array $params, array $config, Collection $payload): array + { + $data['_serial_no'] = get_wechat_serial_no($params); + + $config = get_provider_config('wechat', $params); + $publicKey = get_wechat_public_key($config, $data['_serial_no']); + + $data['name'] = encrypt_wechat_contents($payload->get('name'), $publicKey); + + return $data; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/CreatePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/CreatePlugin.php new file mode 100644 index 0000000..07f9da7 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/CreatePlugin.php @@ -0,0 +1,137 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 缺少请求分账参数'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $params, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/profitsharing/orders', + '_service_url' => 'v3/profitsharing/orders', + ], + $data ?? $this->normal($payload, $params, $config), + )); + + Logger::info('[Wechat][Extend][ProfitSharing][CreatePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + /** + * @throws ContainerException + * @throws DecryptException + * @throws InvalidConfigException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + protected function normal(Collection $payload, array $params, array $config): array + { + $data = [ + 'appid' => $config[get_wechat_type_key($params)] ?? '', + ]; + + if (!$payload->has('receivers.0.name')) { + return $data; + } + + return array_merge($data, $this->encryptSensitiveData($params, $payload)); + } + + /** + * @throws ContainerException + * @throws DecryptException + * @throws InvalidConfigException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + protected function service(Collection $payload, array $params, array $config): array + { + $wechatTypeKey = get_wechat_type_key($params); + + $data = [ + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + 'appid' => $config[$wechatTypeKey] ?? '', + ]; + + if ('PERSONAL_SUB_OPENID' === $payload->get('receivers.0.type')) { + $data['sub_appid'] = $config['sub_'.$wechatTypeKey] ?? ''; + } + + if (!$payload->has('receivers.0.name')) { + return $data; + } + + return array_merge($data, $this->encryptSensitiveData($params, $payload)); + } + + /** + * @throws ContainerException + * @throws DecryptException + * @throws InvalidConfigException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + protected function encryptSensitiveData(array $params, Collection $payload): array + { + $data['receivers'] = $payload->get('receivers', []); + $data['_serial_no'] = get_wechat_serial_no($params); + + $config = get_provider_config('wechat', $params); + $publicKey = get_wechat_public_key($config, $data['_serial_no']); + + foreach ($data['receivers'] as $key => $list) { + $data['receivers'][$key]['name'] = encrypt_wechat_contents($list['name'], $publicKey); + } + + return $data; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/DeleteReceiverPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/DeleteReceiverPlugin.php new file mode 100644 index 0000000..07dac6e --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/DeleteReceiverPlugin.php @@ -0,0 +1,84 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 缺少分账参数'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $params, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/profitsharing/receivers/delete', + '_service_url' => 'v3/profitsharing/receivers/delete', + ], + $data ?? $this->normal($params, $config), + )); + + Logger::info('[Wechat][Extend][ProfitSharing][DeleteReceiverPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $params, array $config): array + { + return [ + 'appid' => $config[get_wechat_type_key($params)] ?? '', + ]; + } + + protected function service(Collection $payload, array $params, array $config): array + { + $wechatTypeKEY = get_wechat_type_key($params); + + $data = [ + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + 'appid' => $config[$wechatTypeKEY] ?? '', + ]; + + if ('PERSONAL_SUB_OPENID' === $payload->get('type')) { + $data['sub_appid'] = $config['sub_'.$wechatTypeKEY] ?? ''; + } + + return $data; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/DownloadBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/DownloadBillPlugin.php new file mode 100644 index 0000000..c98b46d --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/DownloadBillPlugin.php @@ -0,0 +1,45 @@ + $rocket]); + + $downloadUrl = $rocket->getPayload()?->get('download_url') ?? null; + + if (empty($downloadUrl)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 下载电子回单,参数缺少 `download_url`'); + } + + $rocket->setDirection(OriginResponseDirection::class) + ->setPayload([ + '_method' => 'GET', + '_url' => $downloadUrl, + '_service_url' => $downloadUrl, + ]); + + Logger::info('[Wechat][Extend][ProfitSharing][DownloadBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/GetBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/GetBillPlugin.php new file mode 100644 index 0000000..09bac2d --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/GetBillPlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 分账 申请账单,参数为空'); + } + + $query = filter_params($payload)->query(); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/profitsharing/bills?'.$query, + '_service_url' => 'v3/profitsharing/bills?'.$query, + ]); + + Logger::info('[Wechat][V3][Extend][ProfitSharing][GetBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/QueryAmountsPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/QueryAmountsPlugin.php new file mode 100644 index 0000000..ff604f9 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/QueryAmountsPlugin.php @@ -0,0 +1,43 @@ + $rocket]); + + $transactionId = $rocket->getPayload()?->get('transaction_id'); + + if (is_null($transactionId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询剩余待分金额,参数缺少 `transaction_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/profitsharing/transactions/'.$transactionId.'/amounts', + '_service_url' => 'v3/profitsharing/transactions/'.$transactionId.'/amounts', + ]); + + Logger::info('[Wechat][Extend][ProfitSharing][QueryAmountsPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/QueryMerchantConfigsPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/QueryMerchantConfigsPlugin.php new file mode 100644 index 0000000..3620fa5 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/QueryMerchantConfigsPlugin.php @@ -0,0 +1,50 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $config = get_provider_config('wechat', $rocket->getParams()); + $subMchId = $payload?->get('sub_mch_id') ?? $config['sub_mch_id'] ?? 'null'; + + if (Pay::MODE_NORMAL === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_SERVICE_MODE, '参数异常: 查询最大分账比例,只支持服务商模式,当前配置为普通商户模式'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_service_url' => 'v3/profitsharing/merchant-configs/'.$subMchId, + ]); + + Logger::info('[Wechat][Extend][ProfitSharing][QueryMerchantConfigsPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/QueryPlugin.php new file mode 100644 index 0000000..02ba6e0 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/QueryPlugin.php @@ -0,0 +1,53 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + $payload = $rocket->getPayload(); + $outOrderNo = $payload?->get('out_order_no') ?? null; + $transactionId = $payload?->get('transaction_id') ?? null; + $subMchId = $payload?->get('sub_mchid') ?? $config['sub_mch_id'] ?? 'null'; + + if (empty($outOrderNo) || empty($transactionId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询分账结果, 缺少必要参数 `out_order_no`, `transaction_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/profitsharing/orders/'.$outOrderNo.'?transaction_id='.$transactionId, + '_service_url' => 'v3/profitsharing/orders/'.$outOrderNo.'?sub_mchid='.$subMchId.'&transaction_id='.$transactionId, + ]); + + Logger::info('[Wechat][Extend][ProfitSharing][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/QueryReturnPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/QueryReturnPlugin.php new file mode 100644 index 0000000..5509b2d --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/QueryReturnPlugin.php @@ -0,0 +1,53 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + $payload = $rocket->getPayload(); + $outOrderNo = $payload?->get('out_order_no') ?? null; + $outReturnNo = $payload?->get('out_return_no') ?? null; + $subMchId = $payload?->get('sub_mchid') ?? $config['sub_mch_id'] ?? 'null'; + + if (empty($outOrderNo) || empty($outReturnNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询分账结果, 缺少必要参数 `out_order_no`, `out_return_no`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/profitsharing/return-orders/'.$outReturnNo.'?out_order_no='.$outOrderNo, + '_service_url' => 'v3/profitsharing/return-orders/'.$outReturnNo.'?sub_mchid='.$subMchId.'&out_order_no='.$outOrderNo, + ]); + + Logger::info('[Wechat][Extend][ProfitSharing][QueryReturnPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/ReturnPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/ReturnPlugin.php new file mode 100644 index 0000000..dd8c616 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/ReturnPlugin.php @@ -0,0 +1,75 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 缺少分账退回参数'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/profitsharing/return-orders', + '_service_url' => 'v3/profitsharing/return-orders', + ], + $data ?? $this->normal($payload, $config), + )); + + Logger::info('[Wechat][Extend][ProfitSharing][ReturnPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(Collection $payload, array $config): array + { + return [ + 'return_mchid' => $payload->get('return_mchid', $config['mch_id'] ?? ''), + ]; + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + 'return_mchid' => $payload->get('return_mchid', $config['mch_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/UnfreezePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/UnfreezePlugin.php new file mode 100644 index 0000000..3c85140 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Extend/ProfitSharing/UnfreezePlugin.php @@ -0,0 +1,73 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 缺少分账解冻剩余资金参数'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/profitsharing/orders/unfreeze', + '_service_url' => 'v3/profitsharing/orders/unfreeze', + ], + $data ?? $this->normal(), + )); + + Logger::info('[Wechat][Extend][ProfitSharing][UnfreezePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(): array + { + return []; + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Callback/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Callback/QueryPlugin.php new file mode 100644 index 0000000..d64a302 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Callback/QueryPlugin.php @@ -0,0 +1,44 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $mchId = $rocket->getPayload()?->get('mchid') ?? $config['mch_id'] ?? 'null'; + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/marketing/favor/callbacks?mchid='.$mchId, + '_service_url' => 'v3/marketing/favor/callbacks?mchid='.$mchId, + ]); + + Logger::info('[Wechat][Marketing][Coupon][Callback][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Callback/SetPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Callback/SetPlugin.php new file mode 100644 index 0000000..e066ecb --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Callback/SetPlugin.php @@ -0,0 +1,48 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/marketing/favor/callbacks', + '_service_url' => 'v3/marketing/favor/callbacks', + 'mchid' => $payload?->get('mchid') ?? $config['mch_id'] ?? '', + 'notify_url' => $payload?->get('notify_url') ?? $config['notify_url'] ?? '', + ], + )); + + Logger::info('[Wechat][V3][Marketing][Coupon][Callback][SetPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Coupons/DetailPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Coupons/DetailPlugin.php new file mode 100644 index 0000000..84a2577 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Coupons/DetailPlugin.php @@ -0,0 +1,55 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $openId = $payload?->get('openid') ?? null; + $couponId = $payload?->get('coupon_id') ?? null; + $appId = $payload?->get('appid') ?? $config[get_wechat_type_key($params)] ?? 'null'; + + if (empty($openId) || empty($couponId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询代金券详情,参数缺少 `openid` 或 `coupon_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/marketing/favor/users/'.$openId.'/coupons/'.$couponId.'?appid='.$appId, + '_service_url' => 'v3/marketing/favor/users/'.$openId.'/coupons/'.$couponId.'?appid='.$appId, + ]); + + Logger::info('[Wechat][V3][Marketing][Coupon][Coupons][DetailPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Coupons/QueryUserPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Coupons/QueryUserPlugin.php new file mode 100644 index 0000000..dc94d70 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Coupons/QueryUserPlugin.php @@ -0,0 +1,66 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $openId = $payload?->get('openid') ?? null; + + if (empty($openId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 根据商户号查用户的券,参数缺少 `openid`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/marketing/favor/users/'.$openId.'/coupons?'.$this->normal($payload, $params, $config), + '_service_url' => 'v3/marketing/favor/users/'.$openId.'/coupons?'.$this->normal($payload, $params, $config), + ]); + + Logger::info('[Wechat][V3][Marketing][Coupon][Coupons][QueryUserPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(Collection $payload, array $params, array $config): string + { + $appId = $payload->get('appid'); + + if (is_null($appId)) { + $payload->set('appid', $config[get_wechat_type_key($params)] ?? ''); + } + + return filter_params($payload)->except('openid')->query(); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Coupons/SendPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Coupons/SendPlugin.php new file mode 100644 index 0000000..178690e --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Coupons/SendPlugin.php @@ -0,0 +1,70 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $openId = $payload?->get('openid') ?? null; + + if (empty($openId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 发放指定批次的代金券,参数缺少 `openid`'); + } + + $rocket->setPayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/marketing/favor/users/'.$openId.'/coupons', + '_service_url' => 'v3/marketing/favor/users/'.$openId.'/coupons', + ], + $this->normal($payload, $params, $config), + )); + + Logger::info('[Wechat][V3][Marketing][Coupon][Coupons][SendPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(Collection $payload, array $params, array $config): array + { + if (empty($payload->get('appid'))) { + $payload->set('appid', $config[get_wechat_type_key($params)] ?? ''); + } + + if (empty($payload->get('stock_creator_mchid'))) { + $payload->set('stock_creator_mchid', $config['mch_id'] ?? ''); + } + + return $payload->except('openid')->all(); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/CreatePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/CreatePlugin.php new file mode 100644 index 0000000..3d34944 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/CreatePlugin.php @@ -0,0 +1,45 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $belongMerchant = $rocket->getPayload()?->get('belong_merchant') ?? $config['mch_id']; + + $rocket->mergePayload([ + '_method' => 'POST', + '_url' => 'v3/marketing/favor/coupon-stocks', + '_service_url' => 'v3/marketing/favor/coupon-stocks', + 'belong_merchant' => $belongMerchant, + ]); + + Logger::info('[Wechat][V3][Marketing][Coupon][Stock][CreatePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/PausePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/PausePlugin.php new file mode 100644 index 0000000..780b1a1 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/PausePlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $stockId = $payload?->get('stock_id') ?? null; + $stockCreatorMchId = $payload?->get('stock_creator_mchid') ?? $config['mch_id'] ?? ''; + + if (empty($stockId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 暂停代金券批次,参数缺少 `stock_id`'); + } + + $rocket->setPayload([ + '_method' => 'POST', + '_url' => 'v3/marketing/favor/stocks/'.$stockId.'/pause', + '_service_url' => 'v3/marketing/favor/stocks/'.$stockId.'/pause', + 'stock_creator_mchid' => $stockCreatorMchId, + ]); + + Logger::info('[Wechat][V3][Marketing][Coupon][Stock][PausePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryDetailPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryDetailPlugin.php new file mode 100644 index 0000000..4b311d4 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryDetailPlugin.php @@ -0,0 +1,53 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $stockId = $payload?->get('stock_id') ?? null; + $mchId = $payload?->get('stock_creator_mchid') ?? $config['mch_id'] ?? 'null'; + + if (empty($stockId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询代金券批次详情,参数缺少 `stock_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/marketing/favor/stocks/'.$stockId.'?stock_creator_mchid='.$mchId, + '_service_url' => 'v3/marketing/favor/stocks/'.$stockId.'?stock_creator_mchid='.$mchId, + ]); + + Logger::info('[Wechat][V3][Marketing][Coupon][Stock][QueryDetailPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryItemsPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryItemsPlugin.php new file mode 100644 index 0000000..ef68a98 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryItemsPlugin.php @@ -0,0 +1,65 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $stockId = $payload?->get('stock_id') ?? null; + + if (empty($stockId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询代金券可用单品,参数缺少 `stock_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/marketing/favor/stocks/'.$stockId.'/items?'.$this->normal($payload, $config), + '_service_url' => 'v3/marketing/favor/stocks/'.$stockId.'/items?'.$this->normal($payload, $config), + ]); + + Logger::info('[Wechat][V3][Marketing][Coupon][Stock][QueryItemsPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + public function normal(Collection $payload, array $config): string + { + $stockCreatorMchId = $payload->get('stock_creator_mchid'); + + if (is_null($stockCreatorMchId)) { + $payload->set('stock_creator_mchid', $config['mch_id'] ?? ''); + } + + return filter_params($payload)->except('stock_id')->query(); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryMerchantsPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryMerchantsPlugin.php new file mode 100644 index 0000000..0777a46 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryMerchantsPlugin.php @@ -0,0 +1,65 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $stockId = $payload?->get('stock_id') ?? null; + + if (empty($stockId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询代金券可用商户,参数缺少 `stock_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/marketing/favor/stocks/'.$stockId.'/merchants?'.$this->normal($payload, $config), + '_service_url' => 'v3/marketing/favor/stocks/'.$stockId.'/merchants?'.$this->normal($payload, $config), + ]); + + Logger::info('[Wechat][V3][Marketing][Coupon][Stock][QueryMerchantsPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + public function normal(Collection $payload, array $config): string + { + $stockCreatorMchId = $payload->get('stock_creator_mchid'); + + if (is_null($stockCreatorMchId)) { + $payload->set('stock_creator_mchid', $config['mch_id'] ?? ''); + } + + return filter_params($payload)->except('stock_id')->query(); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryPlugin.php new file mode 100644 index 0000000..503c238 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryPlugin.php @@ -0,0 +1,64 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 缺少代金券相关参数'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/marketing/favor/stocks?'.$this->normal($payload, $config), + '_service_url' => 'v3/marketing/favor/stocks?'.$this->normal($payload, $config), + ]); + + Logger::info('[Wechat][V3][Marketing][Coupon][Stock][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + public function normal(Collection $payload, array $config): string + { + $stockCreatorMchId = $payload->get('stock_creator_mchid'); + + if (is_null($stockCreatorMchId)) { + $payload->set('stock_creator_mchid', $config['mch_id'] ?? ''); + } + + return filter_params($payload)->query(); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryRefundFlowPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryRefundFlowPlugin.php new file mode 100644 index 0000000..243fa30 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryRefundFlowPlugin.php @@ -0,0 +1,43 @@ + $rocket]); + + $stockId = $rocket->getPayload()?->get('stock_id') ?? null; + + if (empty($stockId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 下载批次退款明细,参数缺少 `stock_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/marketing/favor/stocks/'.$stockId.'/refund-flow', + '_service_url' => 'v3/marketing/favor/stocks/'.$stockId.'/refund-flow', + ]); + + Logger::info('[Wechat][V3][Marketing][Coupon][Stock][QueryRefundFlowPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryUseFlowPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryUseFlowPlugin.php new file mode 100644 index 0000000..0de6bc4 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/QueryUseFlowPlugin.php @@ -0,0 +1,43 @@ + $rocket]); + + $stockId = $rocket->getPayload()?->get('stock_id') ?? null; + + if (empty($stockId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 下载批次核销明细,参数缺少 `stock_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/marketing/favor/stocks/'.$stockId.'/use-flow', + '_service_url' => 'v3/marketing/favor/stocks/'.$stockId.'/use-flow', + ]); + + Logger::info('[Wechat][V3][Marketing][Coupon][Stock][QueryUseFlowPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/RestartPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/RestartPlugin.php new file mode 100644 index 0000000..c1393dc --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/RestartPlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $stockId = $payload?->get('stock_id') ?? null; + $stockCreatorMchId = $payload?->get('stock_creator_mchid') ?? $config['mch_id'] ?? ''; + + if (empty($stockId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 激活代金券,参数缺少 `stock_id`'); + } + + $rocket->setPayload([ + '_method' => 'POST', + '_url' => 'v3/marketing/favor/stocks/'.$stockId.'/restart', + '_service_url' => 'v3/marketing/favor/stocks/'.$stockId.'/restart', + 'stock_creator_mchid' => $stockCreatorMchId, + ]); + + Logger::info('[Wechat][V3][Marketing][Coupon][Stock][RestartPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/StartPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/StartPlugin.php new file mode 100644 index 0000000..8fae020 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Coupon/Stock/StartPlugin.php @@ -0,0 +1,53 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $stockId = $payload?->get('stock_id') ?? null; + + if (empty($stockId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 激活代金券,参数缺少 `stock_id`'); + } + + $rocket->setPayload([ + '_method' => 'POST', + '_url' => 'v3/marketing/favor/stocks/'.$stockId.'/start', + '_service_url' => 'v3/marketing/favor/stocks/'.$stockId.'/start', + 'stock_creator_mchid' => $payload->get('stock_creator_mchid') ?? $config['mch_id'] ?? '', + ]); + + Logger::info('[Wechat][V3][Marketing][Coupon][Stock][StartPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceBalance/QueryDayEndPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceBalance/QueryDayEndPlugin.php new file mode 100644 index 0000000..c1a5c82 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceBalance/QueryDayEndPlugin.php @@ -0,0 +1,67 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + $payload = $rocket->getPayload(); + $accountType = $payload?->get('account_type') ?? null; + + if (Pay::MODE_NORMAL === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_SERVICE_MODE, '参数异常: 查询电商平台账户日终余额,只支持服务商模式,当前配置为普通商户模式'); + } + + if (empty($accountType)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询电商平台账户日终余额,参数缺少 `account_type`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_service_url' => 'v3/merchant/fund/dayendbalance/'.$accountType.$this->service($payload), + ]); + + Logger::info('[Wechat][Marketing][ECommerceBalance][QueryDayEndPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function service(Collection $payload): string + { + $query = filter_params($payload)->except('account_type'); + + if ($query->isEmpty()) { + return ''; + } + + return '?'.$query->query(); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceBalance/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceBalance/QueryPlugin.php new file mode 100644 index 0000000..942064a --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceBalance/QueryPlugin.php @@ -0,0 +1,53 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + $accountType = $rocket->getPayload()?->get('account_type') ?? null; + + if (Pay::MODE_NORMAL === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_SERVICE_MODE, '参数异常: 查询电商平台账户实时余额,只支持服务商模式,当前配置为普通商户模式'); + } + + if (empty($accountType)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询电商平台账户实时余额,参数缺少 `account_type`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_service_url' => 'v3/merchant/fund/balance/'.$accountType, + ]); + + Logger::info('[Wechat][Marketing][ECommerceBalance][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceRefund/ApplyPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceRefund/ApplyPlugin.php new file mode 100644 index 0000000..c7aa7d3 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceRefund/ApplyPlugin.php @@ -0,0 +1,60 @@ + $rocket]); + + $params = $rocket->getParams(); + $payload = $rocket->getPayload(); + $config = get_provider_config('wechat', $params); + $subMchId = $payload?->get('sub_mchid') ?? $config['sub_mch_id'] ?? ''; + $spAppId = $payload?->get('sp_appid') ?? $config[get_wechat_type_key($params)] ?? ''; + + if (Pay::MODE_NORMAL === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_SERVICE_MODE, '参数异常: 平台收付通(退款)-申请退款,只支持服务商模式,当前配置为普通商户模式'); + } + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 平台收付通(退款)-申请退款,缺少必要参数'); + } + + $rocket->mergePayload([ + '_method' => 'POST', + '_service_url' => 'v3/ecommerce/refunds/apply', + 'sub_mchid' => $subMchId, + 'sp_appid' => $spAppId, + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null), + ]); + + Logger::info('[Wechat][V3][Marketing][ECommerceRefund][ApplyPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryByWxPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryByWxPlugin.php new file mode 100644 index 0000000..6caf710 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryByWxPlugin.php @@ -0,0 +1,55 @@ + $rocket]); + + $params = $rocket->getParams(); + $payload = $rocket->getPayload(); + $config = get_provider_config('wechat', $params); + $refundId = $payload?->get('refund_id') ?? null; + + if (Pay::MODE_NORMAL === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_SERVICE_MODE, '参数异常: 平台收付通(退款)-查询单笔退款(按微信支付退款单号),只支持服务商模式,当前配置为普通商户模式'); + } + + if (is_null($refundId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 平台收付通(退款)-查询单笔退款(按微信支付退款单号),缺少必要参数 `refund_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_service_url' => 'v3/ecommerce/refunds/id/'.$refundId.'?sub_mchid='.$payload->get('sub_mchid', $config['sub_mch_id'] ?? 'null'), + ]); + + Logger::info('[Wechat][Marketing][ECommerceRefund][QueryBatchByWxPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryPlugin.php new file mode 100644 index 0000000..00f910f --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryPlugin.php @@ -0,0 +1,55 @@ + $rocket]); + + $params = $rocket->getParams(); + $payload = $rocket->getPayload(); + $config = get_provider_config('wechat', $params); + $outRefundNo = $payload?->get('out_refund_no') ?? null; + + if (Pay::MODE_NORMAL === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_SERVICE_MODE, '参数异常: 平台收付通(退款)-查询单笔退款(按商户退款单号),只支持服务商模式,当前配置为普通商户模式'); + } + + if (is_null($outRefundNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 平台收付通(退款)-查询单笔退款(按商户退款单号),缺少必要参数 `out_refund_no`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_service_url' => 'v3/ecommerce/refunds/out-refund-no/'.$outRefundNo.'?sub_mchid='.$payload->get('sub_mchid', $config['sub_mch_id'] ?? 'null'), + ]); + + Logger::info('[Wechat][V3][Marketing][ECommerceRefund][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryReturnAdvancePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryReturnAdvancePlugin.php new file mode 100644 index 0000000..f4a22a3 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceRefund/QueryReturnAdvancePlugin.php @@ -0,0 +1,55 @@ + $rocket]); + + $params = $rocket->getParams(); + $payload = $rocket->getPayload(); + $config = get_provider_config('wechat', $params); + $refundId = $payload?->get('refund_id') ?? null; + + if (Pay::MODE_NORMAL === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_SERVICE_MODE, '参数异常: 平台收付通(退款)-查询垫付回补结果,只支持服务商模式,当前配置为普通商户模式'); + } + + if (is_null($refundId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 平台收付通(退款)-查询垫付回补结果,缺少必要参数 `refund_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_service_url' => 'v3/ecommerce/refunds/'.$refundId.'/return-advance?sub_mchid='.$payload->get('sub_mchid', $config['sub_mch_id']), + ]); + + Logger::info('[Wechat][V3][Marketing][ECommerceRefund][QueryReturnAdvancePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceRefund/ReturnAdvancePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceRefund/ReturnAdvancePlugin.php new file mode 100644 index 0000000..9a256f5 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/ECommerceRefund/ReturnAdvancePlugin.php @@ -0,0 +1,56 @@ + $rocket]); + + $params = $rocket->getParams(); + $payload = $rocket->getPayload(); + $config = get_provider_config('wechat', $params); + $refundId = $payload?->get('refund_id') ?? null; + + if (Pay::MODE_NORMAL === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_SERVICE_MODE, '参数异常: 平台收付通(退款)-垫付退款回补,只支持服务商模式,当前配置为普通商户模式'); + } + + if (is_null($refundId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 平台收付通(退款)-垫付退款回补,缺少必要参数 `refund_id`'); + } + + $rocket->setPayload([ + '_method' => 'POST', + '_service_url' => 'v3/ecommerce/refunds/'.$refundId.'/return-advance', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]); + + Logger::info('[Wechat][V3][Marketing][ECommerceRefund][ReturnAdvancePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/CreatePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/CreatePlugin.php new file mode 100644 index 0000000..c197545 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/CreatePlugin.php @@ -0,0 +1,80 @@ + $rocket]); + + $params = $rocket->getParams(); + $payload = $rocket->getPayload(); + $config = get_provider_config('wechat', $params); + + $rocket->mergePayload(array_merge([ + '_method' => 'POST', + '_url' => 'v3/new-tax-control-fapiao/fapiao-applications', + ], $this->encryptSensitiveData($payload, $params, $config))); + + Logger::info('[Wechat][V3][Marketing][Fapiao][Blockchain][CreatePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + /** + * @throws ContainerException + * @throws InvalidConfigException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + * @throws DecryptException + */ + protected function encryptSensitiveData(?Collection $payload, array $params, array $config): array + { + $data['_serial_no'] = get_wechat_serial_no($params); + + $config = get_provider_config('wechat', $params); + $publicKey = get_wechat_public_key($config, $data['_serial_no']); + + $phone = $payload?->get('buyer_information.phone') ?? null; + $email = $payload?->get('buyer_information.email') ?? null; + + if (!is_null($phone)) { + $data['buyer_information']['phone'] = encrypt_wechat_contents($phone, $publicKey); + } + + if (!is_null($email)) { + $data['buyer_information']['email'] = encrypt_wechat_contents($email, $publicKey); + } + + return $data; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/DownloadPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/DownloadPlugin.php new file mode 100644 index 0000000..485029e --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/DownloadPlugin.php @@ -0,0 +1,46 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $downloadUrl = $payload?->get('download_url') ?? null; + + if (empty($downloadUrl)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 下载发票文件,缺少 `download_url` 参数'); + } + + $rocket->setDirection(OriginResponseDirection::class) + ->setPayload([ + '_method' => 'GET', + '_url' => $downloadUrl.'&'.filter_params($payload)->except('download_url')->query(), + ]); + + Logger::info('[Wechat][V3][Marketing][Fapiao][Blockchain][DownloadPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetBaseInformationPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetBaseInformationPlugin.php new file mode 100644 index 0000000..a4df4f8 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetBaseInformationPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/new-tax-control-fapiao/merchant/base-information', + ]); + + Logger::info('[Wechat][V3][Marketing][Fapiao][Blockchain][GetBaseInformationPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetDownloadInfoPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetDownloadInfoPlugin.php new file mode 100644 index 0000000..27d5d8f --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetDownloadInfoPlugin.php @@ -0,0 +1,43 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $applyId = $payload?->get('fapiao_apply_id') ?? null; + $fapiaoId = $payload?->get('fapiao_id') ?? null; + + if (empty($applyId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询电子发票,参数缺少 `fapiao_apply_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/new-tax-control-fapiao/fapiao-applications/'.$applyId.'/fapiao-files'.(empty($fapiaoId) ? '' : '?fapiao_id='.$fapiaoId), + ]); + + Logger::info('[Wechat][V3][Marketing][Fapiao][GetDownloadInfoPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetTaxCodePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetTaxCodePlugin.php new file mode 100644 index 0000000..c40a3a1 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/GetTaxCodePlugin.php @@ -0,0 +1,43 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $offset = $payload?->get('offset') ?? null; + $limit = $payload?->get('limit') ?? null; + + if (empty($offset) || empty($limit)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 获取商户可开具的商品和服务税收分类编码对照表,缺少 `offset` 或 `limit` 参数'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/new-tax-control-fapiao/merchant/tax-codes?offset='.$offset.'&limit='.$limit, + ]); + + Logger::info('[Wechat][V3][Marketing][Fapiao][Blockchain][GetTaxCodePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/ReversePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/ReversePlugin.php new file mode 100644 index 0000000..170ca48 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/Blockchain/ReversePlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $applyId = $payload?->get('fapiao_apply_id') ?? null; + + if (empty($applyId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 冲红电子发票,参数缺少 `fapiao_apply_id`'); + } + + $rocket->setPayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/new-tax-control-fapiao/fapiao-applications/'.$applyId.'/reverse', + ], + filter_params($payload)->except('fapiao_apply_id')->all(), + )); + + Logger::info('[Wechat][V3][Marketing][Fapiao][Blockchain][ReversePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/CreateCardTemplatePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/CreateCardTemplatePlugin.php new file mode 100644 index 0000000..d7436f1 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/CreateCardTemplatePlugin.php @@ -0,0 +1,43 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + $rocket->mergePayload([ + '_method' => 'POST', + '_url' => 'v3/new-tax-control-fapiao/card-template', + 'card_appid' => $payload?->get('card_appid') ?? $config['mp_app_id'], + ]); + + Logger::info('[Wechat][V3][Marketing][Fapiao][CreateCardTemplatePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/GetTitleUrlPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/GetTitleUrlPlugin.php new file mode 100644 index 0000000..ed6ccbf --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/GetTitleUrlPlugin.php @@ -0,0 +1,64 @@ + $rocket]); + + $params = $rocket->getParams(); + $payload = $rocket->getPayload(); + + if (empty($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 获取抬头填写链接,缺少必要参数'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/new-tax-control-fapiao/user-title/title-url?'.$this->getQuery($payload, $params)->query(), + ]); + + Logger::info('[Wechat][V3][Marketing][Fapiao][GetTitleUrlPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + /** + * @throws ContainerException + * @throws ServiceNotFoundException + */ + protected function getQuery(Collection $payload, array $params): Collection + { + $config = get_provider_config('wechat', $params); + + return filter_params($payload)->merge([ + 'appid' => $payload->get('appid', $config[get_wechat_type_key($params)] ?? ''), + ]); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/QueryConfigPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/QueryConfigPlugin.php new file mode 100644 index 0000000..7ac20b2 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/QueryConfigPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/new-tax-control-fapiao/merchant/development-config', + ]); + + Logger::info('[Wechat][V3][Marketing][Fapiao][QueryConfigPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/QueryPlugin.php new file mode 100644 index 0000000..c0863f0 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/QueryPlugin.php @@ -0,0 +1,43 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $applyId = $payload?->get('fapiao_apply_id') ?? null; + $fapiaoId = $payload?->get('fapiao_id') ?? null; + + if (empty($applyId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询电子发票,参数缺少 `fapiao_apply_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/new-tax-control-fapiao/fapiao-applications/'.$applyId.(empty($fapiaoId) ? '' : '?fapiao_id='.$fapiaoId), + ]); + + Logger::info('[Wechat][V3][Marketing][Fapiao][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/QueryUserTitlePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/QueryUserTitlePlugin.php new file mode 100644 index 0000000..750cce3 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/QueryUserTitlePlugin.php @@ -0,0 +1,43 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + if (empty($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 获取用户填写的抬头,缺少必要参数'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/new-tax-control-fapiao/user-title?'.filter_params($payload)->query(), + ]); + + Logger::info('[Wechat][V3][Marketing][Fapiao][QueryUserTitlePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/UpdateConfigPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/UpdateConfigPlugin.php new file mode 100644 index 0000000..72a2d4c --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Fapiao/UpdateConfigPlugin.php @@ -0,0 +1,30 @@ + $rocket]); + + $rocket->mergePayload([ + '_method' => 'PATCH', + '_url' => 'v3/new-tax-control-fapiao/merchant/development-config', + ]); + + Logger::info('[Wechat][V3][Marketing][Fapiao][UpdateConfigPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/CancelPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/CancelPlugin.php new file mode 100644 index 0000000..91cd699 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/CancelPlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + $payload = $rocket->getPayload(); + $outBillNo = $payload?->get('out_bill_no') ?? null; + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 撤销转账,只支持普通商户模式,当前配置为服务商模式'); + } + + if (empty($outBillNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 撤销转账,参数缺少 `out_bill_no`'); + } + + $rocket->setPayload([ + '_method' => 'POST', + '_url' => 'v3/fund-app/mch-transfer/transfer-bills/out-bill-no/'.$outBillNo.'/cancel', + ]); + + Logger::info('[Wechat][Marketing][MchTransfer][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/CreatePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/CreatePlugin.php new file mode 100644 index 0000000..668ce1f --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/CreatePlugin.php @@ -0,0 +1,103 @@ + $rocket]); + + $params = $rocket->getParams(); + $payload = $rocket->getPayload(); + $config = get_provider_config('wechat', $params); + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 发起商家转账,只支持普通商户模式,当前配置为服务商模式'); + } + + if (is_null($payload) || $payload->isEmpty()) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 发起商家转账参数,参数缺失'); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/fund-app/mch-transfer/transfer-bills', + 'appid' => $payload->get('appid', $config[get_wechat_type_key($params)] ?? ''), + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''), + ], + $this->normal($params, $payload) + )); + + Logger::info('[Wechat][Marketing][MchTransfer][CreatePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + * @throws DecryptException + * @throws InvalidConfigException + */ + protected function normal(array $params, Collection $payload): array + { + if (!$payload->has('user_name')) { + return []; + } + + return $this->encryptSensitiveData($params, $payload); + } + + /** + * @throws ContainerException + * @throws DecryptException + * @throws InvalidConfigException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + protected function encryptSensitiveData(array $params, Collection $payload): array + { + $data['_serial_no'] = get_wechat_serial_no($params); + + $config = get_provider_config('wechat', $params); + $publicKey = get_wechat_public_key($config, $data['_serial_no']); + + $data['user_name'] = encrypt_wechat_contents($payload->get('user_name'), $publicKey); + + return $data; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeAndroidPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeAndroidPlugin.php new file mode 100644 index 0000000..b8ac3ea --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeAndroidPlugin.php @@ -0,0 +1,77 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + $destination = $rocket->getDestination(); + $packageInfo = $destination?->get('package_info'); + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: Android调起用户确认收款,只支持普通商户模式,当前配置为服务商模式'); + } + + if (is_null($packageInfo)) { + Logger::error('[Wechat][V3][Marketing][MchTransfer][InvokeAndroidPlugin] Android调起用户确认收款失败:响应缺少 `package_info` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + + throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('fail_reason') ?? 'Android调起用户确认收款失败:响应缺少 `package_info` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + } + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + $rocket->setDestination($this->getInvokeConfig($payload, $params, $config, $packageInfo)); + + Logger::info('[Wechat][V3][Marketing][MchTransfer][InvokeAndroidPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + protected function getInvokeConfig(?Collection $payload, array $params, array $config, string $packageInfo): Config + { + return new Config([ + 'businessType' => 'requestMerchantTransfer', + 'query' => http_build_query([ + 'appId' => $payload?->get('_invoke_appId') ?? $config[get_wechat_type_key($params)] ?? '', + 'mchId' => $payload?->get('_invoke_mchId') ?? $config['mch_id'] ?? '', + 'package' => $packageInfo, + ]), + ]); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeIosPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeIosPlugin.php new file mode 100644 index 0000000..f124cc9 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeIosPlugin.php @@ -0,0 +1,77 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + $destination = $rocket->getDestination(); + $packageInfo = $destination?->get('package_info'); + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: iOS调起用户确认收款,只支持普通商户模式,当前配置为服务商模式'); + } + + if (is_null($packageInfo)) { + Logger::error('[Wechat][V3][Marketing][MchTransfer][InvokeIosPlugin] iOS调起用户确认收款失败:响应缺少 `package_info` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + + throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('fail_reason') ?? 'iOS调起用户确认收款失败:响应缺少 `package_info` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + } + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + $rocket->setDestination($this->getInvokeConfig($payload, $params, $config, $packageInfo)); + + Logger::info('[Wechat][V3][Marketing][MchTransfer][InvokeIosPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + protected function getInvokeConfig(?Collection $payload, array $params, array $config, string $packageInfo): Config + { + return new Config([ + 'businessType' => 'requestMerchantTransfer', + 'query' => http_build_query([ + 'appId' => $payload?->get('_invoke_appId') ?? $config[get_wechat_type_key($params)] ?? '', + 'mchId' => $payload?->get('_invoke_mchId') ?? $config['mch_id'] ?? '', + 'package' => $packageInfo, + ]), + ]); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeJsapiPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeJsapiPlugin.php new file mode 100644 index 0000000..ac7c5ac --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeJsapiPlugin.php @@ -0,0 +1,74 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + $destination = $rocket->getDestination(); + $packageInfo = $destination?->get('package_info'); + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: JSAPI调起用户确认收款,只支持普通商户模式,当前配置为服务商模式'); + } + + if (is_null($packageInfo)) { + Logger::error('[Wechat][V3][Marketing][MchTransfer][InvokeJsapiPlugin] JSAPI调起用户确认收款失败:响应缺少 `package_info` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + + throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('fail_reason') ?? 'JSAPI调起用户确认收款失败:响应缺少 `package_info` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + } + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + $rocket->setDestination($this->getInvokeConfig($payload, $params, $config, $packageInfo)); + + Logger::info('[Wechat][V3][Marketing][MchTransfer][InvokeJsapiPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + protected function getInvokeConfig(?Collection $payload, array $params, array $config, string $packageInfo): Config + { + return new Config([ + 'appId' => $payload?->get('_invoke_appId') ?? $config[get_wechat_type_key($params)] ?? '', + 'mchId' => $payload?->get('_invoke_mchId') ?? $config['mch_id'] ?? '', + 'package' => $packageInfo, + ]); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/QueryByWxPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/QueryByWxPlugin.php new file mode 100644 index 0000000..a112900 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/QueryByWxPlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + $payload = $rocket->getPayload(); + $transferBillNo = $payload?->get('transfer_bill_no') ?? null; + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 通过微信单号查询转账单,只支持普通商户模式,当前配置为服务商模式'); + } + + if (empty($transferBillNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 通过微信单号查询转账单,参数缺少 `transfer_bill_no`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/fund-app/mch-transfer/transfer-bills/transfer-bill-no/'.$transferBillNo, + ]); + + Logger::info('[Wechat][Marketing][MchTransfer][QueryByWxPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/QueryPlugin.php new file mode 100644 index 0000000..f7b8020 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/MchTransfer/QueryPlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + $payload = $rocket->getPayload(); + $outBillNo = $payload?->get('out_bill_no') ?? null; + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 通过商户单号查询转账单,只支持普通商户模式,当前配置为服务商模式'); + } + + if (empty($outBillNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 通过商户单号查询转账单,参数缺少 `out_bill_no`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/fund-app/mch-transfer/transfer-bills/out-bill-no/'.$outBillNo, + ]); + + Logger::info('[Wechat][Marketing][MchTransfer][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/Batch/QueryByWxPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/Batch/QueryByWxPlugin.php new file mode 100644 index 0000000..d0e9490 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/Batch/QueryByWxPlugin.php @@ -0,0 +1,57 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + $payload = $rocket->getPayload(); + $batchId = $payload?->get('batch_id') ?? null; + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 通过微信批次单号查询批次单,只支持普通商户模式,当前配置为服务商模式'); + } + + if (empty($batchId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 通过微信批次单号查询批次单,参数缺少 `batch_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/transfer/batches/batch-id/'.$batchId.'?'.filter_params($payload)->except('batch_id')->query(), + ]); + + Logger::info('[Wechat][Marketing][Transfer][Batch][QueryByWxPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/Batch/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/Batch/QueryPlugin.php new file mode 100644 index 0000000..d787f3e --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/Batch/QueryPlugin.php @@ -0,0 +1,57 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + $payload = $rocket->getPayload(); + $outBatchNo = $payload?->get('out_batch_no') ?? null; + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 通过商家批次单号查询批次单,只支持普通商户模式,当前配置为服务商模式'); + } + + if (empty($outBatchNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 通过商家批次单号查询批次单,参数缺少 `out_batch_no`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/transfer/batches/out-batch-no/'.$outBatchNo.'?'.filter_params($payload)->except('out_batch_no')->query(), + ]); + + Logger::info('[Wechat][Marketing][Transfer][Batch][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/CreatePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/CreatePlugin.php new file mode 100644 index 0000000..a617255 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/CreatePlugin.php @@ -0,0 +1,109 @@ + $rocket]); + + $params = $rocket->getParams(); + $payload = $rocket->getPayload(); + $config = get_provider_config('wechat', $params); + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 发起商家转账,只支持普通商户模式,当前配置为服务商模式'); + } + + if (is_null($payload) || $payload->isEmpty()) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 发起商家转账参数,参数缺失'); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/transfer/batches', + 'appid' => $payload->get('appid', $config[get_wechat_type_key($params)] ?? ''), + ], + $this->normal($params, $payload) + )); + + Logger::info('[Wechat][Marketing][Transfer][CreatePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + * @throws DecryptException + * @throws InvalidConfigException + */ + protected function normal(array $params, Collection $payload): array + { + if (!$payload->has('transfer_detail_list.0.user_name')) { + return []; + } + + return $this->encryptSensitiveData($params, $payload); + } + + /** + * @throws ContainerException + * @throws DecryptException + * @throws InvalidConfigException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + protected function encryptSensitiveData(array $params, Collection $payload): array + { + $data['transfer_detail_list'] = $payload->get('transfer_detail_list', []); + $data['_serial_no'] = get_wechat_serial_no($params); + + $config = get_provider_config('wechat', $params); + $publicKey = get_wechat_public_key($config, $data['_serial_no']); + + foreach ($data['transfer_detail_list'] as $key => $list) { + if (!empty($list['user_name'])) { + $data['transfer_detail_list'][$key]['user_name'] = encrypt_wechat_contents($list['user_name'], $publicKey); + } + } + + return $data; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/Detail/QueryByWxPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/Detail/QueryByWxPlugin.php new file mode 100644 index 0000000..e2d7505 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/Detail/QueryByWxPlugin.php @@ -0,0 +1,57 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + $payload = $rocket->getPayload(); + $batchId = $payload?->get('batch_id') ?? null; + $detailId = $payload?->get('detail_id') ?? null; + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 通过微信明细单号查询明细单,只支持普通商户模式,当前配置为服务商模式'); + } + + if (empty($batchId) || empty($detailId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 通过微信明细单号查询明细单,参数缺少 `batch_id` 或 `detail_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/transfer/batches/batch-id/'.$batchId.'/details/detail-id/'.$detailId, + ]); + + Logger::info('[Wechat][Marketing][Transfer][Detail][QueryByWxPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/Detail/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/Detail/QueryPlugin.php new file mode 100644 index 0000000..d401a68 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/Detail/QueryPlugin.php @@ -0,0 +1,57 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + $payload = $rocket->getPayload(); + $outBatchNo = $payload?->get('out_batch_no') ?? null; + $outDetailNo = $payload?->get('out_detail_no') ?? null; + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 通过商家明细单号查询明细单,只支持普通商户模式,当前配置为服务商模式'); + } + + if (empty($outBatchNo) || empty($outDetailNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 通过商家明细单号查询明细单,参数缺少 `out_batch_no` 或 `out_detail_no`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/transfer/batches/out-batch-no/'.$outBatchNo.'/details/out-detail-no/'.$outDetailNo, + ]); + + Logger::info('[Wechat][Marketing][Transfer][Detail][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/DownloadReceiptPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/DownloadReceiptPlugin.php new file mode 100644 index 0000000..049ea5f --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/DownloadReceiptPlugin.php @@ -0,0 +1,57 @@ + $rocket]); + + $downloadUrl = $rocket->getPayload()?->get('download_url') ?? null; + $config = get_provider_config('wechat', $rocket->getParams()); + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 下载电子回单,只支持普通商户模式,当前配置为服务商模式'); + } + + if (empty($downloadUrl)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 下载电子回单,参数缺少 `download_url`'); + } + + $rocket->setDirection(OriginResponseDirection::class) + ->setPayload([ + '_method' => 'GET', + '_url' => $downloadUrl, + ]); + + Logger::info('[Wechat][Marketing][Transfer][DownloadReceiptPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/Receipt/CreatePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/Receipt/CreatePlugin.php new file mode 100644 index 0000000..9775669 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/Receipt/CreatePlugin.php @@ -0,0 +1,50 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 转账账单电子回单申请受理接口,只支持普通商户模式,当前配置为服务商模式'); + } + + $rocket->mergePayload([ + '_method' => 'POST', + '_url' => 'v3/transfer/bill-receipt', + ]); + + Logger::info('[Wechat][Marketing][Transfer][Receipt][CreatePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/Receipt/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/Receipt/QueryPlugin.php new file mode 100644 index 0000000..c4032cc --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/Receipt/QueryPlugin.php @@ -0,0 +1,55 @@ + $rocket]); + + $outBatchNo = $rocket->getPayload()?->get('out_batch_no') ?? null; + $config = get_provider_config('wechat', $rocket->getParams()); + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 查询转账账单电子回单接口,只支持普通商户模式,当前配置为服务商模式'); + } + + if (empty($outBatchNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询转账账单电子回单接口,参数缺少 `out_batch_no`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/transfer/bill-receipt/'.$outBatchNo, + ]); + + Logger::info('[Wechat][Marketing][Transfer][Receipt][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/ReceiptDetail/CreatePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/ReceiptDetail/CreatePlugin.php new file mode 100644 index 0000000..28cd2a8 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/ReceiptDetail/CreatePlugin.php @@ -0,0 +1,50 @@ + $rocket]); + + $config = get_provider_config('wechat', $rocket->getParams()); + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 受理转账明细电子回单,只支持普通商户模式,当前配置为服务商模式'); + } + + $rocket->mergePayload([ + '_method' => 'POST', + '_url' => 'v3/transfer-detail/electronic-receipts', + ]); + + Logger::info('[Wechat][Marketing][Transfer][ReceiptDetail][CreatePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/ReceiptDetail/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/ReceiptDetail/QueryPlugin.php new file mode 100644 index 0000000..efe4670 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Marketing/Transfer/ReceiptDetail/QueryPlugin.php @@ -0,0 +1,56 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $config = get_provider_config('wechat', $rocket->getParams()); + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 查询转账明细电子回单受理结果API,只支持普通商户模式,当前配置为服务商模式'); + } + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询转账明细电子回单受理结果API,参数为空'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/transfer-detail/electronic-receipts?'.filter_params($payload)->query(), + ]); + + Logger::info('[Wechat][Marketing][Transfer][ReceiptDetail][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/ClosePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/ClosePlugin.php new file mode 100644 index 0000000..e6209e8 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/ClosePlugin.php @@ -0,0 +1,78 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $outTradeNo = $payload?->get('out_trade_no') ?? null; + + if (empty($outTradeNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 关闭订单,参数缺少 `out_trade_no`'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config); + } + + $rocket->setDirection(OriginResponseDirection::class) + ->setPayload(array_merge( + [ + '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'/close', + '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'/close', + '_method' => 'POST', + ], + $data ?? $this->normal($config) + )); + + Logger::info('[Wechat][V3][Pay][App][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config): array + { + return [ + 'mchid' => $config['mch_id'] ?? '', + ]; + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/DownloadBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/DownloadBillPlugin.php new file mode 100644 index 0000000..e9ed826 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/DownloadBillPlugin.php @@ -0,0 +1,45 @@ + $rocket]); + + $downloadUrl = $rocket->getPayload()?->get('download_url') ?? null; + + if (empty($downloadUrl)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 下载交易对账单,参数缺少 `download_url`'); + } + + $rocket->setDirection(OriginResponseDirection::class) + ->setPayload([ + '_method' => 'GET', + '_url' => $downloadUrl, + '_service_url' => $downloadUrl, + ]); + + Logger::info('[Wechat][V3][Pay][App][DownloadBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/GetFundBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/GetFundBillPlugin.php new file mode 100644 index 0000000..be05cfd --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/GetFundBillPlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 申请资金账单,参数为空'); + } + + $query = filter_params($payload)->query(); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/bill/fundflowbill?'.$query, + '_service_url' => 'v3/bill/fundflowbill?'.$query, + ]); + + Logger::info('[Wechat][V3][Pay][App][GetFundBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/GetTradeBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/GetTradeBillPlugin.php new file mode 100644 index 0000000..dfba012 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/GetTradeBillPlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 申请交易账单,参数为空'); + } + + $query = filter_params($payload)->query(); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/bill/tradebill?'.$query, + '_service_url' => 'v3/bill/tradebill?'.$query, + ]); + + Logger::info('[Wechat][V3][Pay][App][GetTradeBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/InvokePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/InvokePlugin.php new file mode 100644 index 0000000..1347d97 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/InvokePlugin.php @@ -0,0 +1,111 @@ + $rocket]); + + $destination = $rocket->getDestination(); + $prepayId = $destination?->get('prepay_id'); + + if (is_null($prepayId)) { + Logger::error('[Wechat][V3][Pay][App][InvokePlugin] 预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + + throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('message') ?? '预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + } + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + $rocket->setDestination($this->getInvokeConfig($payload, $config, $prepayId)); + + Logger::info('[Wechat][V3][Pay][App][InvokePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + /** + * @throws InvalidConfigException + * @throws Throwable 生成随机串失败 + */ + protected function getInvokeConfig(?Collection $payload, array $config, string $prepayId): Config + { + $invokeConfig = new Config([ + 'appid' => $this->getAppId($payload, $config), + 'partnerid' => $this->getPartnerId($payload, $config), + 'prepayid' => $prepayId, + 'package' => 'Sign=WXPay', + 'noncestr' => Str::random(32), + 'timestamp' => time().'', + ]); + + $invokeConfig->set('sign', $this->getSign($invokeConfig, $config)); + + return $invokeConfig; + } + + /** + * @throws InvalidConfigException + */ + protected function getSign(Collection $invokeConfig, array $config): string + { + $contents = $invokeConfig->get('appid', '')."\n" + .$invokeConfig->get('timestamp', '')."\n" + .$invokeConfig->get('noncestr', '')."\n" + .$invokeConfig->get('prepayid', '')."\n"; + + return get_wechat_sign($config, $contents); + } + + protected function getAppId(?Collection $payload, array $config): string + { + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + return $payload?->get('_invoke_appid') ?? $config['sub_app_id'] ?? ''; + } + + return $payload?->get('_invoke_appid') ?? $config['app_id'] ?? ''; + } + + protected function getPartnerId(?Collection $payload, array $config): string + { + return $payload?->get('_invoke_partnerid') ?? $config['mch_id'] ?? ''; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/PayPlugin.php new file mode 100644 index 0000000..2b8008d --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/PayPlugin.php @@ -0,0 +1,78 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: APP下单,参数为空'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/pay/transactions/app', + '_service_url' => 'v3/pay/partner/transactions/app', + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''), + ], + $data ?? $this->normal($config) + )); + + Logger::info('[Wechat][V3][Pay][App][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config): array + { + return [ + 'appid' => $config['app_id'] ?? '', + 'mchid' => $config['mch_id'] ?? '', + ]; + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'sp_appid' => $config['app_id'] ?? '', + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/QueryByWxPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/QueryByWxPlugin.php new file mode 100644 index 0000000..b0a7bd7 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/QueryByWxPlugin.php @@ -0,0 +1,68 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $transactionId = $payload?->get('transaction_id') ?? null; + + if (empty($transactionId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 通过微信订单号查询订单,参数缺少 `transaction_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/pay/transactions/id/'.$transactionId.'?'.$this->normal($config), + '_service_url' => 'v3/pay/partner/transactions/id/'.$transactionId.'?'.$this->service($payload, $config), + ]); + + Logger::info('[Wechat][V3][Pay][App][QueryByWxPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config): string + { + return http_build_query([ + 'mchid' => $config['mch_id'] ?? 'null', + ]); + } + + protected function service(Collection $payload, array $config): string + { + return http_build_query([ + 'sp_mchid' => $config['mch_id'] ?? 'null', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? 'null'), + ]); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/QueryPlugin.php new file mode 100644 index 0000000..eb2ded4 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/QueryPlugin.php @@ -0,0 +1,68 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $outTradeNo = $payload?->get('out_trade_no') ?? null; + + if (empty($outTradeNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 通过商户订单号查询订单,参数缺少 `out_trade_no`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'?'.$this->normal($config), + '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'?'.$this->service($payload, $config), + ]); + + Logger::info('[Wechat][V3][Pay][App][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config): string + { + return http_build_query([ + 'mchid' => $config['mch_id'] ?? '', + ]); + } + + protected function service(Collection $payload, array $config): string + { + return http_build_query([ + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/QueryRefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/QueryRefundPlugin.php new file mode 100644 index 0000000..9c09f15 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/QueryRefundPlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $outRefundNo = $payload?->get('out_refund_no') ?? null; + + if (empty($outRefundNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 查询退款订单,参数缺少 `out_refund_no`'); + } + + $subMchId = $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/refund/domestic/refunds/'.$outRefundNo, + '_service_url' => 'v3/refund/domestic/refunds/'.$outRefundNo.'?sub_mchid='.$subMchId, + ]); + + Logger::info('[Wechat][V3][Pay][App][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/RefundPlugin.php new file mode 100644 index 0000000..c66896e --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/App/RefundPlugin.php @@ -0,0 +1,73 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 退款申请,参数为空'); + } + + if (Pay::MODE_SERVICE === $config['mode']) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/refund/domestic/refunds', + '_service_url' => 'v3/refund/domestic/refunds', + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null), + ], + $data ?? $this->normal() + )); + + Logger::info('[Wechat][V3][Pay][App][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(): array + { + return []; + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Bill/DownloadPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Bill/DownloadPlugin.php new file mode 100644 index 0000000..3ec1650 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Bill/DownloadPlugin.php @@ -0,0 +1,45 @@ + $rocket]); + + $downloadUrl = $rocket->getPayload()?->get('download_url') ?? null; + + if (empty($downloadUrl)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 下载交易对账单,参数缺少 `download_url`'); + } + + $rocket->setDirection(OriginResponseDirection::class) + ->setPayload([ + '_method' => 'GET', + '_url' => $downloadUrl, + '_service_url' => $downloadUrl, + ]); + + Logger::info('[Wechat][V3][Pay][Bill][DownloadPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Bill/GetFundPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Bill/GetFundPlugin.php new file mode 100644 index 0000000..2f51eb5 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Bill/GetFundPlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 申请资金账单,参数为空'); + } + + $query = filter_params($payload)->query(); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/bill/fundflowbill?'.$query, + '_service_url' => 'v3/bill/fundflowbill?'.$query, + ]); + + Logger::info('[Wechat][V3][Pay][Bill][GetFundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Bill/GetTradePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Bill/GetTradePlugin.php new file mode 100644 index 0000000..d0f54cc --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Bill/GetTradePlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: App 申请交易账单,参数为空'); + } + + $query = filter_params($payload)->query(); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/bill/tradebill?'.$query, + '_service_url' => 'v3/bill/tradebill?'.$query, + ]); + + Logger::info('[Wechat][V3][Pay][Bill][GetTradePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/AppInvokePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/AppInvokePlugin.php new file mode 100644 index 0000000..8a98c15 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/AppInvokePlugin.php @@ -0,0 +1,111 @@ + $rocket]); + + $destination = $rocket->getDestination(); + $prepayId = $destination?->get('prepay_id'); + + if (is_null($prepayId)) { + Logger::error('[Wechat][Pay][Combine][AppInvokePlugin] 预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + + throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('message') ?? '预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + } + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + $rocket->setDestination($this->getInvokeConfig($payload, $config, $prepayId)); + + Logger::info('[Wechat][Pay][Combine][AppInvokePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + /** + * @throws InvalidConfigException + * @throws Throwable 生成随机串失败 + */ + protected function getInvokeConfig(?Collection $payload, array $config, string $prepayId): Config + { + $invokeConfig = new Config([ + 'appid' => $this->getAppId($payload, $config), + 'partnerid' => $this->getPartnerId($payload, $config), + 'prepayid' => $prepayId, + 'package' => 'Sign=WXPay', + 'noncestr' => Str::random(32), + 'timestamp' => time().'', + ]); + + $invokeConfig->set('sign', $this->getSign($invokeConfig, $config)); + + return $invokeConfig; + } + + /** + * @throws InvalidConfigException + */ + protected function getSign(Collection $invokeConfig, array $config): string + { + $contents = $invokeConfig->get('appid', '')."\n" + .$invokeConfig->get('timestamp', '')."\n" + .$invokeConfig->get('noncestr', '')."\n" + .$invokeConfig->get('prepayid', '')."\n"; + + return get_wechat_sign($config, $contents); + } + + protected function getAppId(?Collection $payload, array $config): string + { + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + return $payload?->get('_invoke_appid') ?? $config['sub_app_id'] ?? ''; + } + + return $payload?->get('_invoke_appid') ?? $config['app_id'] ?? ''; + } + + protected function getPartnerId(?Collection $payload, array $config): string + { + return $payload?->get('_invoke_partnerid') ?? $config['mch_id'] ?? ''; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/AppPayPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/AppPayPlugin.php new file mode 100644 index 0000000..5044c4c --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/AppPayPlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: APP合单 下单,参数为空'); + } + + $rocket->mergePayload([ + '_method' => 'POST', + '_url' => 'v3/combine-transactions/app', + '_service_url' => 'v3/combine-transactions/app', + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''), + 'combine_appid' => $payload->get('combine_appid', $config['app_id'] ?? ''), + 'combine_mchid' => $payload->get('combine_mchid', $config['mch_id'] ?? ''), + ]); + + Logger::info('[Wechat][Pay][Combine][AppPayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/ClosePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/ClosePlugin.php new file mode 100644 index 0000000..8af5263 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/ClosePlugin.php @@ -0,0 +1,57 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $combineOutTradeNo = $payload?->get('combine_out_trade_no') ?? null; + + if (empty($combineOutTradeNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 合单关单,参数缺少 `combine_out_trade_no`'); + } + + $rocket->setDirection(OriginResponseDirection::class) + ->mergePayload([ + '_method' => 'POST', + '_url' => 'v3/combine-transactions/out-trade-no/'.$combineOutTradeNo.'/close', + '_service_url' => 'v3/combine-transactions/out-trade-no/'.$combineOutTradeNo.'/close', + 'combine_appid' => $payload->get('combine_appid', $config[get_wechat_type_key($params)] ?? ''), + ]) + ->exceptPayload('combine_out_trade_no'); + + Logger::info('[Wechat][Pay][Combine][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/DownloadBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/DownloadBillPlugin.php new file mode 100644 index 0000000..91b47fd --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/DownloadBillPlugin.php @@ -0,0 +1,45 @@ + $rocket]); + + $downloadUrl = $rocket->getPayload()?->get('download_url') ?? null; + + if (empty($downloadUrl)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 合单 下载交易对账单,参数缺少 `download_url`'); + } + + $rocket->setDirection(OriginResponseDirection::class) + ->setPayload([ + '_method' => 'GET', + '_url' => $downloadUrl, + '_service_url' => $downloadUrl, + ]); + + Logger::info('[Wechat][Pay][Combine][DownloadBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/GetFundBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/GetFundBillPlugin.php new file mode 100644 index 0000000..4c0850f --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/GetFundBillPlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 合单 申请资金账单,参数为空'); + } + + $query = filter_params($payload)->query(); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/bill/fundflowbill?'.$query, + '_service_url' => 'v3/bill/fundflowbill?'.$query, + ]); + + Logger::info('[Wechat][Pay][Combine][GetFundBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/GetTradeBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/GetTradeBillPlugin.php new file mode 100644 index 0000000..f93b06d --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/GetTradeBillPlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 合单 申请交易账单,参数为空'); + } + + $query = filter_params($payload)->query(); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/bill/tradebill?'.$query, + '_service_url' => 'v3/bill/tradebill?'.$query, + ]); + + Logger::info('[Wechat][Pay][Combine][GetTradeBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/H5PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/H5PayPlugin.php new file mode 100644 index 0000000..76efcd5 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/H5PayPlugin.php @@ -0,0 +1,55 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5合单 下单,参数为空'); + } + + $rocket->mergePayload([ + '_method' => 'POST', + '_url' => 'v3/combine-transactions/h5', + '_service_url' => 'v3/combine-transactions/h5', + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''), + 'combine_appid' => $payload->get('combine_appid', $config[get_wechat_type_key($params)] ?? ''), + 'combine_mchid' => $payload->get('combine_mchid', $config['mch_id'] ?? ''), + ]); + + Logger::info('[Wechat][Pay][Combine][H5PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/JsapiInvokePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/JsapiInvokePlugin.php new file mode 100644 index 0000000..2c469e1 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/JsapiInvokePlugin.php @@ -0,0 +1,106 @@ + $rocket]); + + $destination = $rocket->getDestination(); + $prepayId = $destination?->get('prepay_id'); + + if (is_null($prepayId)) { + Logger::error('[Wechat][Pay][Combine][JsapiInvokePlugin] 预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + + throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('message') ?? '预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + } + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + $rocket->setDestination($this->getInvokeConfig($payload, $params, $config, $prepayId)); + + Logger::info('[Wechat][Pay][Combine][JsapiInvokePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + /** + * @throws InvalidConfigException + * @throws Throwable 生成随机串失败 + */ + protected function getInvokeConfig(?Collection $payload, array $params, array $config, string $prepayId): Config + { + $invokeConfig = new Config([ + 'appId' => $this->getAppId($payload, $config, $params), + 'timeStamp' => time().'', + 'nonceStr' => Str::random(32), + 'package' => 'prepay_id='.$prepayId, + 'signType' => 'RSA', + ]); + + $invokeConfig->set('paySign', $this->getSign($invokeConfig, $config)); + + return $invokeConfig; + } + + /** + * @throws InvalidConfigException + */ + protected function getSign(Collection $invokeConfig, array $config): string + { + $contents = $invokeConfig->get('appId', '')."\n" + .$invokeConfig->get('timeStamp', '')."\n" + .$invokeConfig->get('nonceStr', '')."\n" + .$invokeConfig->get('package', '')."\n"; + + return get_wechat_sign($config, $contents); + } + + protected function getAppId(?Collection $payload, array $config, array $params): string + { + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + return $payload?->get('_invoke_appid') ?? $config['sub_'.get_wechat_type_key($params)] ?? ''; + } + + return $payload?->get('_invoke_appid') ?? $config[get_wechat_type_key($params)] ?? ''; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/JsapiPayPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/JsapiPayPlugin.php new file mode 100644 index 0000000..f4323bd --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/JsapiPayPlugin.php @@ -0,0 +1,55 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi合单 下单,参数为空'); + } + + $rocket->mergePayload([ + '_method' => 'POST', + '_url' => 'v3/combine-transactions/jsapi', + '_service_url' => 'v3/combine-transactions/jsapi', + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''), + 'combine_appid' => $payload->get('combine_appid', $config[get_wechat_type_key($params)] ?? ''), + 'combine_mchid' => $payload->get('combine_mchid', $config['mch_id'] ?? ''), + ]); + + Logger::info('[Wechat][Pay][Combine][JsapiPayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/MiniInvokePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/MiniInvokePlugin.php new file mode 100644 index 0000000..7828158 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/MiniInvokePlugin.php @@ -0,0 +1,105 @@ + $rocket]); + + $destination = $rocket->getDestination(); + $prepayId = $destination?->get('prepay_id'); + + if (is_null($prepayId)) { + Logger::error('[Wechat][Pay][Combine][MiniInvokePlugin] 预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + + throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('message') ?? '预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + } + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + $rocket->setDestination($this->getInvokeConfig($payload, $config, $prepayId)); + + Logger::info('[Wechat][Pay][Combine][MiniInvokePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + /** + * @throws InvalidConfigException + * @throws Throwable 生成随机串失败 + */ + protected function getInvokeConfig(?Collection $payload, array $config, string $prepayId): Config + { + $invokeConfig = new Config([ + 'appId' => $this->getAppId($payload, $config), + 'timeStamp' => time().'', + 'nonceStr' => Str::random(32), + 'package' => 'prepay_id='.$prepayId, + 'signType' => 'RSA', + ]); + + $invokeConfig->set('paySign', $this->getSign($invokeConfig, $config)); + + return $invokeConfig; + } + + /** + * @throws InvalidConfigException + */ + protected function getSign(Collection $invokeConfig, array $config): string + { + $contents = $invokeConfig->get('appId', '')."\n" + .$invokeConfig->get('timeStamp', '')."\n" + .$invokeConfig->get('nonceStr', '')."\n" + .$invokeConfig->get('package', '')."\n"; + + return get_wechat_sign($config, $contents); + } + + protected function getAppId(?Collection $payload, array $config): string + { + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + return $payload?->get('_invoke_appid') ?? $config['sub_mini_app_id'] ?? ''; + } + + return $payload?->get('_invoke_appid') ?? $config['mini_app_id'] ?? ''; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/MiniPayPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/MiniPayPlugin.php new file mode 100644 index 0000000..3ec706b --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/MiniPayPlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini合单 下单,参数为空'); + } + + $rocket->mergePayload([ + '_method' => 'POST', + '_url' => 'v3/combine-transactions/jsapi', + '_service_url' => 'v3/combine-transactions/jsapi', + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''), + 'combine_appid' => $payload->get('combine_appid', $config['mini_app_id'] ?? ''), + 'combine_mchid' => $payload->get('combine_mchid', $config['mch_id'] ?? ''), + ]); + + Logger::info('[Wechat][Pay][Combine][MiniPayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/NativePayPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/NativePayPlugin.php new file mode 100644 index 0000000..c435b67 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/NativePayPlugin.php @@ -0,0 +1,55 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native合单 下单,参数为空'); + } + + $rocket->mergePayload([ + '_method' => 'POST', + '_url' => 'v3/combine-transactions/native', + '_service_url' => 'v3/combine-transactions/native', + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''), + 'combine_appid' => $payload->get('combine_appid', $config[get_wechat_type_key($params)] ?? ''), + 'combine_mchid' => $payload->get('combine_mchid', $config['mch_id'] ?? ''), + ]); + + Logger::info('[Wechat][Pay][Combine][NativePayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/QueryPlugin.php new file mode 100644 index 0000000..c1d5b46 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/QueryPlugin.php @@ -0,0 +1,43 @@ + $rocket]); + + $combineOutTradeNo = $rocket->getPayload()?->get('combine_out_trade_no') ?? null; + + if (empty($combineOutTradeNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 合单查询,参数缺少 `combine_out_trade_no`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/combine-transactions/out-trade-no/'.$combineOutTradeNo, + '_service_url' => 'v3/combine-transactions/out-trade-no/'.$combineOutTradeNo, + ]); + + Logger::info('[Wechat][Pay][Combine][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/QueryRefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/QueryRefundPlugin.php new file mode 100644 index 0000000..9f1c637 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/QueryRefundPlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $outRefundNo = $payload?->get('out_refund_no') ?? null; + + if (empty($outRefundNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 合单查询退款订单,参数缺少 `out_refund_no`'); + } + + $subMchId = $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/refund/domestic/refunds/'.$outRefundNo, + '_service_url' => 'v3/refund/domestic/refunds/'.$outRefundNo.'?sub_mchid='.$subMchId, + ]); + + Logger::info('[Wechat][Pay][Combine][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/RefundPlugin.php new file mode 100644 index 0000000..08efaa8 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Combine/RefundPlugin.php @@ -0,0 +1,73 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 合单 退款申请,参数为空'); + } + + if (Pay::MODE_SERVICE === $config['mode']) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/refund/domestic/refunds', + '_service_url' => 'v3/refund/domestic/refunds', + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null), + ], + $data ?? $this->normal() + )); + + Logger::info('[Wechat][Pay][Combine][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(): array + { + return []; + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/ClosePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/ClosePlugin.php new file mode 100644 index 0000000..4b946bb --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/ClosePlugin.php @@ -0,0 +1,78 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $outTradeNo = $payload?->get('out_trade_no') ?? null; + + if (empty($outTradeNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5 关闭订单,参数缺少 `out_trade_no`'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config); + } + + $rocket->setDirection(OriginResponseDirection::class) + ->setPayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'/close', + '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'/close', + ], + $data ?? $this->normal($config) + )); + + Logger::info('[Wechat][V3][Pay][H5][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config): array + { + return [ + 'mchid' => $config['mch_id'] ?? '', + ]; + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/DownloadBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/DownloadBillPlugin.php new file mode 100644 index 0000000..326f4de --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/DownloadBillPlugin.php @@ -0,0 +1,45 @@ + $rocket]); + + $downloadUrl = $rocket->getPayload()?->get('download_url') ?? null; + + if (empty($downloadUrl)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5 下载交易对账单,参数缺少 `download_url`'); + } + + $rocket->setDirection(OriginResponseDirection::class) + ->setPayload([ + '_method' => 'GET', + '_url' => $downloadUrl, + '_service_url' => $downloadUrl, + ]); + + Logger::info('[Wechat][V3][Pay][H5][DownloadBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/GetFundBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/GetFundBillPlugin.php new file mode 100644 index 0000000..b58789d --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/GetFundBillPlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5 申请资金账单,参数为空'); + } + + $query = filter_params($payload)->query(); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/bill/fundflowbill?'.$query, + '_service_url' => 'v3/bill/fundflowbill?'.$query, + ]); + + Logger::info('[Wechat][V3][Pay][H5][GetFundBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/GetTradeBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/GetTradeBillPlugin.php new file mode 100644 index 0000000..6f420c6 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/GetTradeBillPlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5 申请交易账单,参数为空'); + } + + $query = filter_params($payload)->query(); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/bill/tradebill?'.$query, + '_service_url' => 'v3/bill/tradebill?'.$query, + ]); + + Logger::info('[Wechat][V3][Pay][H5][GetTradeBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/PayPlugin.php new file mode 100644 index 0000000..6fde0cb --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/PayPlugin.php @@ -0,0 +1,81 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5 下单,参数为空'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $params, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/pay/transactions/h5', + '_service_url' => 'v3/pay/partner/transactions/h5', + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''), + ], + $data ?? $this->normal($params, $config) + )); + + Logger::info('[Wechat][V3][Pay][H5][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $params, array $config): array + { + return [ + 'appid' => $config[get_wechat_type_key($params)] ?? '', + 'mchid' => $config['mch_id'] ?? '', + ]; + } + + protected function service(Collection $payload, array $params, array $config): array + { + $configKey = get_wechat_type_key($params); + + return [ + 'sp_appid' => $config[$configKey] ?? '', + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/QueryByWxPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/QueryByWxPlugin.php new file mode 100644 index 0000000..aa81152 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/QueryByWxPlugin.php @@ -0,0 +1,68 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $transactionId = $payload?->get('transaction_id') ?? null; + + if (empty($transactionId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5 通过微信订单号查询订单,参数缺少 `transaction_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/pay/transactions/id/'.$transactionId.'?'.$this->normal($config), + '_service_url' => 'v3/pay/partner/transactions/id/'.$transactionId.'?'.$this->service($payload, $config), + ]); + + Logger::info('[Wechat][V3][Pay][H5][QueryByWxPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config): string + { + return http_build_query([ + 'mchid' => $config['mch_id'] ?? 'null', + ]); + } + + protected function service(Collection $payload, array $config): string + { + return http_build_query([ + 'sp_mchid' => $config['mch_id'] ?? 'null', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? 'null'), + ]); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/QueryPlugin.php new file mode 100644 index 0000000..9786a4b --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/QueryPlugin.php @@ -0,0 +1,68 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $outTradeNo = $payload?->get('out_trade_no') ?? null; + + if (empty($outTradeNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5 通过商户订单号查询订单,参数缺少 `out_trade_no`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'?'.$this->normal($config), + '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'?'.$this->service($payload, $config), + ]); + + Logger::info('[Wechat][V3][Pay][H5][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config): string + { + return http_build_query([ + 'mchid' => $config['mch_id'] ?? '', + ]); + } + + protected function service(Collection $payload, array $config): string + { + return http_build_query([ + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/QueryRefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/QueryRefundPlugin.php new file mode 100644 index 0000000..c282160 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/QueryRefundPlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $outRefundNo = $payload?->get('out_refund_no') ?? null; + + if (empty($outRefundNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5 查询退款订单,参数缺少 `out_refund_no`'); + } + + $subMchId = $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/refund/domestic/refunds/'.$outRefundNo, + '_service_url' => 'v3/refund/domestic/refunds/'.$outRefundNo.'?sub_mchid='.$subMchId, + ]); + + Logger::info('[Wechat][V3][Pay][H5][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/RefundPlugin.php new file mode 100644 index 0000000..9dbd42f --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/H5/RefundPlugin.php @@ -0,0 +1,73 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: H5 退款申请,参数为空'); + } + + if (Pay::MODE_SERVICE === $config['mode']) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/refund/domestic/refunds', + '_service_url' => 'v3/refund/domestic/refunds', + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null), + ], + $data ?? $this->normal() + )); + + Logger::info('[Wechat][V3][Pay][H5][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(): array + { + return []; + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/ClosePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/ClosePlugin.php new file mode 100644 index 0000000..4aad2b0 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/ClosePlugin.php @@ -0,0 +1,78 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $outTradeNo = $payload?->get('out_trade_no') ?? null; + + if (empty($outTradeNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi 关闭订单,参数缺少 `out_trade_no`'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config); + } + + $rocket->setDirection(OriginResponseDirection::class) + ->setPayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'/close', + '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'/close', + ], + $data ?? $this->normal($config) + )); + + Logger::info('[Wechat][Pay][Jsapi][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config): array + { + return [ + 'mchid' => $config['mch_id'] ?? '', + ]; + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/DownloadBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/DownloadBillPlugin.php new file mode 100644 index 0000000..46328e6 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/DownloadBillPlugin.php @@ -0,0 +1,45 @@ + $rocket]); + + $downloadUrl = $rocket->getPayload()?->get('download_url') ?? null; + + if (empty($downloadUrl)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi 下载交易对账单,参数缺少 `download_url`'); + } + + $rocket->setDirection(OriginResponseDirection::class) + ->setPayload([ + '_method' => 'GET', + '_url' => $downloadUrl, + '_service_url' => $downloadUrl, + ]); + + Logger::info('[Wechat][Pay][Jsapi][DownloadBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/GetFundBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/GetFundBillPlugin.php new file mode 100644 index 0000000..2fa7b89 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/GetFundBillPlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi 申请资金账单,参数为空'); + } + + $query = filter_params($payload)->query(); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/bill/fundflowbill?'.$query, + '_service_url' => 'v3/bill/fundflowbill?'.$query, + ]); + + Logger::info('[Wechat][Pay][Jsapi][GetFundBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/GetTradeBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/GetTradeBillPlugin.php new file mode 100644 index 0000000..584807b --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/GetTradeBillPlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi 申请交易账单,参数为空'); + } + + $query = filter_params($payload)->query(); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/bill/tradebill?'.$query, + '_service_url' => 'v3/bill/tradebill?'.$query, + ]); + + Logger::info('[Wechat][Pay][Jsapi][GetTradeBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/InvokePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/InvokePlugin.php new file mode 100644 index 0000000..9762c4f --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/InvokePlugin.php @@ -0,0 +1,106 @@ + $rocket]); + + $destination = $rocket->getDestination(); + $prepayId = $destination?->get('prepay_id'); + + if (is_null($prepayId)) { + Logger::error('[Wechat][V3][Pay][Jsapi][InvokePlugin] 预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + + throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('message') ?? '预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + } + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + $rocket->setDestination($this->getInvokeConfig($payload, $params, $config, $prepayId)); + + Logger::info('[Wechat][V3][Pay][Jsapi][InvokePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + /** + * @throws InvalidConfigException + * @throws Throwable 生成随机串失败 + */ + protected function getInvokeConfig(?Collection $payload, array $params, array $config, string $prepayId): Config + { + $invokeConfig = new Config([ + 'appId' => $this->getAppId($payload, $config, $params), + 'timeStamp' => time().'', + 'nonceStr' => Str::random(32), + 'package' => 'prepay_id='.$prepayId, + 'signType' => 'RSA', + ]); + + $invokeConfig->set('paySign', $this->getSign($invokeConfig, $config)); + + return $invokeConfig; + } + + /** + * @throws InvalidConfigException + */ + protected function getSign(Collection $invokeConfig, array $config): string + { + $contents = $invokeConfig->get('appId', '')."\n" + .$invokeConfig->get('timeStamp', '')."\n" + .$invokeConfig->get('nonceStr', '')."\n" + .$invokeConfig->get('package', '')."\n"; + + return get_wechat_sign($config, $contents); + } + + protected function getAppId(?Collection $payload, array $config, array $params): string + { + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + return $payload?->get('_invoke_appid') ?? $config['sub_'.get_wechat_type_key($params)] ?? ''; + } + + return $payload?->get('_invoke_appid') ?? $config[get_wechat_type_key($params)] ?? ''; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/PayPlugin.php new file mode 100644 index 0000000..7642cf3 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/PayPlugin.php @@ -0,0 +1,87 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi 下单,参数为空'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config, $params); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/pay/transactions/jsapi', + '_service_url' => 'v3/pay/partner/transactions/jsapi', + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''), + ], + $data ?? $this->normal($config, $params) + )); + + Logger::info('[Wechat][V3][Pay][Jsapi][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config, array $params): array + { + return [ + 'appid' => $config[get_wechat_type_key($params)] ?? '', + 'mchid' => $config['mch_id'] ?? '', + ]; + } + + protected function service(Collection $payload, array $config, array $params): array + { + $wechatTypeKey = get_wechat_type_key($params); + + $data = [ + 'sp_appid' => $config[$wechatTypeKey] ?? '', + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + + if ($payload->has('payer.sub_openid')) { + $data['sub_appid'] = $config['sub_'.$wechatTypeKey] ?? ''; + } + + return $data; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/QueryByWxPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/QueryByWxPlugin.php new file mode 100644 index 0000000..bbd08cd --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/QueryByWxPlugin.php @@ -0,0 +1,68 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $transactionId = $payload?->get('transaction_id') ?? null; + + if (empty($transactionId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi 通过微信订单号查询订单,参数缺少 `transaction_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/pay/transactions/id/'.$transactionId.'?'.$this->normal($config), + '_service_url' => 'v3/pay/partner/transactions/id/'.$transactionId.'?'.$this->service($payload, $config), + ]); + + Logger::info('[Wechat][Pay][Jsapi][QueryByWxPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config): string + { + return http_build_query([ + 'mchid' => $config['mch_id'] ?? 'null', + ]); + } + + protected function service(Collection $payload, array $config): string + { + return http_build_query([ + 'sp_mchid' => $config['mch_id'] ?? 'null', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? 'null'), + ]); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/QueryPlugin.php new file mode 100644 index 0000000..c52825a --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/QueryPlugin.php @@ -0,0 +1,68 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $outTradeNo = $payload?->get('out_trade_no') ?? null; + + if (empty($outTradeNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi 通过商户订单号查询订单,参数缺少 `out_trade_no`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'?'.$this->normal($config), + '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'?'.$this->service($payload, $config), + ]); + + Logger::info('[Wechat][Pay][Jsapi][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config): string + { + return http_build_query([ + 'mchid' => $config['mch_id'] ?? '', + ]); + } + + protected function service(Collection $payload, array $config): string + { + return http_build_query([ + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/QueryRefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/QueryRefundPlugin.php new file mode 100644 index 0000000..f050eb3 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/QueryRefundPlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $outRefundNo = $payload?->get('out_refund_no') ?? null; + + if (empty($outRefundNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi 查询退款订单,参数缺少 `out_refund_no`'); + } + + $subMchId = $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/refund/domestic/refunds/'.$outRefundNo, + '_service_url' => 'v3/refund/domestic/refunds/'.$outRefundNo.'?sub_mchid='.$subMchId, + ]); + + Logger::info('[Wechat][Pay][Jsapi][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/RefundPlugin.php new file mode 100644 index 0000000..b419cd6 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Jsapi/RefundPlugin.php @@ -0,0 +1,73 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Jsapi 退款申请,参数为空'); + } + + if (Pay::MODE_SERVICE === $config['mode']) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/refund/domestic/refunds', + '_service_url' => 'v3/refund/domestic/refunds', + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null), + ], + $data ?? $this->normal() + )); + + Logger::info('[Wechat][Pay][Jsapi][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(): array + { + return []; + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/ClosePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/ClosePlugin.php new file mode 100644 index 0000000..cfaf85c --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/ClosePlugin.php @@ -0,0 +1,78 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $outTradeNo = $payload?->get('out_trade_no') ?? null; + + if (empty($outTradeNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 关闭订单,参数缺少 `out_trade_no`'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config); + } + + $rocket->setDirection(OriginResponseDirection::class) + ->setPayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'/close', + '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'/close', + ], + $data ?? $this->normal($config) + )); + + Logger::info('[Wechat][Pay][Mini][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config): array + { + return [ + 'mchid' => $config['mch_id'] ?? '', + ]; + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/DownloadBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/DownloadBillPlugin.php new file mode 100644 index 0000000..27f82e3 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/DownloadBillPlugin.php @@ -0,0 +1,45 @@ + $rocket]); + + $downloadUrl = $rocket->getPayload()?->get('download_url') ?? null; + + if (empty($downloadUrl)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 下载交易对账单,参数缺少 `download_url`'); + } + + $rocket->setDirection(OriginResponseDirection::class) + ->setPayload([ + '_method' => 'GET', + '_url' => $downloadUrl, + '_service_url' => $downloadUrl, + ]); + + Logger::info('[Wechat][Pay][Mini][DownloadBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/GetFundBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/GetFundBillPlugin.php new file mode 100644 index 0000000..4c3f701 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/GetFundBillPlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 申请资金账单,参数为空'); + } + + $query = filter_params($payload)->query(); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/bill/fundflowbill?'.$query, + '_service_url' => 'v3/bill/fundflowbill?'.$query, + ]); + + Logger::info('[Wechat][Pay][Mini][GetFundBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/GetTradeBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/GetTradeBillPlugin.php new file mode 100644 index 0000000..7cd9cbe --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/GetTradeBillPlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 申请交易账单,参数为空'); + } + + $query = filter_params($payload)->query(); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/bill/tradebill?'.$query, + '_service_url' => 'v3/bill/tradebill?'.$query, + ]); + + Logger::info('[Wechat][Pay][Mini][GetTradeBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/InvokePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/InvokePlugin.php new file mode 100644 index 0000000..793de71 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/InvokePlugin.php @@ -0,0 +1,105 @@ + $rocket]); + + $destination = $rocket->getDestination(); + $prepayId = $destination?->get('prepay_id'); + + if (is_null($prepayId)) { + Logger::error('[Wechat][Pay][Mini][InvokePlugin] 预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + + throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('message') ?? '预下单失败:响应缺少 `prepay_id` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null); + } + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + $rocket->setDestination($this->getInvokeConfig($payload, $config, $prepayId)); + + Logger::info('[Wechat][Pay][Mini][InvokePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } + + /** + * @throws InvalidConfigException + * @throws Throwable 生成随机串失败 + */ + protected function getInvokeConfig(?Collection $payload, array $config, string $prepayId): Config + { + $invokeConfig = new Config([ + 'appId' => $this->getAppId($payload, $config), + 'timeStamp' => time().'', + 'nonceStr' => Str::random(32), + 'package' => 'prepay_id='.$prepayId, + 'signType' => 'RSA', + ]); + + $invokeConfig->set('paySign', $this->getSign($invokeConfig, $config)); + + return $invokeConfig; + } + + /** + * @throws InvalidConfigException + */ + protected function getSign(Collection $invokeConfig, array $config): string + { + $contents = $invokeConfig->get('appId', '')."\n" + .$invokeConfig->get('timeStamp', '')."\n" + .$invokeConfig->get('nonceStr', '')."\n" + .$invokeConfig->get('package', '')."\n"; + + return get_wechat_sign($config, $contents); + } + + protected function getAppId(?Collection $payload, array $config): string + { + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + return $payload?->get('_invoke_appid') ?? $config['sub_mini_app_id'] ?? ''; + } + + return $payload?->get('_invoke_appid') ?? $config['mini_app_id'] ?? ''; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/PayPlugin.php new file mode 100644 index 0000000..5549c0d --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/PayPlugin.php @@ -0,0 +1,84 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 下单,参数为空'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/pay/transactions/jsapi', + '_service_url' => 'v3/pay/partner/transactions/jsapi', + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''), + ], + $data ?? $this->normal($config) + )); + + Logger::info('[Wechat][Pay][Mini][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config): array + { + return [ + 'appid' => $config['mini_app_id'] ?? '', + 'mchid' => $config['mch_id'] ?? '', + ]; + } + + protected function service(Collection $payload, array $config): array + { + $data = [ + 'sp_appid' => $config['mini_app_id'] ?? '', + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + + if ($payload->has('payer.sub_openid')) { + $data['sub_appid'] = $config['sub_mini_app_id'] ?? ''; + } + + return $data; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/QueryByWxPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/QueryByWxPlugin.php new file mode 100644 index 0000000..90d6da1 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/QueryByWxPlugin.php @@ -0,0 +1,68 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $transactionId = $payload?->get('transaction_id') ?? null; + + if (empty($transactionId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 通过微信订单号查询订单,参数缺少 `transaction_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/pay/transactions/id/'.$transactionId.'?'.$this->normal($config), + '_service_url' => 'v3/pay/partner/transactions/id/'.$transactionId.'?'.$this->service($payload, $config), + ]); + + Logger::info('[Wechat][Pay][Mini][QueryByWxPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config): string + { + return http_build_query([ + 'mchid' => $config['mch_id'] ?? 'null', + ]); + } + + protected function service(Collection $payload, array $config): string + { + return http_build_query([ + 'sp_mchid' => $config['mch_id'] ?? 'null', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? 'null'), + ]); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/QueryPlugin.php new file mode 100644 index 0000000..22450b1 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/QueryPlugin.php @@ -0,0 +1,68 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $outTradeNo = $payload?->get('out_trade_no') ?? null; + + if (empty($outTradeNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 通过商户订单号查询订单,参数缺少 `out_trade_no`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'?'.$this->normal($config), + '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'?'.$this->service($payload, $config), + ]); + + Logger::info('[Wechat][Pay][Mini][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config): string + { + return http_build_query([ + 'mchid' => $config['mch_id'] ?? '', + ]); + } + + protected function service(Collection $payload, array $config): string + { + return http_build_query([ + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/QueryRefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/QueryRefundPlugin.php new file mode 100644 index 0000000..9302ea0 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/QueryRefundPlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $outRefundNo = $payload?->get('out_refund_no') ?? null; + + if (empty($outRefundNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 查询退款订单,参数缺少 `out_refund_no`'); + } + + $subMchId = $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/refund/domestic/refunds/'.$outRefundNo, + '_service_url' => 'v3/refund/domestic/refunds/'.$outRefundNo.'?sub_mchid='.$subMchId, + ]); + + Logger::info('[Wechat][Pay][Mini][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/RefundPlugin.php new file mode 100644 index 0000000..306f978 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Mini/RefundPlugin.php @@ -0,0 +1,73 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Mini 退款申请,参数为空'); + } + + if (Pay::MODE_SERVICE === $config['mode']) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/refund/domestic/refunds', + '_service_url' => 'v3/refund/domestic/refunds', + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null), + ], + $data ?? $this->normal() + )); + + Logger::info('[Wechat][Pay][Mini][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(): array + { + return []; + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/ClosePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/ClosePlugin.php new file mode 100644 index 0000000..62c65eb --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/ClosePlugin.php @@ -0,0 +1,78 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $outTradeNo = $payload?->get('out_trade_no') ?? null; + + if (empty($outTradeNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native 关闭订单,参数缺少 `out_trade_no`'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $config); + } + + $rocket->setDirection(OriginResponseDirection::class) + ->setPayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'/close', + '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'/close', + ], + $data ?? $this->normal($config) + )); + + Logger::info('[Wechat][Pay][Native][ClosePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config): array + { + return [ + 'mchid' => $config['mch_id'] ?? '', + ]; + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/DownloadBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/DownloadBillPlugin.php new file mode 100644 index 0000000..7c4e11f --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/DownloadBillPlugin.php @@ -0,0 +1,45 @@ + $rocket]); + + $downloadUrl = $rocket->getPayload()?->get('download_url') ?? null; + + if (empty($downloadUrl)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native 下载交易对账单,参数缺少 `download_url`'); + } + + $rocket->setDirection(OriginResponseDirection::class) + ->setPayload([ + '_method' => 'GET', + '_url' => $downloadUrl, + '_service_url' => $downloadUrl, + ]); + + Logger::info('[Wechat][Pay][Native][DownloadBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/GetFundBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/GetFundBillPlugin.php new file mode 100644 index 0000000..af88b6a --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/GetFundBillPlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native 申请资金账单,参数为空'); + } + + $query = filter_params($payload)->query(); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/bill/fundflowbill?'.$query, + '_service_url' => 'v3/bill/fundflowbill?'.$query, + ]); + + Logger::info('[Wechat][V3][Pay][Native][GetFundBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/GetTradeBillPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/GetTradeBillPlugin.php new file mode 100644 index 0000000..88fa99b --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/GetTradeBillPlugin.php @@ -0,0 +1,47 @@ + $rocket]); + + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native 申请交易账单,参数为空'); + } + + $query = filter_params($payload)->query(); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/bill/tradebill?'.$query, + '_service_url' => 'v3/bill/tradebill?'.$query, + ]); + + Logger::info('[Wechat][Pay][Native][GetTradeBillPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/PayPlugin.php new file mode 100644 index 0000000..151b281 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/PayPlugin.php @@ -0,0 +1,81 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native 下单,参数为空'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $params, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/pay/transactions/native', + '_service_url' => 'v3/pay/partner/transactions/native', + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? ''), + ], + $data ?? $this->normal($params, $config) + )); + + Logger::info('[Wechat][V3][Pay][Native][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $params, array $config): array + { + return [ + 'appid' => $config[get_wechat_type_key($params)] ?? '', + 'mchid' => $config['mch_id'] ?? '', + ]; + } + + protected function service(Collection $payload, array $params, array $config): array + { + $configKey = get_wechat_type_key($params); + + return [ + 'sp_appid' => $config[$configKey] ?? '', + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/QueryByWxPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/QueryByWxPlugin.php new file mode 100644 index 0000000..e5b67ad --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/QueryByWxPlugin.php @@ -0,0 +1,68 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $transactionId = $payload?->get('transaction_id') ?? null; + + if (empty($transactionId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native 通过微信订单号查询订单,参数缺少 `transaction_id`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/pay/transactions/id/'.$transactionId.'?'.$this->normal($config), + '_service_url' => 'v3/pay/partner/transactions/id/'.$transactionId.'?'.$this->service($payload, $config), + ]); + + Logger::info('[Wechat][Pay][Native][QueryBatchByWxPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config): string + { + return http_build_query([ + 'mchid' => $config['mch_id'] ?? 'null', + ]); + } + + protected function service(Collection $payload, array $config): string + { + return http_build_query([ + 'sp_mchid' => $config['mch_id'] ?? 'null', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? 'null'), + ]); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/QueryPlugin.php new file mode 100644 index 0000000..50a14d7 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/QueryPlugin.php @@ -0,0 +1,68 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $outTradeNo = $payload?->get('out_trade_no') ?? null; + + if (empty($outTradeNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native 通过商户订单号查询订单,参数缺少 `out_trade_no`'); + } + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'?'.$this->normal($config), + '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'?'.$this->service($payload, $config), + ]); + + Logger::info('[Wechat][V3][Pay][Native][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $config): string + { + return http_build_query([ + 'mchid' => $config['mch_id'] ?? '', + ]); + } + + protected function service(Collection $payload, array $config): string + { + return http_build_query([ + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/QueryRefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/QueryRefundPlugin.php new file mode 100644 index 0000000..ff323c3 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/QueryRefundPlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $outRefundNo = $payload?->get('out_refund_no') ?? null; + + if (empty($outRefundNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native 查询退款订单,参数缺少 `out_refund_no`'); + } + + $subMchId = $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/refund/domestic/refunds/'.$outRefundNo, + '_service_url' => 'v3/refund/domestic/refunds/'.$outRefundNo.'?sub_mchid='.$subMchId, + ]); + + Logger::info('[Wechat][Pay][Native][QueryRefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/RefundPlugin.php new file mode 100644 index 0000000..4a22300 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Native/RefundPlugin.php @@ -0,0 +1,73 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: Native 退款申请,参数为空'); + } + + if (Pay::MODE_SERVICE === $config['mode']) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/refund/domestic/refunds', + '_service_url' => 'v3/refund/domestic/refunds', + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null), + ], + $data ?? $this->normal() + )); + + Logger::info('[Wechat][V3][Pay][Native][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(): array + { + return []; + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Pos/CancelPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Pos/CancelPlugin.php new file mode 100644 index 0000000..2da3ac3 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Pos/CancelPlugin.php @@ -0,0 +1,83 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + $outTradeNo = $payload?->get('out_trade_no') ?? null; + + if (empty($outTradeNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 付款码支付撤销订单,参数缺少 `out_trade_no`'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $params, $config); + } + + $rocket->setPayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'/reverse', + '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'/reverse', + ], + $data ?? $this->normal($params, $config) + )); + + Logger::info('[Wechat][V3][Pay][Pos][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $params, array $config): array + { + return [ + 'appid' => $config[get_wechat_type_key($params)] ?? '', + 'mchid' => $config['mch_id'] ?? '', + ]; + } + + protected function service(Collection $payload, array $params, array $config): array + { + $configKey = get_wechat_type_key($params); + + return [ + 'sp_appid' => $config[$configKey] ?? '', + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Pos/PayPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Pos/PayPlugin.php new file mode 100644 index 0000000..c97f7cb --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Pos/PayPlugin.php @@ -0,0 +1,81 @@ + $rocket]); + + $payload = $rocket->getPayload(); + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 付款码支付,参数为空'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $params, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/pay/transactions/codepay', + '_service_url' => 'v3/pay/partner/transactions/codepay', + ], + $data ?? $this->normal($params, $config) + )); + + Logger::info('[Wechat][V3][Pay][Pos][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(array $params, array $config): array + { + return [ + 'appid' => $config[get_wechat_type_key($params)] ?? '', + 'mchid' => $config['mch_id'] ?? '', + ]; + } + + protected function service(Collection $payload, array $params, array $config): array + { + $configKey = get_wechat_type_key($params); + + return [ + 'sp_appid' => $config[$configKey] ?? '', + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Refund/QueryPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Refund/QueryPlugin.php new file mode 100644 index 0000000..52a4122 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Refund/QueryPlugin.php @@ -0,0 +1,54 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + $outRefundNo = $payload?->get('out_refund_no') ?? null; + + if (empty($outRefundNo)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 查询退款订单,参数缺少 `out_refund_no`'); + } + + $subMchId = $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/refund/domestic/refunds/'.$outRefundNo, + '_service_url' => 'v3/refund/domestic/refunds/'.$outRefundNo.'?sub_mchid='.$subMchId, + ]); + + Logger::info('[Wechat][V3][Pay][Refund][QueryPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Refund/RefundAbnormalPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Refund/RefundAbnormalPlugin.php new file mode 100644 index 0000000..9203689 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Refund/RefundAbnormalPlugin.php @@ -0,0 +1,118 @@ + $rocket]); + + $params = $rocket->getParams(); + $payload = $rocket->getPayload(); + $config = get_provider_config('wechat', $params); + $refundId = $payload?->get('refund_id') ?? null; + + if (empty($refundId)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 发起异常退款,参数缺少 `refund_id`'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($params, $config, $payload); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/refund/domestic/refunds/'.$refundId.'/apply-abnormal-refund', + '_service_url' => 'v3/refund/domestic/refunds/'.$refundId.'/apply-abnormal-refund', + ], + $data ?? $this->normal($params, $config, $payload) + ))->exceptPayload('refund_id'); + + Logger::info('[Wechat][V3][Pay][Refund][RefundAbnormalPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + * @throws DecryptException + * @throws InvalidConfigException + */ + protected function normal(array $params, array $config, Collection $payload): array + { + return $this->encryptSensitiveData($params, $config, $payload); + } + + /** + * @throws ContainerException + * @throws DecryptException + * @throws InvalidConfigException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + protected function service(array $params, array $config, Collection $payload): array + { + $data = [ + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + + return array_merge($data, $this->encryptSensitiveData($params, $config, $payload)); + } + + /** + * @throws ContainerException + * @throws DecryptException + * @throws InvalidConfigException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + protected function encryptSensitiveData(array $params, array $config, Collection $payload): array + { + if ($payload->has('bank_account') && $payload->has('real_name')) { + $data['_serial_no'] = get_wechat_serial_no($params); + + $config = get_provider_config('wechat', $params); + $publicKey = get_wechat_public_key($config, $data['_serial_no']); + + $data['real_name'] = encrypt_wechat_contents($payload->get('real_name'), $publicKey); + $data['bank_account'] = encrypt_wechat_contents($payload->get('bank_account'), $publicKey); + } + + return $data ?? []; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Refund/RefundPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Refund/RefundPlugin.php new file mode 100644 index 0000000..51bf5b4 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/Pay/Refund/RefundPlugin.php @@ -0,0 +1,73 @@ + $rocket]); + + $params = $rocket->getParams(); + $config = get_provider_config('wechat', $params); + $payload = $rocket->getPayload(); + + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 退款申请,参数为空'); + } + + if (Pay::MODE_SERVICE === $config['mode']) { + $data = $this->service($payload, $config); + } + + $rocket->mergePayload(array_merge( + [ + '_method' => 'POST', + '_url' => 'v3/refund/domestic/refunds', + '_service_url' => 'v3/refund/domestic/refunds', + 'notify_url' => $payload->get('notify_url', $config['notify_url'] ?? null), + ], + $data ?? $this->normal() + )); + + Logger::info('[Wechat][V3][Pay][Refund][RefundPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } + + protected function normal(): array + { + return []; + } + + protected function service(Collection $payload, array $config): array + { + return [ + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/VerifySignaturePlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/VerifySignaturePlugin.php new file mode 100644 index 0000000..9f651bc --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/VerifySignaturePlugin.php @@ -0,0 +1,48 @@ + $rocket]); + + if (!should_do_http_request($rocket->getDirection()) || is_null($rocket->getDestinationOrigin())) { + return $rocket; + } + + verify_wechat_sign($rocket->getDestinationOrigin(), $rocket->getParams()); + + Logger::info('[Wechat][V3][VerifySignaturePlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $rocket; + } +} diff --git a/vendor/yansongda/pay/src/Plugin/Wechat/V3/WechatPublicCertsPlugin.php b/vendor/yansongda/pay/src/Plugin/Wechat/V3/WechatPublicCertsPlugin.php new file mode 100644 index 0000000..7d9af61 --- /dev/null +++ b/vendor/yansongda/pay/src/Plugin/Wechat/V3/WechatPublicCertsPlugin.php @@ -0,0 +1,27 @@ + $rocket]); + + $rocket->setPayload([ + '_method' => 'GET', + '_url' => 'v3/certificates', + ]); + + Logger::info('[Wechat][V3][WechatPublicCertsPlugin] 插件装载完毕', ['rocket' => $rocket]); + + return $next($rocket); + } +} diff --git a/vendor/yansongda/pay/src/Provider/Alipay.php b/vendor/yansongda/pay/src/Provider/Alipay.php new file mode 100644 index 0000000..44500ae --- /dev/null +++ b/vendor/yansongda/pay/src/Provider/Alipay.php @@ -0,0 +1,175 @@ + 'https://openapi.alipay.com/gateway.do?charset=utf-8', + Pay::MODE_SANDBOX => 'https://openapi-sandbox.dl.alipaydev.com/gateway.do?charset=utf-8', + Pay::MODE_SERVICE => 'https://openapi.alipay.com/gateway.do?charset=utf-8', + ]; + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function __call(string $shortcut, array $params): null|Collection|MessageInterface|Rocket + { + $plugin = '\Yansongda\Pay\Shortcut\Alipay\\'.Str::studly($shortcut).'Shortcut'; + + return Artful::shortcut($plugin, ...$params); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + */ + public function pay(array $plugins, array $params): null|Collection|MessageInterface|Rocket + { + return Artful::artful($plugins, $params); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function query(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('alipay', __METHOD__, $order, null)); + + return $this->__call('query', [$order]); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function cancel(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('alipay', __METHOD__, $order, null)); + + return $this->__call('cancel', [$order]); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function close(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('alipay', __METHOD__, $order, null)); + + return $this->__call('close', [$order]); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function refund(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('alipay', __METHOD__, $order, null)); + + return $this->__call('refund', [$order]); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + */ + public function callback(null|array|ServerRequestInterface $contents = null, ?array $params = null): Collection + { + $request = $this->getCallbackParams($contents); + + Event::dispatch(new CallbackReceived('alipay', $request->all(), $params, null)); + + return $this->pay([CallbackPlugin::class], $request->merge($params)->all()); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + */ + public function appCallback(null|array|ServerRequestInterface $contents = null, ?array $params = null): Collection + { + $request = $this->getCallbackParams($contents); + + return $this->pay([AppCallbackPlugin::class], $request->merge($params)->all()); + } + + public function success(): ResponseInterface + { + return new Response(200, [], 'success'); + } + + public function mergeCommonPlugins(array $plugins): array + { + return array_merge( + [StartPlugin::class], + $plugins, + [FormatPayloadBizContentPlugin::class, AddPayloadSignaturePlugin::class, AddRadarPlugin::class, VerifySignaturePlugin::class, ResponsePlugin::class, ParserPlugin::class], + ); + } + + protected function getCallbackParams(null|array|ServerRequestInterface $contents = null): Collection + { + if (is_array($contents)) { + return Collection::wrap($contents); + } + + if ($contents instanceof ServerRequestInterface) { + return Collection::wrap('GET' === $contents->getMethod() ? $contents->getQueryParams() + : $contents->getParsedBody()); + } + + $request = ServerRequest::fromGlobals(); + + return Collection::wrap( + array_merge($request->getQueryParams(), $request->getParsedBody()) + ); + } +} diff --git a/vendor/yansongda/pay/src/Provider/Douyin.php b/vendor/yansongda/pay/src/Provider/Douyin.php new file mode 100644 index 0000000..87a0112 --- /dev/null +++ b/vendor/yansongda/pay/src/Provider/Douyin.php @@ -0,0 +1,154 @@ + 'https://developer.toutiao.com/', + Pay::MODE_SANDBOX => 'https://open-sandbox.douyin.com/', + Pay::MODE_SERVICE => 'https://developer.toutiao.com/', + ]; + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function __call(string $shortcut, array $params): null|Collection|MessageInterface|Rocket + { + $plugin = '\Yansongda\Pay\Shortcut\Douyin\\'.Str::studly($shortcut).'Shortcut'; + + return Artful::shortcut($plugin, ...$params); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + */ + public function pay(array $plugins, array $params): null|Collection|MessageInterface|Rocket + { + return Artful::artful($plugins, $params); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function query(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('douyin', __METHOD__, $order, null)); + + return $this->__call('query', [$order]); + } + + /** + * @throws InvalidParamsException + */ + public function cancel(array $order): Collection|Rocket + { + throw new InvalidParamsException(Exception::PARAMS_METHOD_NOT_SUPPORTED, '参数异常: 抖音不支持 cancel API'); + } + + /** + * @throws InvalidParamsException + */ + public function close(array $order): Collection|Rocket + { + throw new InvalidParamsException(Exception::PARAMS_METHOD_NOT_SUPPORTED, '参数异常: 抖音不支持 close API'); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function refund(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('douyin', __METHOD__, $order, null)); + + return $this->__call('refund', [$order]); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + */ + public function callback(null|array|ServerRequestInterface $contents = null, ?array $params = null): Collection|Rocket + { + $request = $this->getCallbackParams($contents); + + Event::dispatch(new CallbackReceived('douyin', $request->all(), $params, null)); + + return $this->pay([CallbackPlugin::class], $request->merge($params)->all()); + } + + public function success(): ResponseInterface + { + return new Response( + 200, + ['Content-Type' => 'application/json'], + json_encode(['err_no' => 0, 'err_tips' => 'success']), + ); + } + + public function mergeCommonPlugins(array $plugins): array + { + return array_merge( + [StartPlugin::class], + $plugins, + [AddPayloadSignaturePlugin::class, AddPayloadBodyPlugin::class, AddRadarPlugin::class, ResponsePlugin::class, ParserPlugin::class], + ); + } + + protected function getCallbackParams(null|array|ServerRequestInterface $contents = null): Collection + { + if (is_array($contents)) { + return Collection::wrap($contents); + } + + if (!$contents instanceof ServerRequestInterface) { + $contents = ServerRequest::fromGlobals(); + } + + $body = Collection::wrap($contents->getParsedBody()); + + if ($body->isNotEmpty()) { + return $body; + } + + return Collection::wrapJson((string) $contents->getBody()); + } +} diff --git a/vendor/yansongda/pay/src/Provider/Jsb.php b/vendor/yansongda/pay/src/Provider/Jsb.php new file mode 100644 index 0000000..d23a2c4 --- /dev/null +++ b/vendor/yansongda/pay/src/Provider/Jsb.php @@ -0,0 +1,152 @@ + 'https://mybank.jsbchina.cn:577/eis/merchant/merchantServices.htm', + Pay::MODE_SANDBOX => 'https://epaytest.jsbchina.cn:9999/eis/merchant/merchantServices.htm', + ]; + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function __call(string $name, array $params): null|Collection|MessageInterface|Rocket + { + $plugin = '\Yansongda\Pay\Shortcut\Jsb\\'.Str::studly($name).'Shortcut'; + + return Artful::shortcut($plugin, ...$params); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + */ + public function pay(array $plugins, array $params): null|Collection|MessageInterface|Rocket + { + return Artful::artful($plugins, $params); + } + + public function mergeCommonPlugins(array $plugins): array + { + return array_merge( + [StartPlugin::class], + $plugins, + [AddPayloadSignPlugin::class, AddRadarPlugin::class, VerifySignaturePlugin::class, ResponsePlugin::class, ParserPlugin::class], + ); + } + + /** + * @throws InvalidParamsException + */ + public function cancel(array $order): Collection|Rocket + { + throw new InvalidParamsException(Exception::PARAMS_METHOD_NOT_SUPPORTED, 'Jsb does not support cancel api'); + } + + /** + * @throws InvalidParamsException + */ + public function close(array $order): Collection|Rocket + { + throw new InvalidParamsException(Exception::PARAMS_METHOD_NOT_SUPPORTED, 'Jsb does not support close api'); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function refund(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('jsb', __METHOD__, $order, null)); + + return $this->__call('refund', [$order]); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + */ + public function callback(null|array|ServerRequestInterface $contents = null, ?array $params = null): Collection|Rocket + { + $request = $this->getCallbackParams($contents); + + Event::dispatch(new CallbackReceived('jsb', $request->all(), $params, null)); + + return $this->pay( + [CallbackPlugin::class], + ['request' => $request, 'params' => $params] + ); + } + + public function success(): ResponseInterface + { + return new Response( + 200, + ['Content-Type' => 'text/html'], + 'success', + ); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function query(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('jsb', __METHOD__, $order, null)); + + return $this->__call('query', [$order]); + } + + protected function getCallbackParams($contents = null): Collection + { + if (is_array($contents)) { + return Collection::wrap($contents); + } + + if ($contents instanceof ServerRequestInterface) { + return Collection::wrap($contents->getParsedBody()); + } + + $request = ServerRequest::fromGlobals(); + + return Collection::wrap($request->getParsedBody()); + } +} diff --git a/vendor/yansongda/pay/src/Provider/Unipay.php b/vendor/yansongda/pay/src/Provider/Unipay.php new file mode 100644 index 0000000..e28703f --- /dev/null +++ b/vendor/yansongda/pay/src/Provider/Unipay.php @@ -0,0 +1,156 @@ + 'https://gateway.95516.com/', + Pay::MODE_SANDBOX => 'https://gateway.test.95516.com/', + Pay::MODE_SERVICE => 'https://gateway.95516.com/', + ]; + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function __call(string $shortcut, array $params): null|Collection|MessageInterface|Rocket + { + $plugin = '\Yansongda\Pay\Shortcut\Unipay\\'.Str::studly($shortcut).'Shortcut'; + + return Artful::shortcut($plugin, ...$params); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + */ + public function pay(array $plugins, array $params): null|Collection|MessageInterface|Rocket + { + return Artful::artful($plugins, $params); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function query(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('unipay', __METHOD__, $order, null)); + + return $this->__call('query', [$order]); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function cancel(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('unipay', __METHOD__, $order, null)); + + return $this->__call('cancel', [$order]); + } + + /** + * @throws InvalidParamsException + */ + public function close(array $order): Collection|Rocket + { + throw new InvalidParamsException(Exception::PARAMS_METHOD_NOT_SUPPORTED, '参数异常: 银联不支持 close API'); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function refund(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('unipay', __METHOD__, $order, null)); + + return $this->__call('refund', [$order]); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + */ + public function callback(null|array|ServerRequestInterface $contents = null, ?array $params = null): Collection|Rocket + { + $request = $this->getCallbackParams($contents); + + Event::dispatch(new CallbackReceived('unipay', $request->all(), $params, null)); + + return $this->pay( + [CallbackPlugin::class], + $request->merge($params)->all() + ); + } + + public function success(): ResponseInterface + { + return new Response(200, [], 'success'); + } + + public function mergeCommonPlugins(array $plugins): array + { + return array_merge( + [StartPlugin::class], + $plugins, + [AddPayloadSignaturePlugin::class, AddPayloadBodyPlugin::class, AddRadarPlugin::class, VerifySignaturePlugin::class, ParserPlugin::class], + ); + } + + protected function getCallbackParams(null|array|ServerRequestInterface $contents = null): Collection + { + if (is_array($contents)) { + return Collection::wrap($contents); + } + + if ($contents instanceof ServerRequestInterface) { + return Collection::wrap($contents->getParsedBody()); + } + + $request = ServerRequest::fromGlobals(); + + return Collection::wrap($request->getParsedBody()); + } +} diff --git a/vendor/yansongda/pay/src/Provider/Wechat.php b/vendor/yansongda/pay/src/Provider/Wechat.php new file mode 100644 index 0000000..cfb464e --- /dev/null +++ b/vendor/yansongda/pay/src/Provider/Wechat.php @@ -0,0 +1,174 @@ + 'https://api.mch.weixin.qq.com/', + Pay::MODE_SANDBOX => 'https://api.mch.weixin.qq.com/sandboxnew/', + Pay::MODE_SERVICE => 'https://api.mch.weixin.qq.com/', + ]; + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function __call(string $shortcut, array $params): null|Collection|MessageInterface|Rocket + { + $plugin = '\Yansongda\Pay\Shortcut\Wechat\\'.Str::studly($shortcut).'Shortcut'; + + return Artful::shortcut($plugin, ...$params); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + */ + public function pay(array $plugins, array $params): null|Collection|MessageInterface|Rocket + { + return Artful::artful($plugins, $params); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function query(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('wechat', __METHOD__, $order, null)); + + return $this->__call('query', [$order]); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function cancel(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('wechat', __METHOD__, $order, null)); + + return $this->__call('cancel', [$order]); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function close(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('wechat', __METHOD__, $order, null)); + + $this->__call('close', [$order]); + + return new Collection(); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function refund(array $order): Collection|Rocket + { + Event::dispatch(new MethodCalled('wechat', __METHOD__, $order, null)); + + return $this->__call('refund', [$order]); + } + + /** + * @throws ContainerException + * @throws InvalidParamsException + */ + public function callback(null|array|ServerRequestInterface $contents = null, ?array $params = null): Collection|Rocket + { + $request = $this->getCallbackParams($contents); + + Event::dispatch(new CallbackReceived('wechat', clone $request, $params, null)); + + return $this->pay( + [CallbackPlugin::class], + ['_request' => $request, '_params' => $params] + ); + } + + public function success(): ResponseInterface + { + return new Response( + 200, + ['Content-Type' => 'application/json'], + json_encode(['code' => 'SUCCESS', 'message' => '成功']), + ); + } + + public function mergeCommonPlugins(array $plugins): array + { + return array_merge( + [StartPlugin::class], + $plugins, + [AddPayloadBodyPlugin::class, AddPayloadSignaturePlugin::class, AddRadarPlugin::class, VerifySignaturePlugin::class, ResponsePlugin::class, ParserPlugin::class], + ); + } + + protected function getCallbackParams(null|array|ServerRequestInterface $contents = null): ServerRequestInterface + { + if (is_array($contents) && isset($contents['body'], $contents['headers'])) { + return new ServerRequest('POST', 'http://localhost', $contents['headers'], $contents['body']); + } + + if (is_array($contents)) { + return new ServerRequest('POST', 'http://localhost', [], json_encode($contents)); + } + + if ($contents instanceof ServerRequestInterface) { + return $contents; + } + + return ServerRequest::fromGlobals(); + } +} diff --git a/vendor/yansongda/pay/src/Service/AlipayServiceProvider.php b/vendor/yansongda/pay/src/Service/AlipayServiceProvider.php new file mode 100644 index 0000000..b74a0bd --- /dev/null +++ b/vendor/yansongda/pay/src/Service/AlipayServiceProvider.php @@ -0,0 +1,24 @@ +{$method}(); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + protected function defaultPlugins(): array + { + return $this->posPlugins(); + } + + protected function agreementPlugins(): array + { + return [ + StartPlugin::class, + AgreementCancelPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function authorizationPlugins(): array + { + return [ + StartPlugin::class, + AuthorizationCancelPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function miniPlugins(): array + { + return [ + StartPlugin::class, + MiniCancelPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function posPlugins(): array + { + return [ + StartPlugin::class, + PosCancelPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function scanPlugins(): array + { + return [ + StartPlugin::class, + ScanCancelPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } +} diff --git a/vendor/yansongda/pay/src/Shortcut/Alipay/CloseShortcut.php b/vendor/yansongda/pay/src/Shortcut/Alipay/CloseShortcut.php new file mode 100644 index 0000000..016d849 --- /dev/null +++ b/vendor/yansongda/pay/src/Shortcut/Alipay/CloseShortcut.php @@ -0,0 +1,159 @@ +{$method}(); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + protected function defaultPlugins(): array + { + return $this->webPlugins(); + } + + protected function agreementPlugins(): array + { + return [ + StartPlugin::class, + AgreementClosePlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function appPlugins(): array + { + return [ + StartPlugin::class, + AppClosePlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function authorizationPlugins(): array + { + return [ + StartPlugin::class, + AuthorizationClosePlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function miniPlugins(): array + { + return [ + StartPlugin::class, + MiniClosePlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function posPlugins(): array + { + return [ + StartPlugin::class, + PosClosePlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function scanPlugins(): array + { + return [ + StartPlugin::class, + ScanClosePlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function h5Plugins(): array + { + return [ + StartPlugin::class, + H5ClosePlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function webPlugins(): array + { + return [ + StartPlugin::class, + WebClosePlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } +} diff --git a/vendor/yansongda/pay/src/Shortcut/Alipay/H5Shortcut.php b/vendor/yansongda/pay/src/Shortcut/Alipay/H5Shortcut.php new file mode 100644 index 0000000..ebc99f4 --- /dev/null +++ b/vendor/yansongda/pay/src/Shortcut/Alipay/H5Shortcut.php @@ -0,0 +1,30 @@ +refundPlugins(); + } + + if (method_exists($this, $method)) { + return $this->{$method}(); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + protected function defaultPlugins(): array + { + return $this->webPlugins(); + } + + protected function agreementPlugins(): array + { + return [ + StartPlugin::class, + AgreementQueryPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function appPlugins(): array + { + return [ + StartPlugin::class, + AppQueryPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function authorizationPlugins(): array + { + return [ + StartPlugin::class, + AuthorizationQueryPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function facePlugins(): array + { + return [ + StartPlugin::class, + FaceQueryPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function miniPlugins(): array + { + return [ + StartPlugin::class, + MiniQueryPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function posPlugins(): array + { + return [ + StartPlugin::class, + PosQueryPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function scanPlugins(): array + { + return [ + StartPlugin::class, + ScanQueryPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function h5Plugins(): array + { + return [ + StartPlugin::class, + H5QueryPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function webPlugins(): array + { + return [ + StartPlugin::class, + WebQueryPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function transferPlugins(): array + { + return [ + StartPlugin::class, + TransferQueryPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function refundPlugins(): array + { + return $this->refundWebPlugins(); + } + + protected function refundAppPlugins(): array + { + return [ + StartPlugin::class, + AppQueryRefundPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function refundAuthorizationPlugins(): array + { + return [ + StartPlugin::class, + AuthorizationQueryRefundPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function refundMiniPlugins(): array + { + return [ + StartPlugin::class, + MiniQueryRefundPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function refundPosPlugins(): array + { + return [ + StartPlugin::class, + PosQueryRefundPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function refundScanPlugins(): array + { + return [ + StartPlugin::class, + ScanQueryRefundPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function refundH5Plugins(): array + { + return [ + StartPlugin::class, + H5QueryRefundPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function refundWebPlugins(): array + { + return [ + StartPlugin::class, + WebQueryRefundPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } +} diff --git a/vendor/yansongda/pay/src/Shortcut/Alipay/RefundShortcut.php b/vendor/yansongda/pay/src/Shortcut/Alipay/RefundShortcut.php new file mode 100644 index 0000000..bf30461 --- /dev/null +++ b/vendor/yansongda/pay/src/Shortcut/Alipay/RefundShortcut.php @@ -0,0 +1,174 @@ +{$method}(); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + protected function defaultPlugins(): array + { + return $this->webPlugins(); + } + + protected function agreementPlugins(): array + { + return [ + StartPlugin::class, + AgreementRefundPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function appPlugins(): array + { + return [ + StartPlugin::class, + AppRefundPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function authorizationPlugins(): array + { + return [ + StartPlugin::class, + AuthorizationRefundPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function miniPlugins(): array + { + return [ + StartPlugin::class, + MiniRefundPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function posPlugins(): array + { + return [ + StartPlugin::class, + PosRefundPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function scanPlugins(): array + { + return [ + StartPlugin::class, + ScanRefundPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function h5Plugins(): array + { + return [ + StartPlugin::class, + H5RefundPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function webPlugins(): array + { + return [ + StartPlugin::class, + WebRefundPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function transferPlugins(): array + { + return [ + StartPlugin::class, + FundTransferRefundPlugin::class, + FormatPayloadBizContentPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } +} diff --git a/vendor/yansongda/pay/src/Shortcut/Alipay/ScanShortcut.php b/vendor/yansongda/pay/src/Shortcut/Alipay/ScanShortcut.php new file mode 100644 index 0000000..ec22dcc --- /dev/null +++ b/vendor/yansongda/pay/src/Shortcut/Alipay/ScanShortcut.php @@ -0,0 +1,32 @@ +{$method}(); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + protected function defaultPlugins(): array + { + return $this->miniPlugins(); + } + + protected function refundPlugins(): array + { + return $this->refundMiniPlugins(); + } + + protected function miniPlugins(): array + { + return [ + StartPlugin::class, + MiniQueryPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function refundMiniPlugins(): array + { + return [ + StartPlugin::class, + MiniQueryRefundPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } +} diff --git a/vendor/yansongda/pay/src/Shortcut/Douyin/RefundShortcut.php b/vendor/yansongda/pay/src/Shortcut/Douyin/RefundShortcut.php new file mode 100644 index 0000000..6bbac50 --- /dev/null +++ b/vendor/yansongda/pay/src/Shortcut/Douyin/RefundShortcut.php @@ -0,0 +1,52 @@ +{$method}(); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + protected function defaultPlugins(): array + { + return $this->miniPlugins(); + } + + protected function miniPlugins(): array + { + return [ + StartPlugin::class, + MiniRefundPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } +} diff --git a/vendor/yansongda/pay/src/Shortcut/Jsb/QueryShortcut.php b/vendor/yansongda/pay/src/Shortcut/Jsb/QueryShortcut.php new file mode 100644 index 0000000..83af51c --- /dev/null +++ b/vendor/yansongda/pay/src/Shortcut/Jsb/QueryShortcut.php @@ -0,0 +1,30 @@ +{$method}(); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + protected function defaultPlugins(): array + { + return $this->webPlugins(); + } + + protected function webPlugins(): array + { + return [ + StartPlugin::class, + webCancelPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ParserPlugin::class, + ]; + } + + protected function qrCodePlugins(): array + { + return [ + StartPlugin::class, + QrCodeCancelPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ParserPlugin::class, + ]; + } + + protected function qraPosPlugins(): array + { + return [ + QraStartPlugin::class, + QraPosCancelQueryPlugin::class, + QraAddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + QraVerifySignaturePlugin::class, + ParserPlugin::class, + ]; + } +} diff --git a/vendor/yansongda/pay/src/Shortcut/Unipay/H5Shortcut.php b/vendor/yansongda/pay/src/Shortcut/Unipay/H5Shortcut.php new file mode 100644 index 0000000..a1ac035 --- /dev/null +++ b/vendor/yansongda/pay/src/Shortcut/Unipay/H5Shortcut.php @@ -0,0 +1,30 @@ +{$method}(); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + protected function defaultPlugins(): array + { + return [ + StartPlugin::class, + PosPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ParserPlugin::class, + ]; + } + + protected function preAuthPlugins(): array + { + return [ + StartPlugin::class, + PosPreAuthPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ParserPlugin::class, + ]; + } + + protected function qraPlugins(): array + { + return [ + QraStartPlugin::class, + PayPlugin::class, + QraAddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + QraVerifySignaturePlugin::class, + ParserPlugin::class, + ]; + } +} diff --git a/vendor/yansongda/pay/src/Shortcut/Unipay/QueryShortcut.php b/vendor/yansongda/pay/src/Shortcut/Unipay/QueryShortcut.php new file mode 100644 index 0000000..7f0b29e --- /dev/null +++ b/vendor/yansongda/pay/src/Shortcut/Unipay/QueryShortcut.php @@ -0,0 +1,97 @@ +{$method}(); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + protected function defaultPlugins(): array + { + return $this->webPlugins(); + } + + protected function webPlugins(): array + { + return [ + StartPlugin::class, + WebQueryPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ParserPlugin::class, + ]; + } + + protected function qrCodePlugins(): array + { + return [ + StartPlugin::class, + QrCodeQueryPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ParserPlugin::class, + ]; + } + + protected function qraPosPlugins(): array + { + return [ + QraStartPlugin::class, + QraPosQueryPlugin::class, + QraAddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + QraVerifySignaturePlugin::class, + ParserPlugin::class, + ]; + } + + protected function qraPosRefundPlugins(): array + { + return [ + QraStartPlugin::class, + QraPosQueryRefundPlugin::class, + QraAddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + QraVerifySignaturePlugin::class, + ParserPlugin::class, + ]; + } +} diff --git a/vendor/yansongda/pay/src/Shortcut/Unipay/RefundShortcut.php b/vendor/yansongda/pay/src/Shortcut/Unipay/RefundShortcut.php new file mode 100644 index 0000000..7539677 --- /dev/null +++ b/vendor/yansongda/pay/src/Shortcut/Unipay/RefundShortcut.php @@ -0,0 +1,83 @@ +{$method}(); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + protected function defaultPlugins(): array + { + return $this->webPlugins(); + } + + protected function webPlugins(): array + { + return [ + StartPlugin::class, + WebRefundPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ParserPlugin::class, + ]; + } + + protected function qrCodePlugins(): array + { + return [ + StartPlugin::class, + QrCodeRefundPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ParserPlugin::class, + ]; + } + + protected function qraPosPlugins(): array + { + return [ + QraStartPlugin::class, + QraPosRefundPlugin::class, + QraAddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + QraVerifySignaturePlugin::class, + ParserPlugin::class, + ]; + } +} diff --git a/vendor/yansongda/pay/src/Shortcut/Unipay/ScanShortcut.php b/vendor/yansongda/pay/src/Shortcut/Unipay/ScanShortcut.php new file mode 100644 index 0000000..74cbf9e --- /dev/null +++ b/vendor/yansongda/pay/src/Shortcut/Unipay/ScanShortcut.php @@ -0,0 +1,89 @@ +{$method}(); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + protected function defaultPlugins(): array + { + return [ + StartPlugin::class, + ScanPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ParserPlugin::class, + ]; + } + + protected function preAuthPlugins(): array + { + return [ + StartPlugin::class, + ScanPreAuthPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ParserPlugin::class, + ]; + } + + protected function preOrderPlugins(): array + { + return [ + StartPlugin::class, + ScanPreOrderPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ParserPlugin::class, + ]; + } + + protected function feePlugins(): array + { + return [ + StartPlugin::class, + ScanFeePlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ParserPlugin::class, + ]; + } +} diff --git a/vendor/yansongda/pay/src/Shortcut/Unipay/WebShortcut.php b/vendor/yansongda/pay/src/Shortcut/Unipay/WebShortcut.php new file mode 100644 index 0000000..5cae9a9 --- /dev/null +++ b/vendor/yansongda/pay/src/Shortcut/Unipay/WebShortcut.php @@ -0,0 +1,30 @@ +{$method}(); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + protected function defaultPlugins(): array + { + return $this->mchTransferPlugins(); + } + + protected function mchTransferPlugins(): array + { + return [ + StartPlugin::class, + CancelPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } +} diff --git a/vendor/yansongda/pay/src/Shortcut/Wechat/CloseShortcut.php b/vendor/yansongda/pay/src/Shortcut/Wechat/CloseShortcut.php new file mode 100644 index 0000000..3b4a178 --- /dev/null +++ b/vendor/yansongda/pay/src/Shortcut/Wechat/CloseShortcut.php @@ -0,0 +1,133 @@ +combinePlugins(); + } + + $method = Str::camel($params['_action'] ?? 'default').'Plugins'; + + if (method_exists($this, $method)) { + return $this->{$method}(); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + protected function defaultPlugins(): array + { + return $this->jsapiPlugins(); + } + + protected function appPlugins(): array + { + return [ + StartPlugin::class, + AppClosePlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function H5Plugins(): array + { + return [ + StartPlugin::class, + H5ClosePlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function jsapiPlugins(): array + { + return [ + StartPlugin::class, + JsapiClosePlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function miniPlugins(): array + { + return [ + StartPlugin::class, + MiniClosePlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function nativePlugins(): array + { + return [ + StartPlugin::class, + NativeClosePlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function combinePlugins(): array + { + return [ + StartPlugin::class, + CombineClosePlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } +} diff --git a/vendor/yansongda/pay/src/Shortcut/Wechat/H5Shortcut.php b/vendor/yansongda/pay/src/Shortcut/Wechat/H5Shortcut.php new file mode 100644 index 0000000..f8a95ec --- /dev/null +++ b/vendor/yansongda/pay/src/Shortcut/Wechat/H5Shortcut.php @@ -0,0 +1,32 @@ +{$method}($params); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + /** + * @throws InvalidParamsException + */ + protected function defaultPlugins(array $params): array + { + return $this->orderPlugins($params); + } + + /** + * @throws InvalidParamsException + */ + protected function orderPlugins(array $params): array + { + return [ + StartPlugin::class, + ContractOrderPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + $this->getInvoke($params), + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + /** + * @throws InvalidParamsException + */ + protected function contractPlugins(array $params): array + { + return match ($params['_type'] ?? 'default') { + 'mini' => [StartPlugin::class, MiniOnlyContractPlugin::class, AddPayloadSignaturePlugin::class], + default => throw new InvalidParamsException(Exception::PARAMS_WECHAT_PAPAY_TYPE_NOT_SUPPORTED, '参数异常: 微信扣关服务纯签约,当前传递的 `_type` 类型不支持') + }; + } + + protected function applyPlugins(): array + { + return [ + StartPlugin::class, + ApplyPlugin::class, + AddPayloadSignaturePlugin::class, + AddPayloadBodyPlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + /** + * @throws InvalidParamsException + */ + protected function getInvoke(array $params): string + { + return match ($params['_type'] ?? 'default') { + 'app' => AppInvokePlugin::class, + 'mini' => MiniInvokePlugin::class, + default => throw new InvalidParamsException(Exception::PARAMS_WECHAT_PAPAY_TYPE_NOT_SUPPORTED, '参数异常: 微信扣关服务支付中签约,当前传递的 `_type` 类型不支持') + }; + } +} diff --git a/vendor/yansongda/pay/src/Shortcut/Wechat/PosShortcut.php b/vendor/yansongda/pay/src/Shortcut/Wechat/PosShortcut.php new file mode 100644 index 0000000..9681f20 --- /dev/null +++ b/vendor/yansongda/pay/src/Shortcut/Wechat/PosShortcut.php @@ -0,0 +1,32 @@ +combinePlugins(); + } + + $method = Str::camel($params['_action'] ?? 'default').'Plugins'; + + if (method_exists($this, $method)) { + return $this->{$method}($params); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + protected function defaultPlugins(): array + { + return $this->jsapiPlugins(); + } + + protected function appPlugins(): array + { + return [ + StartPlugin::class, + AppQueryPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function combinePlugins(): array + { + return [ + StartPlugin::class, + CombineQueryPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function h5Plugins(): array + { + return [ + StartPlugin::class, + H5QueryPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function jsapiPlugins(): array + { + return [ + StartPlugin::class, + JsapiQueryPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function miniPlugins(): array + { + return [ + StartPlugin::class, + MiniQueryPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function nativePlugins(): array + { + return [ + StartPlugin::class, + NativeQueryPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function mchTransferPlugins(array $params): array + { + $query = MchTransferQueryPlugin::class; + + if (isset($params['transfer_bill_no'])) { + $query = MchTransferQueryByWxPlugin::class; + } + + return [ + StartPlugin::class, + $query, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function refundPlugins(): array + { + return $this->refundJsapiPlugins(); + } + + protected function refundAppPlugins(): array + { + return [ + StartPlugin::class, + AppQueryRefundPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function refundCombinePlugins(): array + { + return [ + StartPlugin::class, + CombineQueryRefundPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function refundH5Plugins(): array + { + return [ + StartPlugin::class, + H5QueryRefundPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function refundJsapiPlugins(): array + { + return [ + StartPlugin::class, + JsapiQueryRefundPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function refundMiniPlugins(): array + { + return [ + StartPlugin::class, + MiniQueryRefundPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function refundNativePlugins(): array + { + return [ + StartPlugin::class, + NativeQueryRefundPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function transferPlugins(): array + { + return [ + StartPlugin::class, + TransferQueryPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } +} diff --git a/vendor/yansongda/pay/src/Shortcut/Wechat/RedpackShortcut.php b/vendor/yansongda/pay/src/Shortcut/Wechat/RedpackShortcut.php new file mode 100644 index 0000000..3d5ed4e --- /dev/null +++ b/vendor/yansongda/pay/src/Shortcut/Wechat/RedpackShortcut.php @@ -0,0 +1,32 @@ +{$method}(); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + protected function defaultPlugins(): array + { + return $this->jsapiPlugins(); + } + + protected function appPlugins(): array + { + return [ + StartPlugin::class, + AppRefundPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function combinePlugins(): array + { + return [ + StartPlugin::class, + CombineRefundPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function h5Plugins(): array + { + return [ + StartPlugin::class, + H5RefundPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function jsapiPlugins(): array + { + return [ + StartPlugin::class, + JsapiRefundPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function miniPlugins(): array + { + return [ + StartPlugin::class, + MiniRefundPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + protected function nativePlugins(): array + { + return [ + StartPlugin::class, + NativeRefundPlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } +} diff --git a/vendor/yansongda/pay/src/Shortcut/Wechat/ScanShortcut.php b/vendor/yansongda/pay/src/Shortcut/Wechat/ScanShortcut.php new file mode 100644 index 0000000..9059e51 --- /dev/null +++ b/vendor/yansongda/pay/src/Shortcut/Wechat/ScanShortcut.php @@ -0,0 +1,32 @@ +{$method}(); + } + + throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_ACTION_INVALID, "您所提供的 action 方法 [{$method}] 不支持,请参考文档或源码确认"); + } + + public function defaultPlugins(): array + { + return $this->transferPlugins(); + } + + public function transferPlugins(): array + { + return [ + StartPlugin::class, + \Yansongda\Pay\Plugin\Wechat\V3\Marketing\Transfer\CreatePlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } + + public function mchTransferPlugins(): array + { + return [ + StartPlugin::class, + CreatePlugin::class, + AddPayloadBodyPlugin::class, + AddPayloadSignaturePlugin::class, + AddRadarPlugin::class, + VerifySignaturePlugin::class, + ResponsePlugin::class, + ParserPlugin::class, + ]; + } +} diff --git a/vendor/yansongda/pay/src/Traits/SupportServiceProviderTrait.php b/vendor/yansongda/pay/src/Traits/SupportServiceProviderTrait.php new file mode 100644 index 0000000..14ae8ae --- /dev/null +++ b/vendor/yansongda/pay/src/Traits/SupportServiceProviderTrait.php @@ -0,0 +1,35 @@ +getParams(); + $config = get_provider_config('alipay', $params); + $serviceProviderId = $config['service_provider_id'] ?? null; + + if (Pay::MODE_SERVICE !== ($config['mode'] ?? Pay::MODE_NORMAL) + || empty($serviceProviderId)) { + return; + } + + $rocket->mergeParams([ + 'extend_params' => array_merge($params['extend_params'] ?? [], ['sys_service_provider_id' => $serviceProviderId]), + ]); + } +} diff --git a/vendor/yansongda/supports/.php-cs-fixer.php b/vendor/yansongda/supports/.php-cs-fixer.php new file mode 100644 index 0000000..c638d19 --- /dev/null +++ b/vendor/yansongda/supports/.php-cs-fixer.php @@ -0,0 +1,22 @@ +in('src'); + +return (new PhpCsFixer\Config()) + ->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect()) + ->setUsingCache(false) + ->setRiskyAllowed(true) + ->setRules([ + '@PhpCsFixer' => true, + 'declare_strict_types' => true, + 'single_line_comment_style' => ['comment_types' => ['hash']], + 'general_phpdoc_annotation_remove' => ['annotations' => ['author'], 'case_sensitive' => false], + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => true, + ], + 'multiline_whitespace_before_semicolons' => ['strategy' => 'no_multi_line'], + ]) + ->setFinder($finder); diff --git a/vendor/yansongda/supports/CHANGELOG.md b/vendor/yansongda/supports/CHANGELOG.md new file mode 100644 index 0000000..a11d325 --- /dev/null +++ b/vendor/yansongda/supports/CHANGELOG.md @@ -0,0 +1,10 @@ +# v4.0.0 + +## change + +- php 最低版本改为 8.0 + +## delete + +- 删除 Yansongda\Supports\Logger\StdoutHandler +- 删除 Yansongda\Supports\Logger diff --git a/vendor/yansongda/supports/LICENSE b/vendor/yansongda/supports/LICENSE new file mode 100644 index 0000000..6ceb153 --- /dev/null +++ b/vendor/yansongda/supports/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2017 yansongda + +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. \ No newline at end of file diff --git a/vendor/yansongda/supports/README.md b/vendor/yansongda/supports/README.md new file mode 100644 index 0000000..d74cdfe --- /dev/null +++ b/vendor/yansongda/supports/README.md @@ -0,0 +1,11 @@ +

Supports

+ +[![Linter Status](https://github.com/yansongda/supports/workflows/Coding%20Style/badge.svg)](https://github.com/yansongda/supports/actions) +[![Tester Status](https://github.com/yansongda/supports/workflows/Tester/badge.svg)](https://github.com/yansongda/supports/actions) +[![Latest Stable Version](https://poser.pugx.org/yansongda/supports/v/stable)](https://packagist.org/packages/yansongda/supports) +[![Total Downloads](https://poser.pugx.org/yansongda/supports/downloads)](https://packagist.org/packages/yansongda/supports) +[![License](https://poser.pugx.org/yansongda/supports/license)](https://packagist.org/packages/yansongda/supports) + + +handle with array/config/Pipeline etc. + diff --git a/vendor/yansongda/supports/composer.json b/vendor/yansongda/supports/composer.json new file mode 100644 index 0000000..355560f --- /dev/null +++ b/vendor/yansongda/supports/composer.json @@ -0,0 +1,49 @@ +{ + "name": "yansongda/supports", + "description": "common components", + "keywords": ["support", "array", "collection", "config"], + "support": { + "issues": "https://github.com/yansongda/supports/issues", + "source": "https://github.com/yansongda/supports" + }, + "authors": [ + { + "name": "yansongda", + "email": "me@yansongda.cn" + } + ], + "require": { + "php": ">=8.0", + "ext-mbstring": "*", + "ext-ctype": "*" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "mockery/mockery": "^1.4", + "friendsofphp/php-cs-fixer": "^3.0", + "phpstan/phpstan": "^1.1.0" + }, + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Yansongda\\Supports\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Yansongda\\Supports\\Tests\\": "tests/" + } + }, + "suggest": { + "symfony/console": "Use stdout logger", + "monolog/monolog": "Use logger" + }, + "scripts": { + "test": "./vendor/bin/phpunit -c phpunit.xml --colors=always", + "cs-fix": "php-cs-fixer fix --dry-run --diff 1>&2", + "analyse": "phpstan analyse --memory-limit 300M -l 5 -c phpstan.neon ./src" + }, + "license": "MIT" +} diff --git a/vendor/yansongda/supports/phpstan.neon b/vendor/yansongda/supports/phpstan.neon new file mode 100644 index 0000000..5fd50a9 --- /dev/null +++ b/vendor/yansongda/supports/phpstan.neon @@ -0,0 +1,7 @@ +parameters: + reportUnmatchedIgnoredErrors: false + ignoreErrors: + - + message: '#Unsafe usage of new static\(\)#' + paths: + - src/Collection.php diff --git a/vendor/yansongda/supports/src/Arr.php b/vendor/yansongda/supports/src/Arr.php new file mode 100644 index 0000000..a7a4022 --- /dev/null +++ b/vendor/yansongda/supports/src/Arr.php @@ -0,0 +1,550 @@ +all(); + } elseif (!is_array($values)) { + continue; + } + $results[] = $values; + } + + return array_merge([], ...$results); + } + + public static function crossJoin(...$arrays): array + { + $results = [[]]; + foreach ($arrays as $index => $array) { + $append = []; + foreach ($results as $product) { + foreach ($array as $item) { + $product[$index] = $item; + $append[] = $product; + } + } + $results = $append; + } + + return $results; + } + + public static function divide(array $array): array + { + return [array_keys($array), array_values($array)]; + } + + public static function dot(array $array, string $prepend = ''): array + { + $results = []; + foreach ($array as $key => $value) { + if (is_array($value) && !empty($value)) { + $results = array_merge($results, static::dot($value, $prepend.$key.'.')); + } else { + $results[$prepend.$key] = $value; + } + } + + return $results; + } + + public static function except(array $array, array|string $keys): array + { + static::forget($array, $keys); + + return $array; + } + + public static function exists(array|ArrayAccess $array, int|string $key): bool + { + if ($array instanceof ArrayAccess) { + return $array->offsetExists($key); + } + + return array_key_exists($key, $array); + } + + public static function first(array $array, ?callable $callback = null, mixed $default = null): mixed + { + if (is_null($callback)) { + if (empty($array)) { + return $default; + } + foreach ($array as $item) { + return $item; + } + } + foreach ($array as $key => $value) { + if (call_user_func($callback, $value, $key)) { + return $value; + } + } + + return $default; + } + + public static function last(array $array, ?callable $callback = null, mixed $default = null): mixed + { + if (is_null($callback)) { + return empty($array) ? $default : end($array); + } + + return static::first(array_reverse($array, true), $callback, $default); + } + + public static function flatten(array $array, float|int $depth = INF): array + { + $result = []; + foreach ($array as $item) { + $item = $item instanceof Collection ? $item->all() : $item; + if (!is_array($item)) { + $result[] = $item; + } elseif (1 === $depth) { + $result = array_merge($result, array_values($item)); + } else { + $result = array_merge($result, static::flatten($item, $depth - 1)); + } + } + + return $result; + } + + public static function forget(array &$array, array|string $keys): void + { + $original = &$array; + $keys = (array) $keys; + if (0 === count($keys)) { + return; + } + foreach ($keys as $key) { + // if the exact key exists in the top-level, remove it + if (static::exists($array, $key)) { + unset($array[$key]); + + continue; + } + $parts = explode('.', $key); + // clean up before each pass + $array = &$original; + while (count($parts) > 1) { + $part = array_shift($parts); + if (isset($array[$part]) && is_array($array[$part])) { + $array = &$array[$part]; + } else { + continue 2; + } + } + unset($array[array_shift($parts)]); + } + } + + public static function get(array|ArrayAccess $array, null|int|string $key = null, mixed $default = null): mixed + { + if (!static::accessible($array)) { + return $default; + } + if (is_null($key)) { + return $array; + } + if (static::exists($array, $key)) { + return $array[$key]; + } + if (!is_string($key) || !str_contains($key, '.')) { + return $array[$key] ?? $default; + } + foreach (explode('.', $key) as $segment) { + if (static::accessible($array) && static::exists($array, $segment)) { + $array = $array[$segment]; + } else { + return $default; + } + } + + return $array; + } + + public static function has(array|ArrayAccess $array, null|array|string $keys): bool + { + if (is_null($keys)) { + return false; + } + $keys = (array) $keys; + if (!$array) { + return false; + } + if ([] === $keys) { + return false; + } + foreach ($keys as $key) { + $subKeyArray = $array; + if (static::exists($array, $key)) { + continue; + } + foreach (explode('.', $key) as $segment) { + if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) { + $subKeyArray = $subKeyArray[$segment]; + } else { + return false; + } + } + } + + return true; + } + + public static function isAssoc(array $array): bool + { + $keys = array_keys($array); + + return array_keys($keys) !== $keys; + } + + public static function only(array $array, array|string $keys): array + { + return array_intersect_key($array, array_flip((array) $keys)); + } + + public static function pluck(array $array, array|string $value, null|array|string $key = null): array + { + $results = []; + + foreach ($array as $item) { + $itemValue = data_get($item, $value); + // If the key is "null", we will just append the value to the array and keep + // looping, Otherwise we will key the array using the value of the key we + // received from the developer. Then we'll return the final array form. + if (is_null($key)) { + $results[] = $itemValue; + } else { + $itemKey = data_get($item, $key); + if (is_object($itemKey) && method_exists($itemKey, '__toString')) { + $itemKey = (string) $itemKey; + } + $results[$itemKey] = $itemValue; + } + } + + return $results; + } + + public static function prepend(array $array, mixed $value, mixed $key = null): array + { + if (is_null($key)) { + array_unshift($array, $value); + } else { + $array = [$key => $value] + $array; + } + + return $array; + } + + public static function pull(array &$array, string $key, mixed $default = null): mixed + { + $value = static::get($array, $key, $default); + static::forget($array, $key); + + return $value; + } + + /** + * @throws InvalidArgumentException + */ + public static function random(array $array, int $number = 1): array + { + $count = count($array); + if ($number > $count) { + throw new InvalidArgumentException("You requested {$number} items, but there are only {$count} items available."); + } + + if (0 === $number) { + return []; + } + + $keys = array_rand($array, $number); + $results = []; + foreach ((array) $keys as $key) { + $results[] = $array[$key]; + } + + return $results; + } + + /** + * Set an array item to a given value using "dot" notation. + * If no key is given to the method, the entire array will be replaced. + */ + public static function set(array &$array, null|int|string $key, mixed $value): array + { + if (is_null($key)) { + return $array = $value; + } + if (!is_string($key)) { + $array[$key] = $value; + + return $array; + } + $keys = explode('.', $key); + while (count($keys) > 1) { + $key = array_shift($keys); + // If the key doesn't exist at this depth, we will just create an empty array + // to hold the next value, allowing us to create the arrays to hold final + // values at the correct depth. Then we'll keep digging into the array. + if (!isset($array[$key]) || !is_array($array[$key])) { + $array[$key] = []; + } + $array = &$array[$key]; + } + $array[array_shift($keys)] = $value; + + return $array; + } + + public static function shuffle(array $array, ?int $seed = null): array + { + if (is_null($seed)) { + shuffle($array); + } else { + srand($seed); + usort($array, function () { + return rand(-1, 1); + }); + } + + return $array; + } + + public static function sort(array $array, callable $callback): array + { + $results = []; + + foreach ($array as $key => $value) { + $results[$key] = $callback($value); + } + + return $results; + } + + public static function sortRecursive(array $array): array + { + foreach ($array as &$value) { + if (is_array($value)) { + $value = static::sortRecursive($value); + } + } + if (static::isAssoc($array)) { + ksort($array); + } else { + sort($array); + } + + return $array; + } + + public static function query(array $array, int $encodingType = PHP_QUERY_RFC1738): string + { + return http_build_query($array, '', '&', $encodingType); + } + + public static function toString(array $array, string $separator = '&'): string + { + $result = ''; + + foreach ($array as $key => $value) { + $result .= $key.'='.$value.$separator; + } + + return substr($result, 0, 0 - Str::length($separator)); + } + + public static function where(array $array, callable $callback): array + { + return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH); + } + + public static function wrap(mixed $value): array + { + if (is_null($value)) { + return []; + } + + return !is_array($value) ? [$value] : $value; + } + + public static function wrapJson(string $json): ?array + { + $result = json_decode($json, true); + + return is_array($result) ? $result : null; + } + + public static function wrapXml(string $xml): array + { + if (empty($xml)) { + return []; + } + + $data = json_decode(json_encode( + simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA), + JSON_UNESCAPED_UNICODE + ), true); + + if (JSON_ERROR_NONE === json_last_error()) { + return $data; + } + + return []; + } + + /** + * @param bool $raw 是否原始解析,有些情况下,原始解析会更好 + * @param bool $spaceToPlus 是否将空格转换为加号 + */ + public static function wrapQuery(string $query, bool $raw = false, bool $spaceToPlus = false): array + { + if (!$raw) { + parse_str($query, $data); + + return $spaceToPlus ? self::querySpaceToPlus($data) : $data; + } + + if (empty($query) || !Str::contains($query, '=')) { + return []; + } + + $result = []; + + foreach (explode('&', $query) as $item) { + $pos = strpos($item, '='); + + $result[substr($item, 0, $pos)] = substr($item, $pos + 1); + } + + return $result; + } + + public static function querySpaceToPlus(array $data): array + { + foreach ($data as $key => $item) { + $data[$key] = is_array($item) ? self::querySpaceToPlus($item) : str_replace(' ', '+', $item); + } + + return $data; + } + + public static function unique(array $array): array + { + $result = []; + foreach ($array as $key => $item) { + if (is_array($item)) { + $result[$key] = self::unique($item); + } else { + $result[$key] = $item; + } + } + + if (!self::isAssoc($result)) { + return array_unique($result); + } + + return $result; + } + + public static function merge(array $array1, array $array2, bool $unique = true): array + { + $isAssoc = static::isAssoc($array1 ?: $array2); + if ($isAssoc) { + foreach ($array2 as $key => $value) { + if (is_array($value)) { + $array1[$key] = static::merge($array1[$key] ?? [], $value, $unique); + } else { + $array1[$key] = $value; + } + } + } else { + foreach ($array2 as $value) { + if ($unique && in_array($value, $array1, true)) { + continue; + } + $array1[] = $value; + } + + $array1 = array_values($array1); + } + + return $array1; + } + + public static function encoding(array $array, string $to_encoding, string $from_encoding = 'gb2312'): array + { + $encoded = []; + + foreach ($array as $key => $value) { + $encoded[$key] = is_array($value) ? self::encoding($value, $to_encoding, $from_encoding) : + mb_convert_encoding($value, $to_encoding, $from_encoding); + } + + return $encoded; + } + + public static function camelCaseKey(mixed $data): mixed + { + if (!self::accessible($data) + && !(is_object($data) && method_exists($data, 'toArray'))) { + return $data; + } + + $result = []; + + foreach ((is_object($data) ? $data->toArray() : $data) as $key => $value) { + $result[is_string($key) ? Str::camel($key) : $key] = self::camelCaseKey($value); + } + + return $result; + } + + public static function snakeCaseKey(mixed $data): mixed + { + if (!self::accessible($data) + && !(is_object($data) && method_exists($data, 'toArray'))) { + return $data; + } + + $result = []; + + foreach ((is_object($data) ? $data->toArray() : $data) as $key => $value) { + $result[is_string($key) ? Str::snake($key) : $key] = self::snakeCaseKey($value); + } + + return $result; + } +} diff --git a/vendor/yansongda/supports/src/Collection.php b/vendor/yansongda/supports/src/Collection.php new file mode 100644 index 0000000..2c3d349 --- /dev/null +++ b/vendor/yansongda/supports/src/Collection.php @@ -0,0 +1,361 @@ +getArrayableItems($items) as $key => $value) { + $this->set($key, $value); + } + } + + public static function wrap(mixed $value): self + { + return $value instanceof self ? new static($value) : new static(Arr::wrap($value)); + } + + public static function wrapJson(string $json): self + { + return new static(Arr::wrapJson($json)); + } + + public static function wrapXml(string $xml): self + { + return new static(Arr::wrapXml($xml)); + } + + public static function wrapQuery(string $query, bool $raw = false, bool $spaceToPlus = false): self + { + return new static(Arr::wrapQuery($query, $raw, $spaceToPlus)); + } + + public static function unwrap(array|Collection $value): array + { + return $value instanceof self ? $value->all() : $value; + } + + public function all(): array + { + return $this->items; + } + + public function only(array $keys): array + { + $return = []; + + foreach ($keys as $key) { + $value = $this->get($key); + + if (!is_null($value)) { + $return[$key] = $value; + } + } + + return $return; + } + + public function except(mixed $keys): self + { + $keys = is_array($keys) ? $keys : func_get_args(); + + return new static(Arr::except($this->items, $keys)); + } + + public function filter(?callable $callback = null): self + { + if ($callback) { + return new static(Arr::where($this->items, $callback)); + } + + return new static(array_filter($this->items)); + } + + public function merge(mixed $items): self + { + return new static(array_merge($this->items, $this->getArrayableItems($items))); + } + + public function has(int|string $key): bool + { + return !is_null(Arr::get($this->items, $key)); + } + + public function first(): mixed + { + return reset($this->items); + } + + public function last(): mixed + { + $end = end($this->items); + + reset($this->items); + + return $end; + } + + public function add(null|int|string $key, mixed $value): void + { + Arr::set($this->items, $key, $value); + } + + public function set(null|int|string $key, mixed $value): void + { + Arr::set($this->items, $key, $value); + } + + public function get(null|int|string $key = null, mixed $default = null): mixed + { + return Arr::get($this->items, $key, $default); + } + + public function forget(int|string $key): void + { + Arr::forget($this->items, $key); + } + + public function flatten(float|int $depth = INF): self + { + return new static(Arr::flatten($this->items, $depth)); + } + + public function map(callable $callback): self + { + $keys = array_keys($this->items); + $items = array_map($callback, $this->items, $keys); + + return new static(array_combine($keys, $items)); + } + + public function pop(): mixed + { + return array_pop($this->items); + } + + public function prepend(mixed $value, mixed $key = null): self + { + $this->items = Arr::prepend($this->items, $value, $key); + + return $this; + } + + public function push(mixed $value): self + { + $this->offsetSet(null, $value); + + return $this; + } + + public function pull(mixed $key, mixed $default = null): mixed + { + return Arr::pull($this->items, $key, $default); + } + + public function put(mixed $key, mixed $value): self + { + $this->offsetSet($key, $value); + + return $this; + } + + /** + * @throws InvalidArgumentException + */ + public function random(?int $number = null): self + { + return new static(Arr::random($this->items, $number ?? 1)); + } + + public function reduce(callable $callback, mixed $initial = null): mixed + { + return array_reduce($this->items, $callback, $initial); + } + + public function values(): self + { + return new static(array_values($this->items)); + } + + public function every(callable|string $key): bool + { + $callback = $this->valueRetriever($key); + + foreach ($this->items as $k => $v) { + if (!$callback($v, $k)) { + return false; + } + } + + return true; + } + + public function chunk(int $size): self + { + if ($size <= 0) { + return new static(); + } + $chunks = []; + foreach (array_chunk($this->items, $size, true) as $chunk) { + $chunks[] = new static($chunk); + } + + return new static($chunks); + } + + public function sort(?callable $callback = null): self + { + $items = $this->items; + $callback ? uasort($items, $callback) : asort($items); + + return new static($items); + } + + public function sortBy(callable|string $callback, int $options = SORT_REGULAR, bool $descending = false): self + { + $results = []; + $callback = $this->valueRetriever($callback); + // First we will loop through the items and get the comparator from a callback + // function which we were given. Then, we will sort the returned values + // and grab the corresponding values for the sorted keys from this array. + foreach ($this->items as $key => $value) { + $results[$key] = $callback($value, $key); + } + $descending ? arsort($results, $options) : asort($results, $options); + // Once we have sorted all the keys in the array, we will loop through them + // and grab the corresponding model, so we can set the underlying items list + // to the sorted version. Then we'll just return the collection instance. + foreach (array_keys($results) as $key) { + $results[$key] = $this->items[$key]; + } + + return new static($results); + } + + public function sortByDesc(callable|string $callback, int $options = SORT_REGULAR): self + { + return $this->sortBy($callback, $options, true); + } + + public function sortKeys(int $options = SORT_REGULAR, bool $descending = false): self + { + $items = $this->items; + $descending ? krsort($items, $options) : ksort($items, $options); + + return new static($items); + } + + public function sortKeysDesc(int $options = SORT_REGULAR): self + { + return $this->sortKeys($options, true); + } + + public function query(int $encodingType = PHP_QUERY_RFC1738): string + { + return Arr::query($this->all(), $encodingType); + } + + public function toString(string $separator = '&'): string + { + return Arr::toString($this->all(), $separator); + } + + public function toArray(): array + { + return $this->all(); + } + + public function toXml(): string + { + $xml = ''; + + foreach ($this->all() as $key => $val) { + $xml .= is_numeric($val) ? '<'.$key.'>'.$val.'' : + '<'.$key.'>'; + } + + $xml .= ''; + + return $xml; + } + + public function getIterator(): Traversable + { + return new ArrayIterator($this->items); + } + + public function count(): int + { + return count($this->items); + } + + public function isEmpty(): bool + { + return empty($this->items); + } + + public function isNotEmpty(): bool + { + return !$this->isEmpty(); + } + + public function offsetUnset(mixed $offset): void + { + if ($this->offsetExists($offset)) { + $this->forget($offset); + } + } + + protected function useAsCallable(mixed $value): bool + { + return !is_string($value) && is_callable($value); + } + + protected function valueRetriever(mixed $value): callable + { + if ($this->useAsCallable($value)) { + return $value; + } + + return function ($item) use ($value) { + return data_get($item, $value); + }; + } + + protected function getArrayableItems(mixed $items): array + { + if (is_array($items)) { + return $items; + } + + if ($items instanceof self) { + return $items->all(); + } + + if ($items instanceof JsonSerializable) { + return $items->jsonSerialize(); + } + + return (array) $items; + } +} diff --git a/vendor/yansongda/supports/src/Config.php b/vendor/yansongda/supports/src/Config.php new file mode 100644 index 0000000..2788d05 --- /dev/null +++ b/vendor/yansongda/supports/src/Config.php @@ -0,0 +1,7 @@ +all(); + } elseif (!is_array($target)) { + return value($default); + } + $result = []; + foreach ($target as $item) { + $result[] = data_get($item, $key); + } + + return in_array('*', $key) ? Arr::collapse($result) : $result; + } + if (Arr::accessible($target) && Arr::exists($target, $segment)) { + $target = $target[$segment]; + } elseif (is_object($target) && isset($target->{$segment})) { + $target = $target->{$segment}; + } else { + return value($default); + } + } + + return $target; + } +} diff --git a/vendor/yansongda/supports/src/Pipeline.php b/vendor/yansongda/supports/src/Pipeline.php new file mode 100644 index 0000000..dcaa29f --- /dev/null +++ b/vendor/yansongda/supports/src/Pipeline.php @@ -0,0 +1,115 @@ +container = $container; + } + + public function send(mixed $passable): self + { + $this->passable = $passable; + + return $this; + } + + public function through(mixed $pipes): self + { + $this->pipes = is_array($pipes) ? $pipes : func_get_args(); + + return $this; + } + + /** + * Set the method to call on the pipes. + */ + public function via(string $method): self + { + $this->method = $method; + + return $this; + } + + public function then(Closure $destination): mixed + { + $pipeline = array_reduce(array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)); + + return $pipeline($this->passable); + } + + protected function prepareDestination(Closure $destination): Closure + { + return static function ($passable) use ($destination) { + return $destination($passable); + }; + } + + protected function carry(): Closure + { + return function ($stack, $pipe) { + return function ($passable) use ($stack, $pipe) { + if (is_callable($pipe)) { + // If the pipe is an instance of a Closure, we will just call it directly, but + // otherwise we'll resolve the pipes out of the container and call it with + // the appropriate method and arguments, returning the results back out. + return $pipe($passable, $stack); + } + if (!is_object($pipe)) { + [$name, $parameters] = $this->parsePipeString($pipe); + + // If the pipe is a string we will parse the string and resolve the class out + // of the dependency injection container. We can then build a callable and + // execute the pipe function giving in the parameters that are required. + $pipe = $this->container->get($name); + + $parameters = array_merge([$passable, $stack], $parameters); + } else { + // If the pipe is already an object we'll just make a callable and pass it to + // the pipe as-is. There is no need to do any extra parsing and formatting + // since the object we're given was already a fully instantiated object. + $parameters = [$passable, $stack]; + } + + $carry = method_exists($pipe, $this->method) ? $pipe->{$this->method}(...$parameters) : $pipe(...$parameters); + + return $this->handleCarry($carry); + }; + }; + } + + protected function parsePipeString(string $pipe): array + { + [$name, $parameters] = array_pad(explode(':', $pipe, 2), 2, []); + + if (is_string($parameters)) { + $parameters = explode(',', $parameters); + } + + return [$name, $parameters]; + } + + protected function handleCarry(mixed $carry): mixed + { + return $carry; + } +} diff --git a/vendor/yansongda/supports/src/Str.php b/vendor/yansongda/supports/src/Str.php new file mode 100644 index 0000000..2abad72 --- /dev/null +++ b/vendor/yansongda/supports/src/Str.php @@ -0,0 +1,455 @@ + $val) { + $value = str_replace($val, $key, $value); + } + + return preg_replace('/[^\x20-\x7E]/u', '', $value); + } + + public static function before(string $subject, string $search): string + { + return '' === $search ? $subject : explode($search, $subject)[0]; + } + + public static function camel(string $value): string + { + return lcfirst(static::studly($value)); + } + + public static function contains(string $haystack, array|string $needles): bool + { + foreach ((array) $needles as $needle) { + if (str_contains($haystack, $needle)) { + return true; + } + } + + return false; + } + + public static function endsWith(string $haystack, array|string $needles): bool + { + foreach ((array) $needles as $needle) { + if (str_ends_with($haystack, $needle)) { + return true; + } + } + + return false; + } + + public static function finish(string $value, string $cap): string + { + $quoted = preg_quote($cap, '/'); + + return preg_replace('/(?:'.$quoted.')+$/u', '', $value).$cap; + } + + public static function is(array|string $pattern, string $value): bool + { + $patterns = Arr::wrap($pattern); + + if (empty($patterns)) { + return false; + } + + foreach ($patterns as $pattern) { + // If the given value is an exact match we can of course return true right + // from the beginning. Otherwise, we will translate asterisks and do an + // actual pattern match against the two strings to see if they match. + if ($pattern == $value) { + return true; + } + + $pattern = preg_quote($pattern, '#'); + + // Asterisks are translated into zero-or-more regular expression wildcards + // to make it convenient to check if the strings starts with the given + // pattern such as "library/*", making any string check convenient. + $pattern = str_replace('\*', '.*', $pattern); + + if (1 === preg_match('#^'.$pattern.'\z#u', $value)) { + return true; + } + } + + return false; + } + + public static function kebab(string $value): string + { + return static::snake($value, '-'); + } + + public static function length(string $value, ?string $encoding = null): int + { + if (null !== $encoding) { + return mb_strlen($value, $encoding); + } + + return mb_strlen($value); + } + + public static function limit(string $value, int $limit = 100, string $end = '...'): string + { + if (mb_strwidth($value, 'UTF-8') <= $limit) { + return $value; + } + + return rtrim(mb_strimwidth($value, 0, $limit, '', 'UTF-8')).$end; + } + + public static function lower(string $value): string + { + return mb_strtolower($value, 'UTF-8'); + } + + public static function words(string $value, int $words = 100, string $end = '...'): string + { + preg_match('/^\s*+\S++\s*+{1,'.$words.'}/u', $value, $matches); + + if (!isset($matches[0]) || static::length($value) === static::length($matches[0])) { + return $value; + } + + return rtrim($matches[0]).$end; + } + + public static function parseCallback(string $callback, ?string $default = null): array + { + return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default]; + } + + public static function random(int $length = 16): string + { + $string = ''; + + while (($len = strlen($string)) < $length) { + $size = $length - $len; + + $bytes = random_bytes($size); + + $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size); + } + + return $string; + } + + public static function uuidV4(): string + { + return sprintf( + '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + // 32 bits for "time_low" + mt_rand(0, 0xFFFF), + mt_rand(0, 0xFFFF), + + // 16 bits for "time_mid" + mt_rand(0, 0xFFFF), + + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 4 + mt_rand(0, 0x0FFF) | 0x4000, + + // 16 bits, 8 bits for "clk_seq_hi_res", + // 8 bits for "clk_seq_low", + // two most significant bits holds zero and one for variant DCE1.1 + mt_rand(0, 0x3FFF) | 0x8000, + + // 48 bits for "node" + mt_rand(0, 0xFFFF), + mt_rand(0, 0xFFFF), + mt_rand(0, 0xFFFF) + ); + } + + public static function replaceArray(string $search, array $replace, string $subject): string + { + foreach ($replace as $value) { + $subject = static::replaceFirst($search, $value, $subject); + } + + return $subject; + } + + public static function replaceFirst(string $search, string $replace, string $subject): string + { + if ('' == $search) { + return $subject; + } + + $position = strpos($subject, $search); + + if (false !== $position) { + return substr_replace($subject, $replace, $position, strlen($search)); + } + + return $subject; + } + + public static function replaceLast(string $search, string $replace, string $subject): string + { + $position = strrpos($subject, $search); + + if (false !== $position) { + return substr_replace($subject, $replace, $position, strlen($search)); + } + + return $subject; + } + + public static function start(string $value, string $prefix): string + { + $quoted = preg_quote($prefix, '/'); + + return $prefix.preg_replace('/^(?:'.$quoted.')+/u', '', $value); + } + + public static function upper(string $value): string + { + return mb_strtoupper($value, 'UTF-8'); + } + + public static function title(string $value): string + { + return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); + } + + public static function slug(string $title, string $separator = '-', string $language = 'en'): string + { + $title = static::ascii($title, $language); + + // Convert all dashes/underscores into separator + $flip = '-' == $separator ? '_' : '-'; + + $title = preg_replace('!['.preg_quote($flip).']+!u', $separator, $title); + + // Replace @ with the word 'at' + $title = str_replace('@', $separator.'at'.$separator, $title); + + // Remove all characters that are not the separator, letters, numbers, or whitespace. + $title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', mb_strtolower($title)); + + // Replace all separator characters and whitespace by a single separator + $title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title); + + return trim($title, $separator); + } + + public static function snake(string $value, string $delimiter = '_'): string + { + if (!ctype_lower($value)) { + $value = preg_replace('/\s+/u', '', ucwords($value)); + + $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $value)); + } + + return $value; + } + + public static function startsWith(int|string $haystack, array|string $needles): bool + { + foreach ((array) $needles as $needle) { + if (str_starts_with(strval($haystack), $needle)) { + return true; + } + } + + return false; + } + + public static function studly(string $value, string $gap = ''): string + { + $value = ucwords(str_replace(['-', '_'], ' ', $value)); + + return str_replace(' ', $gap, $value); + } + + public static function substr(string $string, int $start, ?int $length = null): string + { + return mb_substr($string, $start, $length, 'UTF-8'); + } + + public static function ucfirst(string $string): string + { + return static::upper(static::substr($string, 0, 1)).static::substr($string, 1); + } + + public static function encoding(string $string, string $to = 'utf-8', string $from = 'gb2312'): string + { + return mb_convert_encoding($string, $to, $from); + } + + /** + * @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt + */ + protected static function charsArray(): array + { + static $charsArray; + + if (isset($charsArray)) { + return $charsArray; + } + + return $charsArray = [ + '0' => ['°', '₀', '۰', '0'], + '1' => ['¹', '₁', '۱', '1'], + '2' => ['²', '₂', '۲', '2'], + '3' => ['³', '₃', '۳', '3'], + '4' => ['⁴', '₄', '۴', '٤', '4'], + '5' => ['⁵', '₅', '۵', '٥', '5'], + '6' => ['⁶', '₆', '۶', '٦', '6'], + '7' => ['⁷', '₇', '۷', '7'], + '8' => ['⁸', '₈', '۸', '8'], + '9' => ['⁹', '₉', '۹', '9'], + 'a' => ['à', 'á', 'ả', 'ã', 'ạ', 'ă', 'ắ', 'ằ', 'ẳ', 'ẵ', 'ặ', 'â', 'ấ', 'ầ', 'ẩ', 'ẫ', 'ậ', 'ā', 'ą', 'å', 'α', 'ά', 'ἀ', 'ἁ', 'ἂ', 'ἃ', 'ἄ', 'ἅ', 'ἆ', 'ἇ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ὰ', 'ά', 'ᾰ', 'ᾱ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'а', 'أ', 'အ', 'ာ', 'ါ', 'ǻ', 'ǎ', 'ª', 'ა', 'अ', 'ا', 'a', 'ä'], + 'b' => ['б', 'β', 'ب', 'ဗ', 'ბ', 'b'], + 'c' => ['ç', 'ć', 'č', 'ĉ', 'ċ', 'c'], + 'd' => ['ď', 'ð', 'đ', 'ƌ', 'ȡ', 'ɖ', 'ɗ', 'ᵭ', 'ᶁ', 'ᶑ', 'д', 'δ', 'د', 'ض', 'ဍ', 'ဒ', 'დ', 'd'], + 'e' => ['é', 'è', 'ẻ', 'ẽ', 'ẹ', 'ê', 'ế', 'ề', 'ể', 'ễ', 'ệ', 'ë', 'ē', 'ę', 'ě', 'ĕ', 'ė', 'ε', 'έ', 'ἐ', 'ἑ', 'ἒ', 'ἓ', 'ἔ', 'ἕ', 'ὲ', 'έ', 'е', 'ё', 'э', 'є', 'ə', 'ဧ', 'ေ', 'ဲ', 'ე', 'ए', 'إ', 'ئ', 'e'], + 'f' => ['ф', 'φ', 'ف', 'ƒ', 'ფ', 'f'], + 'g' => ['ĝ', 'ğ', 'ġ', 'ģ', 'г', 'ґ', 'γ', 'ဂ', 'გ', 'گ', 'g'], + 'h' => ['ĥ', 'ħ', 'η', 'ή', 'ح', 'ه', 'ဟ', 'ှ', 'ჰ', 'h'], + 'i' => ['í', 'ì', 'ỉ', 'ĩ', 'ị', 'î', 'ï', 'ī', 'ĭ', 'į', 'ı', 'ι', 'ί', 'ϊ', 'ΐ', 'ἰ', 'ἱ', 'ἲ', 'ἳ', 'ἴ', 'ἵ', 'ἶ', 'ἷ', 'ὶ', 'ί', 'ῐ', 'ῑ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'і', 'ї', 'и', 'ဣ', 'ိ', 'ီ', 'ည်', 'ǐ', 'ი', 'इ', 'ی', 'i'], + 'j' => ['ĵ', 'ј', 'Ј', 'ჯ', 'ج', 'j'], + 'k' => ['ķ', 'ĸ', 'к', 'κ', 'Ķ', 'ق', 'ك', 'က', 'კ', 'ქ', 'ک', 'k'], + 'l' => ['ł', 'ľ', 'ĺ', 'ļ', 'ŀ', 'л', 'λ', 'ل', 'လ', 'ლ', 'l'], + 'm' => ['м', 'μ', 'م', 'မ', 'მ', 'm'], + 'n' => ['ñ', 'ń', 'ň', 'ņ', 'ʼn', 'ŋ', 'ν', 'н', 'ن', 'န', 'ნ', 'n'], + 'o' => ['ó', 'ò', 'ỏ', 'õ', 'ọ', 'ô', 'ố', 'ồ', 'ổ', 'ỗ', 'ộ', 'ơ', 'ớ', 'ờ', 'ở', 'ỡ', 'ợ', 'ø', 'ō', 'ő', 'ŏ', 'ο', 'ὀ', 'ὁ', 'ὂ', 'ὃ', 'ὄ', 'ὅ', 'ὸ', 'ό', 'о', 'و', 'θ', 'ို', 'ǒ', 'ǿ', 'º', 'ო', 'ओ', 'o', 'ö'], + 'p' => ['п', 'π', 'ပ', 'პ', 'پ', 'p'], + 'q' => ['ყ', 'q'], + 'r' => ['ŕ', 'ř', 'ŗ', 'р', 'ρ', 'ر', 'რ', 'r'], + 's' => ['ś', 'š', 'ş', 'с', 'σ', 'ș', 'ς', 'س', 'ص', 'စ', 'ſ', 'ს', 's'], + 't' => ['ť', 'ţ', 'т', 'τ', 'ț', 'ت', 'ط', 'ဋ', 'တ', 'ŧ', 'თ', 'ტ', 't'], + 'u' => ['ú', 'ù', 'ủ', 'ũ', 'ụ', 'ư', 'ứ', 'ừ', 'ử', 'ữ', 'ự', 'û', 'ū', 'ů', 'ű', 'ŭ', 'ų', 'µ', 'у', 'ဉ', 'ု', 'ူ', 'ǔ', 'ǖ', 'ǘ', 'ǚ', 'ǜ', 'უ', 'उ', 'u', 'ў', 'ü'], + 'v' => ['в', 'ვ', 'ϐ', 'v'], + 'w' => ['ŵ', 'ω', 'ώ', 'ဝ', 'ွ', 'w'], + 'x' => ['χ', 'ξ', 'x'], + 'y' => ['ý', 'ỳ', 'ỷ', 'ỹ', 'ỵ', 'ÿ', 'ŷ', 'й', 'ы', 'υ', 'ϋ', 'ύ', 'ΰ', 'ي', 'ယ', 'y'], + 'z' => ['ź', 'ž', 'ż', 'з', 'ζ', 'ز', 'ဇ', 'ზ', 'z'], + 'aa' => ['ع', 'आ', 'آ'], + 'ae' => ['æ', 'ǽ'], + 'ai' => ['ऐ'], + 'ch' => ['ч', 'ჩ', 'ჭ', 'چ'], + 'dj' => ['ђ', 'đ'], + 'dz' => ['џ', 'ძ'], + 'ei' => ['ऍ'], + 'gh' => ['غ', 'ღ'], + 'ii' => ['ई'], + 'ij' => ['ij'], + 'kh' => ['х', 'خ', 'ხ'], + 'lj' => ['љ'], + 'nj' => ['њ'], + 'oe' => ['ö', 'œ', 'ؤ'], + 'oi' => ['ऑ'], + 'oii' => ['ऒ'], + 'ps' => ['ψ'], + 'sh' => ['ш', 'შ', 'ش'], + 'shch' => ['щ'], + 'ss' => ['ß'], + 'sx' => ['ŝ'], + 'th' => ['þ', 'ϑ', 'ث', 'ذ', 'ظ'], + 'ts' => ['ц', 'ც', 'წ'], + 'ue' => ['ü'], + 'uu' => ['ऊ'], + 'ya' => ['я'], + 'yu' => ['ю'], + 'zh' => ['ж', 'ჟ', 'ژ'], + '(c)' => ['©'], + 'A' => ['Á', 'À', 'Ả', 'Ã', 'Ạ', 'Ă', 'Ắ', 'Ằ', 'Ẳ', 'Ẵ', 'Ặ', 'Â', 'Ấ', 'Ầ', 'Ẩ', 'Ẫ', 'Ậ', 'Å', 'Ā', 'Ą', 'Α', 'Ά', 'Ἀ', 'Ἁ', 'Ἂ', 'Ἃ', 'Ἄ', 'Ἅ', 'Ἆ', 'Ἇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'Ᾰ', 'Ᾱ', 'Ὰ', 'Ά', 'ᾼ', 'А', 'Ǻ', 'Ǎ', 'A', 'Ä'], + 'B' => ['Б', 'Β', 'ब', 'B'], + 'C' => ['Ç', 'Ć', 'Č', 'Ĉ', 'Ċ', 'C'], + 'D' => ['Ď', 'Ð', 'Đ', 'Ɖ', 'Ɗ', 'Ƌ', 'ᴅ', 'ᴆ', 'Д', 'Δ', 'D'], + 'E' => ['É', 'È', 'Ẻ', 'Ẽ', 'Ẹ', 'Ê', 'Ế', 'Ề', 'Ể', 'Ễ', 'Ệ', 'Ë', 'Ē', 'Ę', 'Ě', 'Ĕ', 'Ė', 'Ε', 'Έ', 'Ἐ', 'Ἑ', 'Ἒ', 'Ἓ', 'Ἔ', 'Ἕ', 'Έ', 'Ὲ', 'Е', 'Ё', 'Э', 'Є', 'Ə', 'E'], + 'F' => ['Ф', 'Φ', 'F'], + 'G' => ['Ğ', 'Ġ', 'Ģ', 'Г', 'Ґ', 'Γ', 'G'], + 'H' => ['Η', 'Ή', 'Ħ', 'H'], + 'I' => ['Í', 'Ì', 'Ỉ', 'Ĩ', 'Ị', 'Î', 'Ï', 'Ī', 'Ĭ', 'Į', 'İ', 'Ι', 'Ί', 'Ϊ', 'Ἰ', 'Ἱ', 'Ἳ', 'Ἴ', 'Ἵ', 'Ἶ', 'Ἷ', 'Ῐ', 'Ῑ', 'Ὶ', 'Ί', 'И', 'І', 'Ї', 'Ǐ', 'ϒ', 'I'], + 'J' => ['J'], + 'K' => ['К', 'Κ', 'K'], + 'L' => ['Ĺ', 'Ł', 'Л', 'Λ', 'Ļ', 'Ľ', 'Ŀ', 'ल', 'L'], + 'M' => ['М', 'Μ', 'M'], + 'N' => ['Ń', 'Ñ', 'Ň', 'Ņ', 'Ŋ', 'Н', 'Ν', 'N'], + 'O' => ['Ó', 'Ò', 'Ỏ', 'Õ', 'Ọ', 'Ô', 'Ố', 'Ồ', 'Ổ', 'Ỗ', 'Ộ', 'Ơ', 'Ớ', 'Ờ', 'Ở', 'Ỡ', 'Ợ', 'Ø', 'Ō', 'Ő', 'Ŏ', 'Ο', 'Ό', 'Ὀ', 'Ὁ', 'Ὂ', 'Ὃ', 'Ὄ', 'Ὅ', 'Ὸ', 'Ό', 'О', 'Θ', 'Ө', 'Ǒ', 'Ǿ', 'O', 'Ö'], + 'P' => ['П', 'Π', 'P'], + 'Q' => ['Q'], + 'R' => ['Ř', 'Ŕ', 'Р', 'Ρ', 'Ŗ', 'R'], + 'S' => ['Ş', 'Ŝ', 'Ș', 'Š', 'Ś', 'С', 'Σ', 'S'], + 'T' => ['Ť', 'Ţ', 'Ŧ', 'Ț', 'Т', 'Τ', 'T'], + 'U' => ['Ú', 'Ù', 'Ủ', 'Ũ', 'Ụ', 'Ư', 'Ứ', 'Ừ', 'Ử', 'Ữ', 'Ự', 'Û', 'Ū', 'Ů', 'Ű', 'Ŭ', 'Ų', 'У', 'Ǔ', 'Ǖ', 'Ǘ', 'Ǚ', 'Ǜ', 'U', 'Ў', 'Ü'], + 'V' => ['В', 'V'], + 'W' => ['Ω', 'Ώ', 'Ŵ', 'W'], + 'X' => ['Χ', 'Ξ', 'X'], + 'Y' => ['Ý', 'Ỳ', 'Ỷ', 'Ỹ', 'Ỵ', 'Ÿ', 'Ῠ', 'Ῡ', 'Ὺ', 'Ύ', 'Ы', 'Й', 'Υ', 'Ϋ', 'Ŷ', 'Y'], + 'Z' => ['Ź', 'Ž', 'Ż', 'З', 'Ζ', 'Z'], + 'AE' => ['Æ', 'Ǽ'], + 'Ch' => ['Ч'], + 'Dj' => ['Ђ'], + 'Dz' => ['Џ'], + 'Gx' => ['Ĝ'], + 'Hx' => ['Ĥ'], + 'Ij' => ['IJ'], + 'Jx' => ['Ĵ'], + 'Kh' => ['Х'], + 'Lj' => ['Љ'], + 'Nj' => ['Њ'], + 'Oe' => ['Œ'], + 'Ps' => ['Ψ'], + 'Sh' => ['Ш'], + 'Shch' => ['Щ'], + 'Ss' => ['ẞ'], + 'Th' => ['Þ'], + 'Ts' => ['Ц'], + 'Ya' => ['Я'], + 'Yu' => ['Ю'], + 'Zh' => ['Ж'], + ' ' => ["\xC2\xA0", "\xE2\x80\x80", "\xE2\x80\x81", "\xE2\x80\x82", "\xE2\x80\x83", "\xE2\x80\x84", "\xE2\x80\x85", "\xE2\x80\x86", "\xE2\x80\x87", "\xE2\x80\x88", "\xE2\x80\x89", "\xE2\x80\x8A", "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80", "\xEF\xBE\xA0"], + ]; + } + + /** + * @see https://github.com/danielstjules/Stringy/blob/3.1.0/LICENSE.txt + */ + protected static function languageSpecificCharsArray(string $language): ?array + { + static $languageSpecific; + if (!isset($languageSpecific)) { + $languageSpecific = [ + 'bg' => [ + ['х', 'Х', 'щ', 'Щ', 'ъ', 'Ъ', 'ь', 'Ь'], + ['h', 'H', 'sht', 'SHT', 'a', 'А', 'y', 'Y'], + ], + 'de' => [ + ['ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü'], + ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], + ], + ]; + } + + return $languageSpecific[$language] ?? null; + } +} diff --git a/vendor/yansongda/supports/src/Traits/Accessable.php b/vendor/yansongda/supports/src/Traits/Accessable.php new file mode 100644 index 0000000..5576dd1 --- /dev/null +++ b/vendor/yansongda/supports/src/Traits/Accessable.php @@ -0,0 +1,73 @@ +get($key); + } + + public function __isset(string $key): bool + { + return !is_null($this->get($key)); + } + + public function __unset(string $key): void + { + $this->offsetUnset($key); + } + + public function __set(string $key, mixed $value): void + { + $this->set($key, $value); + } + + public function get(?string $key = null, mixed $default = null): mixed + { + if (is_null($key)) { + return method_exists($this, 'toArray') ? $this->toArray() : $default; + } + + $method = 'get'.Str::studly($key); + + if (method_exists($this, $method)) { + return $this->{$method}(); + } + + return $default; + } + + public function set(string $key, mixed $value): self + { + $method = 'set'.Str::studly($key); + + if (method_exists($this, $method)) { + $this->{$method}($value); + } + + return $this; + } + + public function offsetExists(mixed $offset): bool + { + return !is_null($this->get($offset)); + } + + public function offsetGet(mixed $offset): mixed + { + return $this->get($offset); + } + + public function offsetSet(mixed $offset, mixed $value): void + { + $this->set($offset, $value); + } + + public function offsetUnset(mixed $offset): void {} +} diff --git a/vendor/yansongda/supports/src/Traits/Arrayable.php b/vendor/yansongda/supports/src/Traits/Arrayable.php new file mode 100644 index 0000000..0e0d3dd --- /dev/null +++ b/vendor/yansongda/supports/src/Traits/Arrayable.php @@ -0,0 +1,25 @@ +getProperties() as $item) { + $k = $item->getName(); + $method = 'get'.Str::studly($k); + + $result[Str::snake($k)] = method_exists($this, $method) ? $this->{$method}() : $this->{$k}; + } + + return $result; + } +} diff --git a/vendor/yansongda/supports/src/Traits/Serializable.php b/vendor/yansongda/supports/src/Traits/Serializable.php new file mode 100644 index 0000000..9735dcd --- /dev/null +++ b/vendor/yansongda/supports/src/Traits/Serializable.php @@ -0,0 +1,58 @@ +toArray(); + } + + return []; + } + + public function __unserialize(array $data): void + { + $this->unserializeArray($data); + } + + public function __toString(): string + { + return $this->toJson(); + } + + public function serialize(): ?string + { + return serialize($this); + } + + public function unserialize(string $data): void + { + unserialize($data); + } + + public function toJson(int $option = JSON_UNESCAPED_UNICODE): string + { + return json_encode($this->__serialize(), $option); + } + + public function jsonSerialize(): array + { + return $this->__serialize(); + } + + public function unserializeArray(array $data): self + { + foreach ($data as $key => $item) { + if (method_exists($this, 'set')) { + $this->set(strval($key), $item); + } + } + + return $this; + } +}