From 90bcbc60fd450460f29d969a0a91129652e329a2 Mon Sep 17 00:00:00 2001 From: jerome Date: Wed, 3 Jun 2026 10:04:42 +0200 Subject: [PATCH 01/11] add phpstan --- .distignore | 25 +- .gitignore | 5 +- composer.json | 5 +- phpstan.neon | 30 ++ stubs/woocommerce.php | 42 ++ vendor/autoload.php | 25 - vendor/composer/ClassLoader.php | 579 ------------------------ vendor/composer/LICENSE | 21 - vendor/composer/autoload_classmap.php | 10 - vendor/composer/autoload_namespaces.php | 9 - vendor/composer/autoload_psr4.php | 10 - vendor/composer/autoload_real.php | 36 -- vendor/composer/autoload_static.php | 36 -- 13 files changed, 96 insertions(+), 737 deletions(-) create mode 100644 phpstan.neon create mode 100644 stubs/woocommerce.php delete mode 100644 vendor/autoload.php delete mode 100644 vendor/composer/ClassLoader.php delete mode 100644 vendor/composer/LICENSE delete mode 100644 vendor/composer/autoload_classmap.php delete mode 100644 vendor/composer/autoload_namespaces.php delete mode 100644 vendor/composer/autoload_psr4.php delete mode 100644 vendor/composer/autoload_real.php delete mode 100644 vendor/composer/autoload_static.php diff --git a/.distignore b/.distignore index a88ffed..ca71997 100644 --- a/.distignore +++ b/.distignore @@ -2,12 +2,19 @@ /.github /.wordpress-org -.distignore -.gitignore -CODE-OF-CONDUCT.md -CONTRIBUTING.md -ISSUE_TEMPLATE.md -LICENSE -PULL_REQUEST_TEMPLATE.md -README.md -SECURITY.md \ No newline at end of file +/.phpstan +/stubs +/vendor +/phpstan.neon +/var +/composer.json +/composer.lock + +/.gitignore +/CODE-OF-CONDUCT.md +/CONTRIBUTING.md +/ISSUE_TEMPLATE.md +/LICENSE +/PULL_REQUEST_TEMPLATE.md +/README.md +/SECURITY.md \ No newline at end of file diff --git a/.gitignore b/.gitignore index ebf869b..0f378d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ .idea .vscode -.DS_Store \ No newline at end of file +.DS_Store +/vendor/ +/var +/composer.lock \ No newline at end of file diff --git a/composer.json b/composer.json index ac2de12..393db68 100644 --- a/composer.json +++ b/composer.json @@ -12,5 +12,8 @@ "name": "helloasso" } ], - "require": {} + "require-dev": { + "phpstan/phpstan": "^2.2", + "szepeviktor/phpstan-wordpress": "^2.0" + } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..789f510 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,30 @@ +includes: + - vendor/szepeviktor/phpstan-wordpress/extension.neon + +parameters: + level: 5 + scanFiles: + - stubs/woocommerce.php + paths: + - inc + - wc-api + - helper + - cron + + excludePaths: + analyse: + - vendor + + + + checkFunctionNameCase: true + checkExplicitMixed: true + tmpDir: var/phpstan + + universalObjectCratesClasses: + - WP_Post + - WP_User + - WC_Product + - WC_Order + - WC_Customer + - WC_Cart \ No newline at end of file diff --git a/stubs/woocommerce.php b/stubs/woocommerce.php new file mode 100644 index 0000000..a5bc55e --- /dev/null +++ b/stubs/woocommerce.php @@ -0,0 +1,42 @@ + */ + public $settings = []; + + /** @var string */ + public $id = ''; + + /** @var string */ + public $method_title = ''; + + /** @var string */ + public $method_description = ''; + + public function __construct() + { + } + + public function init_form_fields(): void + { + } + + public function init_settings(): void + { + } + + public function get_option($key, $empty_value = null) + { + return $empty_value; + } + + public function process_admin_options(): bool + { + return true; + } + } +} \ No newline at end of file diff --git a/vendor/autoload.php b/vendor/autoload.php deleted file mode 100644 index b2da1bb..0000000 --- a/vendor/autoload.php +++ /dev/null @@ -1,25 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Autoload; - -/** - * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. - * - * $loader = new \Composer\Autoload\ClassLoader(); - * - * // register classes with namespaces - * $loader->add('Symfony\Component', __DIR__.'/component'); - * $loader->add('Symfony', __DIR__.'/framework'); - * - * // activate the autoloader - * $loader->register(); - * - * // to enable searching the include path (eg. for PEAR packages) - * $loader->setUseIncludePath(true); - * - * In this example, if you try to use a class in the Symfony\Component - * namespace or one of its children (Symfony\Component\Console for instance), - * the autoloader will first look for the class under the component/ - * directory, and it will then fallback to the framework/ directory if not - * found before giving up. - * - * This class is loosely based on the Symfony UniversalClassLoader. - * - * @author Fabien Potencier - * @author Jordi Boggiano - * @see https://www.php-fig.org/psr/psr-0/ - * @see https://www.php-fig.org/psr/psr-4/ - */ -class ClassLoader -{ - /** @var \Closure(string):void */ - private static $includeFile; - - /** @var string|null */ - private $vendorDir; - - // PSR-4 - /** - * @var array> - */ - private $prefixLengthsPsr4 = array(); - /** - * @var array> - */ - private $prefixDirsPsr4 = array(); - /** - * @var list - */ - private $fallbackDirsPsr4 = array(); - - // PSR-0 - /** - * List of PSR-0 prefixes - * - * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) - * - * @var array>> - */ - private $prefixesPsr0 = array(); - /** - * @var list - */ - private $fallbackDirsPsr0 = array(); - - /** @var bool */ - private $useIncludePath = false; - - /** - * @var array - */ - private $classMap = array(); - - /** @var bool */ - private $classMapAuthoritative = false; - - /** - * @var array - */ - private $missingClasses = array(); - - /** @var string|null */ - private $apcuPrefix; - - /** - * @var array - */ - private static $registeredLoaders = array(); - - /** - * @param string|null $vendorDir - */ - public function __construct($vendorDir = null) - { - $this->vendorDir = $vendorDir; - self::initializeIncludeClosure(); - } - - /** - * @return array> - */ - public function getPrefixes() - { - if (!empty($this->prefixesPsr0)) { - return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); - } - - return array(); - } - - /** - * @return array> - */ - public function getPrefixesPsr4() - { - return $this->prefixDirsPsr4; - } - - /** - * @return list - */ - public function getFallbackDirs() - { - return $this->fallbackDirsPsr0; - } - - /** - * @return list - */ - public function getFallbackDirsPsr4() - { - return $this->fallbackDirsPsr4; - } - - /** - * @return array Array of classname => path - */ - public function getClassMap() - { - return $this->classMap; - } - - /** - * @param array $classMap Class to filename map - * - * @return void - */ - public function addClassMap(array $classMap) - { - if ($this->classMap) { - $this->classMap = array_merge($this->classMap, $classMap); - } else { - $this->classMap = $classMap; - } - } - - /** - * Registers a set of PSR-0 directories for a given prefix, either - * appending or prepending to the ones previously set for this prefix. - * - * @param string $prefix The prefix - * @param list|string $paths The PSR-0 root directories - * @param bool $prepend Whether to prepend the directories - * - * @return void - */ - public function add($prefix, $paths, $prepend = false) - { - $paths = (array) $paths; - if (!$prefix) { - if ($prepend) { - $this->fallbackDirsPsr0 = array_merge( - $paths, - $this->fallbackDirsPsr0 - ); - } else { - $this->fallbackDirsPsr0 = array_merge( - $this->fallbackDirsPsr0, - $paths - ); - } - - return; - } - - $first = $prefix[0]; - if (!isset($this->prefixesPsr0[$first][$prefix])) { - $this->prefixesPsr0[$first][$prefix] = $paths; - - return; - } - if ($prepend) { - $this->prefixesPsr0[$first][$prefix] = array_merge( - $paths, - $this->prefixesPsr0[$first][$prefix] - ); - } else { - $this->prefixesPsr0[$first][$prefix] = array_merge( - $this->prefixesPsr0[$first][$prefix], - $paths - ); - } - } - - /** - * Registers a set of PSR-4 directories for a given namespace, either - * appending or prepending to the ones previously set for this namespace. - * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param list|string $paths The PSR-4 base directories - * @param bool $prepend Whether to prepend the directories - * - * @throws \InvalidArgumentException - * - * @return void - */ - public function addPsr4($prefix, $paths, $prepend = false) - { - $paths = (array) $paths; - if (!$prefix) { - // Register directories for the root namespace. - if ($prepend) { - $this->fallbackDirsPsr4 = array_merge( - $paths, - $this->fallbackDirsPsr4 - ); - } else { - $this->fallbackDirsPsr4 = array_merge( - $this->fallbackDirsPsr4, - $paths - ); - } - } elseif (!isset($this->prefixDirsPsr4[$prefix])) { - // Register directories for a new namespace. - $length = strlen($prefix); - if ('\\' !== $prefix[$length - 1]) { - throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); - } - $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; - $this->prefixDirsPsr4[$prefix] = $paths; - } elseif ($prepend) { - // Prepend directories for an already registered namespace. - $this->prefixDirsPsr4[$prefix] = array_merge( - $paths, - $this->prefixDirsPsr4[$prefix] - ); - } else { - // Append directories for an already registered namespace. - $this->prefixDirsPsr4[$prefix] = array_merge( - $this->prefixDirsPsr4[$prefix], - $paths - ); - } - } - - /** - * Registers a set of PSR-0 directories for a given prefix, - * replacing any others previously set for this prefix. - * - * @param string $prefix The prefix - * @param list|string $paths The PSR-0 base directories - * - * @return void - */ - public function set($prefix, $paths) - { - if (!$prefix) { - $this->fallbackDirsPsr0 = (array) $paths; - } else { - $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; - } - } - - /** - * Registers a set of PSR-4 directories for a given namespace, - * replacing any others previously set for this namespace. - * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param list|string $paths The PSR-4 base directories - * - * @throws \InvalidArgumentException - * - * @return void - */ - public function setPsr4($prefix, $paths) - { - if (!$prefix) { - $this->fallbackDirsPsr4 = (array) $paths; - } else { - $length = strlen($prefix); - if ('\\' !== $prefix[$length - 1]) { - throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); - } - $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; - $this->prefixDirsPsr4[$prefix] = (array) $paths; - } - } - - /** - * Turns on searching the include path for class files. - * - * @param bool $useIncludePath - * - * @return void - */ - public function setUseIncludePath($useIncludePath) - { - $this->useIncludePath = $useIncludePath; - } - - /** - * Can be used to check if the autoloader uses the include path to check - * for classes. - * - * @return bool - */ - public function getUseIncludePath() - { - return $this->useIncludePath; - } - - /** - * Turns off searching the prefix and fallback directories for classes - * that have not been registered with the class map. - * - * @param bool $classMapAuthoritative - * - * @return void - */ - public function setClassMapAuthoritative($classMapAuthoritative) - { - $this->classMapAuthoritative = $classMapAuthoritative; - } - - /** - * Should class lookup fail if not found in the current class map? - * - * @return bool - */ - public function isClassMapAuthoritative() - { - return $this->classMapAuthoritative; - } - - /** - * APCu prefix to use to cache found/not-found classes, if the extension is enabled. - * - * @param string|null $apcuPrefix - * - * @return void - */ - public function setApcuPrefix($apcuPrefix) - { - $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; - } - - /** - * The APCu prefix in use, or null if APCu caching is not enabled. - * - * @return string|null - */ - public function getApcuPrefix() - { - return $this->apcuPrefix; - } - - /** - * Registers this instance as an autoloader. - * - * @param bool $prepend Whether to prepend the autoloader or not - * - * @return void - */ - public function register($prepend = false) - { - spl_autoload_register(array($this, 'loadClass'), true, $prepend); - - if (null === $this->vendorDir) { - return; - } - - if ($prepend) { - self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; - } else { - unset(self::$registeredLoaders[$this->vendorDir]); - self::$registeredLoaders[$this->vendorDir] = $this; - } - } - - /** - * Unregisters this instance as an autoloader. - * - * @return void - */ - public function unregister() - { - spl_autoload_unregister(array($this, 'loadClass')); - - if (null !== $this->vendorDir) { - unset(self::$registeredLoaders[$this->vendorDir]); - } - } - - /** - * Loads the given class or interface. - * - * @param string $class The name of the class - * @return true|null True if loaded, null otherwise - */ - public function loadClass($class) - { - if ($file = $this->findFile($class)) { - $includeFile = self::$includeFile; - $includeFile($file); - - return true; - } - - return null; - } - - /** - * Finds the path to the file where the class is defined. - * - * @param string $class The name of the class - * - * @return string|false The path if found, false otherwise - */ - public function findFile($class) - { - // class map lookup - if (isset($this->classMap[$class])) { - return $this->classMap[$class]; - } - if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { - return false; - } - if (null !== $this->apcuPrefix) { - $file = apcu_fetch($this->apcuPrefix.$class, $hit); - if ($hit) { - return $file; - } - } - - $file = $this->findFileWithExtension($class, '.php'); - - // Search for Hack files if we are running on HHVM - if (false === $file && defined('HHVM_VERSION')) { - $file = $this->findFileWithExtension($class, '.hh'); - } - - if (null !== $this->apcuPrefix) { - apcu_add($this->apcuPrefix.$class, $file); - } - - if (false === $file) { - // Remember that this class does not exist. - $this->missingClasses[$class] = true; - } - - return $file; - } - - /** - * Returns the currently registered loaders keyed by their corresponding vendor directories. - * - * @return array - */ - public static function getRegisteredLoaders() - { - return self::$registeredLoaders; - } - - /** - * @param string $class - * @param string $ext - * @return string|false - */ - private function findFileWithExtension($class, $ext) - { - // PSR-4 lookup - $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; - - $first = $class[0]; - if (isset($this->prefixLengthsPsr4[$first])) { - $subPath = $class; - while (false !== $lastPos = strrpos($subPath, '\\')) { - $subPath = substr($subPath, 0, $lastPos); - $search = $subPath . '\\'; - if (isset($this->prefixDirsPsr4[$search])) { - $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); - foreach ($this->prefixDirsPsr4[$search] as $dir) { - if (file_exists($file = $dir . $pathEnd)) { - return $file; - } - } - } - } - } - - // PSR-4 fallback dirs - foreach ($this->fallbackDirsPsr4 as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { - return $file; - } - } - - // PSR-0 lookup - if (false !== $pos = strrpos($class, '\\')) { - // namespaced class name - $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) - . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); - } else { - // PEAR-like class name - $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; - } - - if (isset($this->prefixesPsr0[$first])) { - foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { - if (0 === strpos($class, $prefix)) { - foreach ($dirs as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { - return $file; - } - } - } - } - } - - // PSR-0 fallback dirs - foreach ($this->fallbackDirsPsr0 as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { - return $file; - } - } - - // PSR-0 include paths. - if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { - return $file; - } - - return false; - } - - /** - * @return void - */ - private static function initializeIncludeClosure() - { - if (self::$includeFile !== null) { - return; - } - - /** - * Scope isolated include. - * - * Prevents access to $this/self from included files. - * - * @param string $file - * @return void - */ - self::$includeFile = \Closure::bind(static function($file) { - include $file; - }, null, null); - } -} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE deleted file mode 100644 index f27399a..0000000 --- a/vendor/composer/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - -Copyright (c) Nils Adermann, Jordi Boggiano - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php deleted file mode 100644 index 0fb0a2c..0000000 --- a/vendor/composer/autoload_classmap.php +++ /dev/null @@ -1,10 +0,0 @@ - $vendorDir . '/composer/InstalledVersions.php', -); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php deleted file mode 100644 index 15a2ff3..0000000 --- a/vendor/composer/autoload_namespaces.php +++ /dev/null @@ -1,9 +0,0 @@ - array($baseDir . '/inc'), -); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php deleted file mode 100644 index 6fac184..0000000 --- a/vendor/composer/autoload_real.php +++ /dev/null @@ -1,36 +0,0 @@ -register(true); - - return $loader; - } -} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php deleted file mode 100644 index 350c6f9..0000000 --- a/vendor/composer/autoload_static.php +++ /dev/null @@ -1,36 +0,0 @@ - - array ( - 'Helloasso\\HelloassoPaymentsForWoocommerce\\' => 42, - ), - ); - - public static $prefixDirsPsr4 = array ( - 'Helloasso\\HelloassoPaymentsForWoocommerce\\' => - array ( - 0 => __DIR__ . '/../..' . '/inc', - ), - ); - - public static $classMap = array ( - 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', - ); - - public static function getInitializer(ClassLoader $loader) - { - return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInit8acb959cdef6e08c0a631c2e2581223e::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit8acb959cdef6e08c0a631c2e2581223e::$prefixDirsPsr4; - $loader->classMap = ComposerStaticInit8acb959cdef6e08c0a631c2e2581223e::$classMap; - - }, null, ClassLoader::class); - } -} From e131d9d997be9bf9e60a2e7e256c83632e243d81 Mon Sep 17 00:00:00 2001 From: jerome Date: Wed, 3 Jun 2026 10:10:33 +0200 Subject: [PATCH 02/11] ignore autoloading --- .gitignore | 5 +- cron/helloasso-woocommerce-cron.php | 2 - helloasso-api/helloasso-woocommerce-api.php | 2 - helloasso-woocommerce-gateway.php | 3 +- vendor/autoload.php | 25 + vendor/composer/ClassLoader.php | 579 ++++++++++++++++++++ vendor/composer/InstalledVersions.php | 359 ++++++++++++ vendor/composer/LICENSE | 21 + vendor/composer/autoload_classmap.php | 10 + vendor/composer/autoload_files.php | 10 + vendor/composer/autoload_namespaces.php | 9 + vendor/composer/autoload_psr4.php | 11 + vendor/composer/autoload_real.php | 48 ++ vendor/composer/autoload_static.php | 48 ++ vendor/composer/installed.json | 198 +++++++ vendor/composer/installed.php | 50 ++ 16 files changed, 1374 insertions(+), 6 deletions(-) create mode 100644 vendor/autoload.php create mode 100644 vendor/composer/ClassLoader.php create mode 100644 vendor/composer/InstalledVersions.php create mode 100644 vendor/composer/LICENSE create mode 100644 vendor/composer/autoload_classmap.php create mode 100644 vendor/composer/autoload_files.php create mode 100644 vendor/composer/autoload_namespaces.php create mode 100644 vendor/composer/autoload_psr4.php create mode 100644 vendor/composer/autoload_real.php create mode 100644 vendor/composer/autoload_static.php create mode 100644 vendor/composer/installed.json create mode 100644 vendor/composer/installed.php diff --git a/.gitignore b/.gitignore index 0f378d0..733156c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ .idea .vscode .DS_Store -/vendor/ +/vendor/* +!/vendor/composer +!/vendor/autoload.php + /var /composer.lock \ No newline at end of file diff --git a/cron/helloasso-woocommerce-cron.php b/cron/helloasso-woocommerce-cron.php index bf1c6a8..3ba58d9 100644 --- a/cron/helloasso-woocommerce-cron.php +++ b/cron/helloasso-woocommerce-cron.php @@ -1,7 +1,5 @@ + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..51e734a --- /dev/null +++ b/vendor/composer/InstalledVersions.php @@ -0,0 +1,359 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + $installed[] = self::$installedByVendor[$vendorDir] = $required; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..0fb0a2c --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,10 @@ + $vendorDir . '/composer/InstalledVersions.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 0000000..b62e293 --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,10 @@ + $vendorDir . '/phpstan/phpstan/bootstrap.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..15a2ff3 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/szepeviktor/phpstan-wordpress/src'), + 'Helloasso\\HelloassoPaymentsForWoocommerce\\' => array($baseDir . '/inc'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..6dacb15 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,48 @@ +register(true); + + $filesToLoad = \Composer\Autoload\ComposerStaticInit8acb959cdef6e08c0a631c2e2581223e::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); + } + + return $loader; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..993ae7f --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,48 @@ + __DIR__ . '/..' . '/phpstan/phpstan/bootstrap.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'S' => + array ( + 'SzepeViktor\\PHPStan\\WordPress\\' => 30, + ), + 'H' => + array ( + 'Helloasso\\HelloassoPaymentsForWoocommerce\\' => 42, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'SzepeViktor\\PHPStan\\WordPress\\' => + array ( + 0 => __DIR__ . '/..' . '/szepeviktor/phpstan-wordpress/src', + ), + 'Helloasso\\HelloassoPaymentsForWoocommerce\\' => + array ( + 0 => __DIR__ . '/../..' . '/inc', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit8acb959cdef6e08c0a631c2e2581223e::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit8acb959cdef6e08c0a631c2e2581223e::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit8acb959cdef6e08c0a631c2e2581223e::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..7b73e81 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,198 @@ +{ + "packages": [ + { + "name": "php-stubs/wordpress-stubs", + "version": "v6.9.4", + "version_normalized": "6.9.4.0", + "source": { + "type": "git", + "url": "https://github.com/php-stubs/wordpress-stubs.git", + "reference": "90a9412826b9944f93b10bf41d795b5fe68abcd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/90a9412826b9944f93b10bf41d795b5fe68abcd5", + "reference": "90a9412826b9944f93b10bf41d795b5fe68abcd5", + "shasum": "" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "5.6.1" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "nikic/php-parser": "^5.5", + "php": "^7.4 || ^8.0", + "php-stubs/generator": "^0.8.6", + "phpdocumentor/reflection-docblock": "^6.0", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^9.5", + "symfony/polyfill-php80": "*", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.1.1", + "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" + }, + "suggest": { + "paragonie/sodium_compat": "Pure PHP implementation of libsodium", + "symfony/polyfill-php80": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" + }, + "time": "2026-05-01T20:36:01+00:00", + "type": "library", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WordPress function and class declaration stubs for static analysis.", + "homepage": "https://github.com/php-stubs/wordpress-stubs", + "keywords": [ + "PHPStan", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/php-stubs/wordpress-stubs/issues", + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.9.4" + }, + "install-path": "../php-stubs/wordpress-stubs" + }, + { + "name": "phpstan/phpstan", + "version": "2.2.1", + "version_normalized": "2.2.1.0", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dea9c8f2d25cc849391042b71e429c1a4bf82660", + "reference": "dea9c8f2d25cc849391042b71e429c1a4bf82660", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "time": "2026-05-28T14:44:12+00:00", + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "install-path": "../phpstan/phpstan" + }, + { + "name": "szepeviktor/phpstan-wordpress", + "version": "v2.0.3", + "version_normalized": "2.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/szepeviktor/phpstan-wordpress.git", + "reference": "aa722f037b2d034828cd6c55ebe9e5c74961927e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/szepeviktor/phpstan-wordpress/zipball/aa722f037b2d034828cd6c55ebe9e5c74961927e", + "reference": "aa722f037b2d034828cd6c55ebe9e5c74961927e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "php-stubs/wordpress-stubs": "^6.6.2", + "phpstan/phpstan": "^2.0" + }, + "require-dev": { + "composer/composer": "^2.1.14", + "composer/semver": "^3.4", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.0", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.0", + "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" + }, + "suggest": { + "swissspidy/phpstan-no-private": "Detect usage of internal core functions, classes and methods" + }, + "time": "2025-09-14T02:58:22+00:00", + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "SzepeViktor\\PHPStan\\WordPress\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "WordPress extensions for PHPStan", + "keywords": [ + "PHPStan", + "code analyse", + "code analysis", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/szepeviktor/phpstan-wordpress/issues", + "source": "https://github.com/szepeviktor/phpstan-wordpress/tree/v2.0.3" + }, + "install-path": "../szepeviktor/phpstan-wordpress" + } + ], + "dev": true, + "dev-package-names": [ + "php-stubs/wordpress-stubs", + "phpstan/phpstan", + "szepeviktor/phpstan-wordpress" + ] +} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php new file mode 100644 index 0000000..245533e --- /dev/null +++ b/vendor/composer/installed.php @@ -0,0 +1,50 @@ + array( + 'name' => 'helloasso/helloasso-payments-for-woocommerce', + 'pretty_version' => 'dev-main', + 'version' => 'dev-main', + 'reference' => '69ced7a552ef32c9f8c21e7eecaf2202de4ab789', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + 'helloasso/helloasso-payments-for-woocommerce' => array( + 'pretty_version' => 'dev-main', + 'version' => 'dev-main', + 'reference' => '69ced7a552ef32c9f8c21e7eecaf2202de4ab789', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'php-stubs/wordpress-stubs' => array( + 'pretty_version' => 'v6.9.4', + 'version' => '6.9.4.0', + 'reference' => '90a9412826b9944f93b10bf41d795b5fe68abcd5', + 'type' => 'library', + 'install_path' => __DIR__ . '/../php-stubs/wordpress-stubs', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'phpstan/phpstan' => array( + 'pretty_version' => '2.2.1', + 'version' => '2.2.1.0', + 'reference' => 'dea9c8f2d25cc849391042b71e429c1a4bf82660', + 'type' => 'library', + 'install_path' => __DIR__ . '/../phpstan/phpstan', + 'aliases' => array(), + 'dev_requirement' => true, + ), + 'szepeviktor/phpstan-wordpress' => array( + 'pretty_version' => 'v2.0.3', + 'version' => '2.0.3.0', + 'reference' => 'aa722f037b2d034828cd6c55ebe9e5c74961927e', + 'type' => 'phpstan-extension', + 'install_path' => __DIR__ . '/../szepeviktor/phpstan-wordpress', + 'aliases' => array(), + 'dev_requirement' => true, + ), + ), +); From 04794abb00cba89bbf29fe61d35a14164475412e Mon Sep 17 00:00:00 2001 From: jerome Date: Wed, 3 Jun 2026 10:12:37 +0200 Subject: [PATCH 03/11] define constant HELLOASSO_REFRESH_TOKEN_LIFETIME globally --- wc-api/helloasso-woocommerce-wc-api.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wc-api/helloasso-woocommerce-wc-api.php b/wc-api/helloasso-woocommerce-wc-api.php index 19d8955..1fdcaec 100644 --- a/wc-api/helloasso-woocommerce-wc-api.php +++ b/wc-api/helloasso-woocommerce-wc-api.php @@ -3,8 +3,7 @@ exit; //Exit if accessed directly } -// Durée de validité du refresh token : 30 jours en secondes -define('HELLOASSO_REFRESH_TOKEN_LIFETIME', 30 * 24 * 60 * 60); // 2592000 secondes + add_action('woocommerce_api_helloasso', 'helloasso_endpoint'); function helloasso_endpoint() From 86960b50d5aebd19c7daa8a31d3015e880d9d076 Mon Sep 17 00:00:00 2001 From: jerome Date: Wed, 3 Jun 2026 11:03:35 +0200 Subject: [PATCH 04/11] Update disconnected message --- block/helloasso-woocommerce-blocks.php | 6 ++-- cron/helloasso-woocommerce-cron.php | 4 +-- helloasso-woocommerce-gateway.php | 2 +- inc/Gateway/WC_HelloAsso_Gateway.php | 17 ++++++--- phpstan.neon | 5 +++ stubs/woocommerce-blocks-integrations.php | 43 +++++++++++++++++++++++ stubs/woocommerce-blocks.php | 14 ++++++++ stubs/woocommerce-payment-gateways.php | 21 +++++++++++ stubs/woocommerce.php | 10 ++++++ 9 files changed, 112 insertions(+), 10 deletions(-) create mode 100644 stubs/woocommerce-blocks-integrations.php create mode 100644 stubs/woocommerce-blocks.php create mode 100644 stubs/woocommerce-payment-gateways.php diff --git a/block/helloasso-woocommerce-blocks.php b/block/helloasso-woocommerce-blocks.php index ebf74bc..ebf61b4 100644 --- a/block/helloasso-woocommerce-blocks.php +++ b/block/helloasso-woocommerce-blocks.php @@ -1,4 +1,6 @@ settings = get_option('woocommerce_helloasso_settings', []); - $this->gateway =WC_Payment_Gateways::instance()->payment_gateways()[$this->name]; + $this->gateway = \WC_Payment_Gateways::instance()->payment_gateways()[$this->name]; } diff --git a/cron/helloasso-woocommerce-cron.php b/cron/helloasso-woocommerce-cron.php index 3ba58d9..463f620 100644 --- a/cron/helloasso-woocommerce-cron.php +++ b/cron/helloasso-woocommerce-cron.php @@ -43,9 +43,9 @@ function helloasso_refresh_token_asso() } $response_body = wp_remote_retrieve_body($response); - $data = json_decode($response_body); + $data = json_decode($response_body, false); - if (isset($data->access_token)) { + if (is_object($data) && isset($data->access_token)) { update_option('helloasso_access_token_asso', $data->access_token); update_option('helloasso_refresh_token_asso', $data->refresh_token); update_option('helloasso_token_expires_in_asso', time() + $data->expires_in); diff --git a/helloasso-woocommerce-gateway.php b/helloasso-woocommerce-gateway.php index 8090037..7ad8f26 100644 --- a/helloasso-woocommerce-gateway.php +++ b/helloasso-woocommerce-gateway.php @@ -28,6 +28,7 @@ use Automattic\WooCommerce\Utilities\FeaturesUtil; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; use Helloasso\HelloassoPaymentsForWoocommerce\Gateway\WC_HelloAsso_Gateway; +use Helloasso\HelloassoPaymentsForWoocommerce\Block\Helloasso_Blocks; require_once('helper/helloasso-woocommerce-api-call.php'); require_once('helper/helloasso-woocommerce-config.php'); @@ -125,5 +126,4 @@ function helloasso_deactivate() delete_option('helloasso_webhook_data'); } -add_action('wp_ajax_helloasso_deco', 'helloasso_deco'); diff --git a/inc/Gateway/WC_HelloAsso_Gateway.php b/inc/Gateway/WC_HelloAsso_Gateway.php index 02e48de..20c7fa0 100644 --- a/inc/Gateway/WC_HelloAsso_Gateway.php +++ b/inc/Gateway/WC_HelloAsso_Gateway.php @@ -111,10 +111,15 @@ public function admin_options() '; } if(!$this->isConnected) { - echo '
-

La connexion à HelloAsso est incomplète. Veuillez réessayer.

-
'; - + if(isset($_GET['deconnect']) && 'success' === $_GET['deconnect']) { + echo '
+

Déconnexion de HelloAsso réussie.

+
'; + } else { + echo '
+

La connexion à HelloAsso est incomplète. Veuillez réessayer.

+
'; + } } } @@ -295,7 +300,9 @@ class="HaAuthorizeButtonLogo"> console.log(data); var data = JSON.parse(data); if (data.success) { - location.reload(); + const url = new URL(window.location.href); + url.searchParams.set("deconnect", "success"); + window.location.replace(url.toString()); } else { alert(data.message); } diff --git a/phpstan.neon b/phpstan.neon index 789f510..3f93b04 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -5,11 +5,16 @@ parameters: level: 5 scanFiles: - stubs/woocommerce.php + - stubs/woocommerce-payment-gateways.php + - stubs/woocommerce-blocks.php + - stubs/woocommerce-blocks-integrations.php paths: - inc - wc-api - helper + - block - cron + - helloasso-woocommerce-gateway.php excludePaths: analyse: diff --git a/stubs/woocommerce-blocks-integrations.php b/stubs/woocommerce-blocks-integrations.php new file mode 100644 index 0000000..2fa29d6 --- /dev/null +++ b/stubs/woocommerce-blocks-integrations.php @@ -0,0 +1,43 @@ + */ + protected array $settings = []; + + /** @var array */ + protected array $data = []; + + protected $script_version = null; + protected $asset_api = null; + + + + public function is_active() + { + return true; + } + + public function get_payment_method_script_handles() + { + return []; + } + + public function get_payment_method_data() + { + return []; + } + + public function get_name(): string + { + return $this->name; + } + } +} \ No newline at end of file diff --git a/stubs/woocommerce-blocks.php b/stubs/woocommerce-blocks.php new file mode 100644 index 0000000..79b6a67 --- /dev/null +++ b/stubs/woocommerce-blocks.php @@ -0,0 +1,14 @@ + + */ + public function payment_gateways(): array + { + return []; + } + } +} \ No newline at end of file diff --git a/stubs/woocommerce.php b/stubs/woocommerce.php index a5bc55e..80eabca 100644 --- a/stubs/woocommerce.php +++ b/stubs/woocommerce.php @@ -29,6 +29,9 @@ public function init_settings(): void { } + /** + * @return mixed + */ public function get_option($key, $empty_value = null) { return $empty_value; @@ -39,4 +42,11 @@ public function process_admin_options(): bool return true; } } +} + +if (!function_exists('get_woocommerce_currency')) { + function get_woocommerce_currency(): string + { + return 'EUR'; + } } \ No newline at end of file From 1ce74e203870153b6f1caeb364efb8deccf68cf1 Mon Sep 17 00:00:00 2001 From: jerome Date: Wed, 3 Jun 2026 12:17:46 +0200 Subject: [PATCH 05/11] fix phpstan typing && errors compat for php 8->8.5 --- changelog.txt | 6 + helloasso-api/helloasso-woocommerce-api.php | 41 +++-- helper/helloasso-woocommerce-api-call.php | 20 ++- inc/Gateway/WC_HelloAsso_Gateway.php | 139 ++++++++++++---- phpstan.neon | 3 + stubs/woocommerce-payment-gateway.php | 168 ++++++++++++++++++++ stubs/woocommerce-payment-gateways.php | 2 +- stubs/woocommerce.php | 66 +++----- wc-api/helloasso-woocommerce-wc-api.php | 78 +++++++-- 9 files changed, 414 insertions(+), 109 deletions(-) create mode 100644 stubs/woocommerce-payment-gateway.php diff --git a/changelog.txt b/changelog.txt index e3ae38c..77bf359 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,10 @@ *** HelloAsso Payments for WooCommerce Changelog *** +2026-06-03 - version 1.1.2 +* Update logout message on admin panel +* Add phpstan to ensuire compatibility with wordpress and woocommerce + +2026-04-23 - version 1.1.1 +* Rework api calls 2024-02-28 - version 1.0.0 * Initial release diff --git a/helloasso-api/helloasso-woocommerce-api.php b/helloasso-api/helloasso-woocommerce-api.php index 0310bc5..93d6a2d 100644 --- a/helloasso-api/helloasso-woocommerce-api.php +++ b/helloasso-api/helloasso-woocommerce-api.php @@ -1,9 +1,9 @@ substr($access_token, 0, 10) . '...', 'expires_in' => $token_expires_in - time() @@ -24,7 +27,7 @@ function helloasso_get_oauth_token($client_id, $client_secret, $api_url) return $access_token; } - if ($refresh_token && time() < $refresh_token_expires_in) { + if (is_string($refresh_token) && time() < $refresh_token_expires_in) { helloasso_log_info('Rafraîchissement du token OAuth2', array( 'refresh_token_preview' => substr($refresh_token, 0, 10) . '...' )); @@ -50,6 +53,8 @@ function helloasso_get_oauth_token($client_id, $client_secret, $api_url) $response_body = wp_remote_retrieve_body($response); $response_code = wp_remote_retrieve_response_code($response); + + /** @var object{access_token?: string, refresh_token?: string, expires_in?: int|string} $data */ $data = json_decode($response_body); helloasso_log_info('Réponse rafraîchissement token OAuth2', array( @@ -57,15 +62,19 @@ function helloasso_get_oauth_token($client_id, $client_secret, $api_url) 'has_access_token' => isset($data->access_token) )); - if (isset($data->access_token)) { + if (isset($data->access_token) && is_string($data->access_token)) { update_option('helloasso_access_token', $data->access_token); - update_option('helloasso_refresh_token', $data->refresh_token); - update_option('helloasso_token_expires_in', time() + $data->expires_in); + if (isset($data->refresh_token) && is_string($data->refresh_token)) { + update_option('helloasso_refresh_token', $data->refresh_token); + } + if (isset($data->expires_in) && is_numeric($data->expires_in)) { + update_option('helloasso_token_expires_in', time() + (int) $data->expires_in); + } update_option('helloasso_refresh_token_expires_in', time() + HELLOASSO_REFRESH_TOKEN_LIFETIME); helloasso_log_info('Token OAuth2 rafraîchi avec succès', array( 'new_token_preview' => substr($data->access_token, 0, 10) . '...', - 'expires_in' => $data->expires_in + 'expires_in' => isset($data->expires_in) && is_numeric($data->expires_in) ? (int) $data->expires_in : null )); return $data->access_token; @@ -103,6 +112,8 @@ function helloasso_get_oauth_token($client_id, $client_secret, $api_url) $response_body = wp_remote_retrieve_body($response); $response_code = wp_remote_retrieve_response_code($response); + + /** @var object{access_token?: string, refresh_token?: string, expires_in?: int|string} $data */ $data = json_decode($response_body); helloasso_log_info('Réponse demande token OAuth2', array( @@ -110,15 +121,19 @@ function helloasso_get_oauth_token($client_id, $client_secret, $api_url) 'has_access_token' => isset($data->access_token) )); - if (isset($data->access_token)) { + if (isset($data->access_token) && is_string($data->access_token)) { add_option('helloasso_access_token', $data->access_token); - add_option('helloasso_refresh_token', $data->refresh_token); - add_option('helloasso_token_expires_in', time() + $data->expires_in); + if (isset($data->refresh_token) && is_string($data->refresh_token)) { + add_option('helloasso_refresh_token', $data->refresh_token); + } + if (isset($data->expires_in) && is_numeric($data->expires_in)) { + add_option('helloasso_token_expires_in', time() + (int) $data->expires_in); + } add_option('helloasso_refresh_token_expires_in', time() + HELLOASSO_REFRESH_TOKEN_LIFETIME); helloasso_log_info('Nouveau token OAuth2 obtenu avec succès', array( 'token_preview' => substr($data->access_token, 0, 10) . '...', - 'expires_in' => $data->expires_in + 'expires_in' => isset($data->expires_in) && is_numeric($data->expires_in) ? (int) $data->expires_in : null )); return $data->access_token; @@ -133,4 +148,4 @@ function helloasso_get_oauth_token($client_id, $client_secret, $api_url) helloasso_log_warning('Aucun token OAuth2 disponible et aucun refresh token valide'); return null; -} +} \ No newline at end of file diff --git a/helper/helloasso-woocommerce-api-call.php b/helper/helloasso-woocommerce-api-call.php index 6ae9d3f..d17313e 100644 --- a/helper/helloasso-woocommerce-api-call.php +++ b/helper/helloasso-woocommerce-api-call.php @@ -2,8 +2,18 @@ if (! defined('ABSPATH')) { exit; //Exit if accessed directly } + +function helloasso_get_user_agent(): string +{ + $woocommerce_db_version = get_option('woocommerce_db_version'); + $woocommerce_db_version = is_string($woocommerce_db_version) ? $woocommerce_db_version : ''; + + return 'PHP ' . PHP_VERSION . '/WooCommerce ' . $woocommerce_db_version; +} + function helloasso_get_args_post_urlencode($data) { + helloasso_log_debug('Préparation requête POST URL-encoded', array( 'data_keys' => array_keys($data), 'data_count' => count($data) @@ -20,7 +30,7 @@ function helloasso_get_args_post_urlencode($data) ), 'body' => http_build_query($data), 'cookies' => array(), - 'user-agent' => 'PHP ' . PHP_VERSION . '/WooCommerce ' . get_option('woocommerce_db_version'), + 'user-agent' => helloasso_get_user_agent() ); return $args; @@ -44,7 +54,7 @@ function helloasso_get_args_post($data) ), 'body' => $data, 'cookies' => array(), - 'user-agent' => 'PHP ' . PHP_VERSION . '/WooCommerce ' . get_option('woocommerce_db_version'), + 'user-agent' => helloasso_get_user_agent(), ); return $args; @@ -71,7 +81,7 @@ function helloasso_get_args_post_token($data, $token) ), 'body' => wp_json_encode($data), 'cookies' => array(), - 'user-agent' => 'PHP ' . PHP_VERSION . '/WooCommerce ' . get_option('woocommerce_db_version'), + 'user-agent' => helloasso_get_user_agent(), ); return $args; @@ -97,7 +107,7 @@ function helloasso_get_args_put_token($data, $token) ), 'body' => wp_json_encode($data), 'cookies' => array(), - 'user-agent' => 'PHP ' . PHP_VERSION . '/WooCommerce ' . get_option('woocommerce_db_version'), + 'user-agent' => helloasso_get_user_agent(), ); return $args; @@ -120,7 +130,7 @@ function helloasso_get_args_get_token($token) 'Authorization' => 'Bearer ' . $token, ), 'cookies' => array(), - 'user-agent' => 'PHP ' . PHP_VERSION . '/WooCommerce ' . get_option('woocommerce_db_version'), + 'user-agent' => helloasso_get_user_agent(), ); return $args; diff --git a/inc/Gateway/WC_HelloAsso_Gateway.php b/inc/Gateway/WC_HelloAsso_Gateway.php index 20c7fa0..794696b 100644 --- a/inc/Gateway/WC_HelloAsso_Gateway.php +++ b/inc/Gateway/WC_HelloAsso_Gateway.php @@ -10,6 +10,41 @@ class WC_HelloAsso_Gateway extends \WC_Payment_Gateway */ public $isConnected; + /** + * @var string + */ + public $icon = ''; + /** + * @var string + */ + public $title = ''; + /** + * @var string + */ + public $description = ''; + + /** + * @var string + */ + public $method_description = ''; + + /** + * @var string + */ + public $enabled = ''; + + /** + * @var bool + */ + public $has_fields = false; + + /** @var string[] */ + public $supports = []; + + /** @var array */ + + public $form_fields = []; + public function __construct() { @@ -20,7 +55,7 @@ public function __construct() )); $this->id = 'helloasso'; - $this->icon = null; + $this->icon = ''; $this->has_fields = true; $this->method_title = 'Payer par carte bancaire avec HelloAsso'; $this->method_description = 'Acceptez des paiements gratuitement avec HelloAsso (0 frais, 0 commission pour votre association).'; @@ -35,7 +70,7 @@ public function __construct() $this->title = $this->get_option('title'); $this->description = 'Le modèle solidaire de HelloAsso garantit que 100% de votre paiement sera versé à l’association choisie. Vous pouvez soutenir l’aide qu’ils apportent aux associations en laissant une contribution volontaire à HelloAsso au moment de votre paiement.'; $this->enabled = $this->get_option('enabled'); - add_action('woocommerce_update_options_payment_gateways_' . $this->id, array($this, 'process_admin_options')); + add_action('woocommerce_update_options_payment_gateways_' . $this->id, array($this, 'save_admin_options')); } public function payment_fields() @@ -80,6 +115,10 @@ public function payment_fields() } } + public function save_admin_options(): void + { + $this->process_admin_options(); + } public function admin_options() { // Check if we have helloasso_access_token_asso in the options @@ -92,7 +131,7 @@ public function admin_options() if (isset($_GET['msg'])) { $msg = sanitize_text_field($_GET['msg']); - if (isset($msg) && 'error_connect' === $msg) { + if ( 'error_connect' === $msg) { if (isset($_GET['status_code']) && '403' === $_GET['status_code']) { echo '

Erreur lors de la connexion à HelloAsso. Veuillez nous contacter. (Erreur 403)

@@ -104,7 +143,7 @@ public function admin_options() } } - if (isset($msg) && 'success_connect' === $msg && $this->isConnected) { + if ( 'success_connect' === $msg && $this->isConnected) { echo '

Connexion à HelloAsso réussie.

@@ -256,8 +295,8 @@ public function admin_options() $("#woocommerce_helloasso_enabled, #woocommerce_helloasso_testmode").change(function() { var enabled = $("#woocommerce_helloasso_enabled").is(":checked") ? 1 : 0; var testmode = $("#woocommerce_helloasso_testmode").is(":checked") ? 1 : 0; - var wasEnabled = ' . esc_js($enabled) . '; - var wasTestMode = ' . esc_js($testmode) . '; + var wasEnabled = ' . wp_json_encode((string) $enabled) . '; + var wasTestMode = ' . wp_json_encode((string) $testmode) . '; var buttonText = "Enregistrer les modifications"; if (enabled == 1 && wasEnabled == 0) { @@ -383,7 +422,7 @@ public function process_admin_options() // } if ( $this->isConnected && get_option('helloasso_testmode') == $this->get_option('testmode')) { - return; + return true; } delete_option('helloasso_access_token'); @@ -407,7 +446,7 @@ public function process_admin_options() } if ($this->get_option('enabled') !== 'yes') { - return; + return true; } helloasso_get_oauth_token($client_id, $client_secret, $api_url); @@ -433,7 +472,7 @@ public function process_admin_options() exit; } - public function validate_fields() + public function validate_fields(): bool { if (isset($_GET['pay_for_order'])) { return true; @@ -452,10 +491,23 @@ public function validate_fields() $json = file_get_contents('php://input'); $data = json_decode($json, true); + if (!is_array($data)) { + wc_add_notice('Données de commande invalides', 'error'); + return false; + } + + if ( + !isset($data['billing_address']) || + !is_array($data['billing_address']) || + !isset($data['billing_address']['first_name'], $data['billing_address']['last_name'], $data['billing_address']['email']) + ) { + wc_add_notice('Données de facturation incomplètes', 'error'); + return false; + } - $firstName = $data['billing_address']['first_name']; - $lastName = $data['billing_address']['last_name']; - $email = $data['billing_address']['email']; + $firstName = is_string($data['billing_address']['first_name']) ? $data['billing_address']['first_name'] : ''; + $lastName = is_string($data['billing_address']['last_name']) ? $data['billing_address']['last_name'] : ''; + $email = is_string($data['billing_address']['email']) ? $data['billing_address']['email'] : ''; } if (preg_match('/(.)\1{2,}/', $firstName)) { @@ -624,14 +676,24 @@ public function process_payment($order_id) $json = file_get_contents('php://input'); $data = json_decode($json, true); - $firstName = $data['billing_address']['first_name']; - $lastName = $data['billing_address']['last_name']; - $email = $data['billing_address']['email']; - $adress = $data['billing_address']['address_1']; - $city = $data['billing_address']['city']; - $zipCode = $data['billing_address']['postcode']; - $countryIso = helloasso_convert_country_code($data['billing_address']['country']); - $company = $data['billing_address']['company']; + if (!is_array($data) || !isset($data['billing_address']) || !is_array($data['billing_address'])) { + wc_add_notice('Données de commande invalides', 'error'); + return array( + 'result' => 'failure', + 'messages' => 'Données de commande invalides', + ); + } + + $billingAddress = $data['billing_address']; + + $firstName = isset($billingAddress['first_name']) && is_string($billingAddress['first_name']) ? $billingAddress['first_name'] : ''; + $lastName = isset($billingAddress['last_name']) && is_string($billingAddress['last_name']) ? $billingAddress['last_name'] : ''; + $email = isset($billingAddress['email']) && is_string($billingAddress['email']) ? $billingAddress['email'] : ''; + $adress = isset($billingAddress['address_1']) && is_string($billingAddress['address_1']) ? $billingAddress['address_1'] : ''; + $city = isset($billingAddress['city']) && is_string($billingAddress['city']) ? $billingAddress['city'] : ''; + $zipCode = isset($billingAddress['postcode']) && is_string($billingAddress['postcode']) ? $billingAddress['postcode'] : ''; + $countryIso = isset($billingAddress['country']) && is_string($billingAddress['country']) ? helloasso_convert_country_code($billingAddress['country']) : ''; + $company = isset($billingAddress['company']) && is_string($billingAddress['company']) ? $billingAddress['company'] : ''; helloasso_log_debug('Données client récupérées depuis JSON', array( 'first_name' => $firstName, @@ -699,13 +761,15 @@ public function process_payment($order_id) $payment_type = 'one_time'; if ($this->get_option('multi_3_enabled') === 'yes' || $this->get_option('multi_12_enabled') === 'yes') { - if (isset($_POST['payment_data']) && isset($_POST['payment_data']['payment_type'])) { + $payment_type = 'one_time'; + + if (isset($_POST['payment_data']) && is_array($_POST['payment_data']) && isset($_POST['payment_data']['payment_type']) && is_string($_POST['payment_data']['payment_type'])) { $payment_type = sanitize_text_field($_POST['payment_data']['payment_type']); - } elseif (isset($_POST['payment_type'])) { + } elseif (isset($_POST['payment_type']) && is_string($_POST['payment_type'])) { $payment_type = sanitize_text_field($_POST['payment_type']); - } elseif (isset($_POST['helloasso_payment_type'])) { + } elseif (isset($_POST['helloasso_payment_type']) && is_string($_POST['helloasso_payment_type'])) { $payment_type = sanitize_text_field($_POST['helloasso_payment_type']); - } elseif (isset($_POST['paymentMethodData']) && isset($_POST['paymentMethodData']['payment_type'])) { + } elseif (isset($_POST['paymentMethodData']) && is_array($_POST['paymentMethodData']) && isset($_POST['paymentMethodData']['payment_type']) && is_string($_POST['paymentMethodData']['payment_type'])) { $payment_type = sanitize_text_field($_POST['paymentMethodData']['payment_type']); } @@ -713,12 +777,12 @@ public function process_payment($order_id) $input = file_get_contents('php://input'); $request = json_decode($input, true); - if ($request) { - if (isset($request['payment_data']) && isset($request['payment_data']['payment_type'])) { + if (is_array($request)) { + if (isset($request['payment_data']) && is_array($request['payment_data']) && isset($request['payment_data']['payment_type']) && is_string($request['payment_data']['payment_type'])) { $payment_type = sanitize_text_field($request['payment_data']['payment_type']); - } elseif (isset($request['paymentMethodData']) && isset($request['paymentMethodData']['payment_type'])) { + } elseif (isset($request['paymentMethodData']) && is_array($request['paymentMethodData']) && isset($request['paymentMethodData']['payment_type']) && is_string($request['paymentMethodData']['payment_type'])) { $payment_type = sanitize_text_field($request['paymentMethodData']['payment_type']); - } elseif (isset($request['meta']) && isset($request['meta']['paymentMethodData']) && isset($request['meta']['paymentMethodData']['payment_type'])) { + } elseif (isset($request['meta']) && is_array($request['meta']) && isset($request['meta']['paymentMethodData']) && is_array($request['meta']['paymentMethodData']) && isset($request['meta']['paymentMethodData']['payment_type']) && is_string($request['meta']['paymentMethodData']['payment_type'])) { $payment_type = sanitize_text_field($request['meta']['paymentMethodData']['payment_type']); } else { $payment_type = helloasso_find_payment_type_recursive($request); @@ -818,8 +882,9 @@ public function process_payment($order_id) } else { $api_url = HELLOASSO_WOOCOMMERCE_API_URL_PROD; } - - $url = $api_url . 'v5/organizations/' . get_option('helloasso_organization_slug') . '/checkout-intents'; + $helloasso_organization_slug = get_option('helloasso_organization_slug'); + $helloasso_organization_slug = is_string($helloasso_organization_slug) ? $helloasso_organization_slug : ''; + $url = $api_url . 'v5/organizations/' . $helloasso_organization_slug . '/checkout-intents'; helloasso_log_info('Appel API HelloAsso', array( 'order_id' => $order_id, @@ -857,25 +922,29 @@ public function process_payment($order_id) $response_data = json_decode($response_body); - if (!$response_data || !isset($response_data->redirectUrl)) { + if (!is_object($response_data) || !isset($response_data->redirectUrl) || !is_string($response_data->redirectUrl)) { helloasso_log_error('Réponse API invalide', array( 'order_id' => $order_id, 'response_body' => $response_body, 'response_code' => $response_code )); + + return array( + 'result' => 'failure', + 'messages' => 'Réponse API invalide', + ); } helloasso_log_info('Paiement traité avec succès', array( 'order_id' => $order_id, - 'redirect_url' => $response_data->redirectUrl ?? 'unknown' + 'redirect_url' => $response_data->redirectUrl )); - - $order->save(); + $order->save(); return array( 'result' => 'success', - 'redirect' => json_decode($response_body)->redirectUrl + 'redirect' => $response_data->redirectUrl, ); } } diff --git a/phpstan.neon b/phpstan.neon index 3f93b04..55d2bc6 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -5,11 +5,13 @@ parameters: level: 5 scanFiles: - stubs/woocommerce.php + - stubs/woocommerce-payment-gateway.php - stubs/woocommerce-payment-gateways.php - stubs/woocommerce-blocks.php - stubs/woocommerce-blocks-integrations.php paths: - inc + - helloasso-api - wc-api - helper - block @@ -25,6 +27,7 @@ parameters: checkFunctionNameCase: true checkExplicitMixed: true tmpDir: var/phpstan + treatPhpDocTypesAsCertain: false universalObjectCratesClasses: - WP_Post diff --git a/stubs/woocommerce-payment-gateway.php b/stubs/woocommerce-payment-gateway.php new file mode 100644 index 0000000..ff37b45 --- /dev/null +++ b/stubs/woocommerce-payment-gateway.php @@ -0,0 +1,168 @@ + */ + public $settings = []; + + /** + * @var + */ + public $form_fields = []; + + /** @var string */ + public $title = ''; + + /** @var string */ + public $description = ''; + + /** @var string */ + public $enabled = ''; + + /** @var string */ + public $order_button_text = ''; + + /** @var string */ + public $icon = ''; + + public function __construct() + { + } + + /** + * Initialise settings form fields. + * + * @return mixed + */ + public function init_form_fields() + { + } + public function init_settings(): void + { + } + + /** + * @param string $key + * @param mixed $empty_value + * @return mixed + */ + public function get_option($key, $empty_value = null) + { + return $empty_value; + } + + /** + * @param string $key + * @param mixed $value + * @return void + */ + public function update_option($key, $value) + { + } + + /** + * @return bool + */ + public function process_admin_options() + { + return true; + } + + /** + * @param int $order_id + * @return array + */ + public function process_payment($order_id) + { + return []; + } + + public function validate_fields(): bool + { + return true; + } + + /** + * @return mixed + */ + public function payment_fields() + { + } + /** + * @return mixed + */ + public function admin_options() + { + } + + public function get_title(): string + { + return $this->title; + } + + public function get_description(): string + { + return $this->description; + } + + public function get_icon(): string + { + return $this->icon; + } + + public function get_return_url($order = null): string + { + return ''; + } + + public function is_available(): bool + { + return true; + } + + /** + * @return array + */ + public function get_tokens(): array + { + return []; + } + + public function add_error(string $error): void + { + } + + public function get_description_html(): string + { + return $this->description; + } + + public function generate_settings_html(array $form_fields = [], bool $echo = true): string + { + return ''; + } + + } +} \ No newline at end of file diff --git a/stubs/woocommerce-payment-gateways.php b/stubs/woocommerce-payment-gateways.php index 0585ee5..62bba98 100644 --- a/stubs/woocommerce-payment-gateways.php +++ b/stubs/woocommerce-payment-gateways.php @@ -18,4 +18,4 @@ public function payment_gateways(): array return []; } } -} \ No newline at end of file +} diff --git a/stubs/woocommerce.php b/stubs/woocommerce.php index 80eabca..64e4482 100644 --- a/stubs/woocommerce.php +++ b/stubs/woocommerce.php @@ -2,51 +2,37 @@ declare(strict_types=1); -if (!class_exists('WC_Payment_Gateway')) { - abstract class WC_Payment_Gateway - { - /** @var array */ - public $settings = []; - - /** @var string */ - public $id = ''; - - /** @var string */ - public $method_title = ''; - - /** @var string */ - public $method_description = ''; - - public function __construct() - { - } - public function init_form_fields(): void - { - } - - public function init_settings(): void - { - } - - /** - * @return mixed - */ - public function get_option($key, $empty_value = null) - { - return $empty_value; - } - - public function process_admin_options(): bool - { - return true; - } - } -} if (!function_exists('get_woocommerce_currency')) { function get_woocommerce_currency(): string { return 'EUR'; } +} +// wc_add_notice +if (!function_exists('wc_add_notice')) { + function wc_add_notice(string $message, string $notice_type = 'success'): void + { + // This is a stub function for wc_add_notice. In a real implementation, this would add the notice to WooCommerce's notice system. + // For testing purposes, you can log the notice or simply ignore it. + } +} +// wc_get_order +if (!function_exists('wc_get_order')) { + function wc_get_order($order_id) + { + // This is a stub function for wc_get_order. In a real implementation, this would retrieve the order object from WooCommerce. + // For testing purposes, you can return a mock order object or simply return null. + return null; + } +} +// wc_get_checkout_url +if (!function_exists('wc_get_checkout_url')) { + function wc_get_checkout_url(): string + { + // This is a stub function for wc_get_checkout_url. In a real implementation, this would return the URL of the checkout page. + // For testing purposes, you can return a placeholder URL or simply return an empty string. + return 'https://example.com/checkout'; + } } \ No newline at end of file diff --git a/wc-api/helloasso-woocommerce-wc-api.php b/wc-api/helloasso-woocommerce-wc-api.php index 1fdcaec..be2c1a7 100644 --- a/wc-api/helloasso-woocommerce-wc-api.php +++ b/wc-api/helloasso-woocommerce-wc-api.php @@ -83,8 +83,10 @@ function helloasso_endpoint() $response_body = wp_remote_retrieve_body($response); $data = json_decode($response_body); - - if (isset($data->access_token)) { + if (!is_object($data)) { + return null; + } + if (isset($data->access_token) && is_string($data->access_token)) { helloasso_log_info('Token OAuth2 reçu avec succès', array( 'organization_slug' => $data->organization_slug ?? 'unknown', 'expires_in' => $data->expires_in ?? 'unknown' @@ -121,7 +123,10 @@ function helloasso_endpoint() 'response_body' => wp_remote_retrieve_body($responseNotif) )); - $gateway_settings = get_option('woocommerce_helloasso_settings', array()); + $gateway_settings = is_array(get_option('woocommerce_helloasso_settings', array())) + ? get_option('woocommerce_helloasso_settings', array()) + : array(); + $gateway_settings['enabled'] = 'no'; update_option('woocommerce_helloasso_settings', $gateway_settings); @@ -156,6 +161,11 @@ function helloasso_endpoint() function helloasso_endpoint_deco() { $gateway_settings = get_option('woocommerce_helloasso_settings', array()); + + if (!is_array($gateway_settings)) { + $gateway_settings = array(); + } + $gateway_settings['enabled'] = 'no'; $gateway_settings['multi_3_enabled'] = 'no'; $gateway_settings['multi_12_enabled'] = 'no'; @@ -186,30 +196,59 @@ function helloasso_endpoint_webhook() $raw_input = file_get_contents('php://input'); $data = json_decode($raw_input, true); + if (!is_array($data)) { + helloasso_log_error('Payload webhook invalide', array( + 'raw_data_length' => strlen($raw_input), + )); + exit; + } + + /** @var array{ + * eventType?: string, + * metadata?: array{reference?: string}, + * data?: array{checkoutIntentId?: string, new_slug_organization?: string} + * } $data + */ + helloasso_log_info('Webhook HelloAsso reçu', array( - 'event_type' => $data['eventType'] ?? 'unknown', + 'event_type' => isset($data['eventType']) && is_string($data['eventType']) ? $data['eventType'] : 'unknown', 'raw_data_length' => strlen($raw_input) )); add_option('helloasso_webhook_data', wp_json_encode($data)); - if ('Order' === $data['eventType']) { + if (($data['eventType'] ?? null) === 'Order') { + $metadata = isset($data['metadata']) && is_array($data['metadata']) ? $data['metadata'] : array(); + $payload = isset($data['data']) && is_array($data['data']) ? $data['data'] : array(); + + $reference = isset($metadata['reference']) && is_string($metadata['reference']) ? $metadata['reference'] : 'unknown'; + $checkoutIntentId = isset($payload['checkoutIntentId']) && is_string($payload['checkoutIntentId']) ? $payload['checkoutIntentId'] : 'unknown'; + helloasso_log_info('Traitement d\'un événement Order', array( - 'order_reference' => $data['metadata']['reference'] ?? 'unknown', - 'checkout_intent_id' => $data['data']['checkoutIntentId'] ?? 'unknown' + 'order_reference' => $reference, + 'checkout_intent_id' => $checkoutIntentId )); - validate_order($data['metadata']['reference'], $data['data']['checkoutIntentId']); - } else if ('Organization' === $data['eventType']) { + + if ($reference !== 'unknown' && $checkoutIntentId !== 'unknown') { + validate_order($reference, $checkoutIntentId); + } + } elseif (($data['eventType'] ?? null) === 'Organization') { + $payload = isset($data['data']) && is_array($data['data']) ? $data['data'] : array(); + $newSlug = isset($payload['new_slug_organization']) && is_string($payload['new_slug_organization']) ? $payload['new_slug_organization'] : 'unknown'; + helloasso_log_info('Traitement d\'un événement Organization', array( - 'new_slug' => $data['data']['new_slug_organization'] ?? 'unknown' + 'new_slug' => $newSlug )); + delete_option('helloasso_organization_slug'); - add_option('helloasso_organization_slug', $data['data']['new_slug_organization']); + if ($newSlug !== 'unknown') { + add_option('helloasso_organization_slug', $newSlug); + } helloasso_refresh_token_asso(); } else { helloasso_log_warning('Événement webhook non reconnu', array( - 'event_type' => $data['eventType'] ?? 'unknown' + 'event_type' => isset($data['eventType']) && is_string($data['eventType']) ? $data['eventType'] : 'unknown' )); } @@ -272,8 +311,10 @@ function validate_order($orderId, $checkoutIntentId) 'has_token' => !empty($helloasso_access_token_asso) )); - $url = $api_url . 'v5/organizations/' . $slug . '/checkout-intents/' . $checkoutIntentId; - $response = wp_remote_request($url, helloasso_get_args_get_token($helloasso_access_token_asso)); + $slug = is_string($slug) ? $slug : ''; + $checkoutIntentId = is_string($checkoutIntentId) ? $checkoutIntentId : ''; + + $url = $api_url . 'v5/organizations/' . $slug . '/checkout-intents/' . $checkoutIntentId; $response = wp_remote_request($url, helloasso_get_args_get_token($helloasso_access_token_asso)); $response_code = wp_remote_retrieve_response_code($response); $body = wp_remote_retrieve_body($response); @@ -295,7 +336,14 @@ function validate_order($orderId, $checkoutIntentId) $haOrder = json_decode($body); - if (!$haOrder || !isset($haOrder->order) || !isset($haOrder->order->payments) || empty($haOrder->order->payments)) { + if (!is_object($haOrder) || !isset($haOrder->order) || !is_object($haOrder->order)) { + helloasso_log_error('Réponse JSON invalide', array( + 'order_id' => $orderId, + 'response_body' => $body, + )); + return $order; + } + if ( !isset($haOrder->order) || !isset($haOrder->order->payments) || empty($haOrder->order->payments)) { helloasso_log_error('Structure de réponse HelloAsso invalide', array( 'order_id' => $orderId, 'response_body' => $body From cfa8ba61c64c8d9885c8bf0967b11ef2d029fcc2 Mon Sep 17 00:00:00 2001 From: jerome Date: Wed, 3 Jun 2026 13:08:48 +0200 Subject: [PATCH 06/11] bump version --- changelog.txt | 8 +++++--- helloasso-woocommerce-gateway.php | 5 +++-- inc/Gateway/WC_HelloAsso_Gateway.php | 2 +- readme.txt | 12 ++++++++++-- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/changelog.txt b/changelog.txt index 77bf359..311e41c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,10 +1,12 @@ *** HelloAsso Payments for WooCommerce Changelog *** -2026-06-03 - version 1.1.2 +2026-06-03 - version 1.1.3 * Update logout message on admin panel * Add phpstan to ensuire compatibility with wordpress and woocommerce - -2026-04-23 - version 1.1.1 +* PHP coverage until 8.5 +* ensure compatibility with woocommerce 10.8.1 +* ensure compatibility with wordpress 7.0 +2026-04-23 - version 1.1.2 * Rework api calls 2024-02-28 - version 1.0.0 * Initial release diff --git a/helloasso-woocommerce-gateway.php b/helloasso-woocommerce-gateway.php index 7ad8f26..b2296e0 100644 --- a/helloasso-woocommerce-gateway.php +++ b/helloasso-woocommerce-gateway.php @@ -3,8 +3,9 @@ /** * Plugin Name: HelloAsso Payments for WooCommerce * Description: Recevez 100% de vos paiements gratuitement. HelloAsso est la seule solution de paiement gratuite du secteur associatif. Nous sommes financés librement par la solidarité de celles et ceux qui choisissent de laisser une contribution volontaire au moment du paiement à une association. - * Version: 1.1.2 - * Requires at least: 5.0 + * Version: 1.1.3 + * Requires at least: 6.0 + * Tested up to: 7.0 * WC requires at least: 7.7 * Requires PHP: 7.2.34 * Requires Plugins: woocommerce diff --git a/inc/Gateway/WC_HelloAsso_Gateway.php b/inc/Gateway/WC_HelloAsso_Gateway.php index 794696b..49ba353 100644 --- a/inc/Gateway/WC_HelloAsso_Gateway.php +++ b/inc/Gateway/WC_HelloAsso_Gateway.php @@ -50,7 +50,7 @@ public function __construct() { helloasso_log_info('Initialisation du gateway HelloAsso', array( - 'plugin_version' => '1.1.2', + 'plugin_version' => '1.1.3', 'wc_version' => defined('WC_VERSION') ? WC_VERSION : 'unknown' )); diff --git a/readme.txt b/readme.txt index bffda55..a92f5a4 100644 --- a/readme.txt +++ b/readme.txt @@ -3,9 +3,9 @@ Contributors: helloasso Donate link: https://helloasso.com Tags: helloasso, payment, association, don, billetterie Requires at least: 5.0 -Tested up to: 6.9 +Tested up to: 7.0 Requires PHP: 7.2.34 -Stable tag: 1.1.2 +Stable tag: 1.1.3 License: GPLv3 License URI: https://www.gnu.org/licenses/gpl-3.0.html @@ -96,6 +96,14 @@ Please report security bugs found in the source code of the helloasso plugin thr == Changelog == += 1.1.3 = + +* Update logout message on admin panel +* Add phpstan to ensuire compatibility with wordpress and woocommerce +* PHP coverage until 8.5 +* ensure compatibility with woocommerce 10.8.1 +* ensure compatibility with wordpress 7.0 + = 1.1.2 = * Fix contrôle d'expiration des refresh tokens From 48a34c8a04e49c9182a7ff6e34da0fc5bca0ae8b Mon Sep 17 00:00:00 2001 From: jerome Date: Mon, 29 Jun 2026 09:31:37 +0200 Subject: [PATCH 07/11] fix incorrect path to plugin srcs --- helloasso-woocommerce-gateway.php | 1 + inc/Gateway/WC_HelloAsso_Gateway.php | 3 ++- stubs/woocommerce.php | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/helloasso-woocommerce-gateway.php b/helloasso-woocommerce-gateway.php index b2296e0..c274785 100644 --- a/helloasso-woocommerce-gateway.php +++ b/helloasso-woocommerce-gateway.php @@ -26,6 +26,7 @@ require_once plugin_dir_path( __FILE__ ) . 'vendor/autoload.php'; // Durée de validité du refresh token : 30 jours en secondes define('HELLOASSO_REFRESH_TOKEN_LIFETIME', 30 * 24 * 60 * 60); // 2592000 secondes +define('HELLOASSO_PLUGIN_DIR', plugin_dir_url( __FILE__)); use Automattic\WooCommerce\Utilities\FeaturesUtil; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; use Helloasso\HelloassoPaymentsForWoocommerce\Gateway\WC_HelloAsso_Gateway; diff --git a/inc/Gateway/WC_HelloAsso_Gateway.php b/inc/Gateway/WC_HelloAsso_Gateway.php index 49ba353..3690249 100644 --- a/inc/Gateway/WC_HelloAsso_Gateway.php +++ b/inc/Gateway/WC_HelloAsso_Gateway.php @@ -76,8 +76,9 @@ public function __construct() public function payment_fields() { if ($this->description) { + echo '
'; - echo 'HelloAsso Logo'; + echo 'HelloAsso Logo'; echo '

' . wp_kses_post($this->description) . '

'; echo '
'; } diff --git a/stubs/woocommerce.php b/stubs/woocommerce.php index 64e4482..cf0ef87 100644 --- a/stubs/woocommerce.php +++ b/stubs/woocommerce.php @@ -3,6 +3,7 @@ declare(strict_types=1); +define('HELLOASSO_PLUGIN_DIR', '/tmp/helloasso-plugin/'); if (!function_exists('get_woocommerce_currency')) { function get_woocommerce_currency(): string From 68656b13d883aed6284cd0dc7dc4e674a69eed08 Mon Sep 17 00:00:00 2001 From: jerome Date: Mon, 29 Jun 2026 09:59:23 +0200 Subject: [PATCH 08/11] add phpstan to contributing rules --- CONTRIBUTING.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 72bb87e..9cd522e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,10 +14,12 @@ We use github to host code, to track issues and feature requests, as well as acc Pull requests are the best way to propose changes to the codebase. We actively welcome your pull requests: 1. Fork the repo and create your branch from `main`. -2. If you've added code that should be tested, add tests. -3. Ensure the test suite passes. -4. Make sure your code lints. -5. Issue that pull request! +2. Install composer dependancies +3. If you've added code that should be tested, add tests. +4. Run ./vendor/bin/phpstan and correct if necessary +5. Ensure the test suite passes. +6. Make sure your code lints. +7. Issue that pull request! ## Any contributions you make will be under the GPLv3 Software License In short, when you submit code changes, your submissions are understood to be under the same [GPLv3](LICENSE) that covers the project. From 550b149eb1684fceacf06ed6a3e2bcdfdc7a2d68 Mon Sep 17 00:00:00 2001 From: jerome Date: Mon, 29 Jun 2026 10:27:08 +0200 Subject: [PATCH 09/11] remove multiple logs && add generic context for all logs --- helloasso-woocommerce-gateway.php | 5 +++++ helper/helloasso-woocommerce-helper.php | 3 ++- inc/Gateway/WC_HelloAsso_Gateway.php | 5 +---- stubs/woocommerce.php | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/helloasso-woocommerce-gateway.php b/helloasso-woocommerce-gateway.php index c274785..4b179b2 100644 --- a/helloasso-woocommerce-gateway.php +++ b/helloasso-woocommerce-gateway.php @@ -26,6 +26,7 @@ require_once plugin_dir_path( __FILE__ ) . 'vendor/autoload.php'; // Durée de validité du refresh token : 30 jours en secondes define('HELLOASSO_REFRESH_TOKEN_LIFETIME', 30 * 24 * 60 * 60); // 2592000 secondes +define('HELLOASSO_PLUGIN_VERSION', '1.1.3'); define('HELLOASSO_PLUGIN_DIR', plugin_dir_url( __FILE__)); use Automattic\WooCommerce\Utilities\FeaturesUtil; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; @@ -104,6 +105,10 @@ function helloasso_activate() deactivate_plugins(plugin_basename(__FILE__)); wp_die('HelloAsso ne prend en charge que les paiements en euros. Veuillez changer la devise de votre boutique en euros pour activer ce plugin.'); } + helloasso_log_info('Initialisation du gateway HelloAsso', array( + 'plugin_version' => HELLOASSO_PLUGIN_VERSION, + 'wc_version' => defined('WC_VERSION') ? WC_VERSION : 'unknown' + )); } diff --git a/helper/helloasso-woocommerce-helper.php b/helper/helloasso-woocommerce-helper.php index 1a2043a..1d0b520 100644 --- a/helper/helloasso-woocommerce-helper.php +++ b/helper/helloasso-woocommerce-helper.php @@ -14,7 +14,8 @@ function helloasso_log($message, $level = 'info', $context = array()) $context['timestamp'] = current_time('Y-m-d H:i:s'); $context['memory_usage'] = memory_get_usage(true); $context['peak_memory'] = memory_get_peak_usage(true); - + $context['plugin_version'] = HELLOASSO_PLUGIN_VERSION; + $context['wc_version'] = defined('WC_VERSION') ? WC_VERSION : 'unknown'; $log_message = sprintf( '[%s] %s - %s', strtoupper($level), diff --git a/inc/Gateway/WC_HelloAsso_Gateway.php b/inc/Gateway/WC_HelloAsso_Gateway.php index 3690249..ee4fc21 100644 --- a/inc/Gateway/WC_HelloAsso_Gateway.php +++ b/inc/Gateway/WC_HelloAsso_Gateway.php @@ -49,10 +49,7 @@ class WC_HelloAsso_Gateway extends \WC_Payment_Gateway public function __construct() { - helloasso_log_info('Initialisation du gateway HelloAsso', array( - 'plugin_version' => '1.1.3', - 'wc_version' => defined('WC_VERSION') ? WC_VERSION : 'unknown' - )); + $this->id = 'helloasso'; $this->icon = ''; diff --git a/stubs/woocommerce.php b/stubs/woocommerce.php index cf0ef87..6f98c34 100644 --- a/stubs/woocommerce.php +++ b/stubs/woocommerce.php @@ -4,7 +4,7 @@ define('HELLOASSO_PLUGIN_DIR', '/tmp/helloasso-plugin/'); - +define('HELLOASSO_PLUGIN_VERSION', '1.1.3'); if (!function_exists('get_woocommerce_currency')) { function get_woocommerce_currency(): string { From cfa918be892143d6f60ca00c7f1057188b340094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=CC=81o?= Date: Tue, 30 Jun 2026 19:08:06 +0200 Subject: [PATCH 10/11] merge race condition --- inc/Gateway/WC_HelloAsso_Gateway.php | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/inc/Gateway/WC_HelloAsso_Gateway.php b/inc/Gateway/WC_HelloAsso_Gateway.php index ee4fc21..b0c5aa1 100644 --- a/inc/Gateway/WC_HelloAsso_Gateway.php +++ b/inc/Gateway/WC_HelloAsso_Gateway.php @@ -548,12 +548,12 @@ public function validate_fields(): bool return false; } - if (preg_match('/![a-zA-ZéèêëáàâäúùûüçÇ\'-]/', $firstName)) { + if (preg_match('/[^a-zA-ZéèêëáàâäúùûüçÇ\' -]/', $firstName)) { wc_add_notice('Le prénom ne doit pas contenir de caractères spéciaux ni de caractères n\'appartenant pas à l\'alphabet latin', 'error'); return false; } - if (preg_match('/![a-zA-ZéèêëáàâäúùûüçÇ\'-]/', $lastName)) { + if (preg_match('/[^a-zA-ZéèêëáàâäúùûüçÇ\' -]/', $lastName)) { wc_add_notice('Le nom ne doit pas contenir de caractères spéciaux ni de caractères n\'appartenant pas à l\'alphabet latin', 'error'); return false; } @@ -579,7 +579,12 @@ public function process_payment($order_id) 'payment_method' => 'helloasso' )); - helloasso_refresh_token_asso(); + $tokenExpires = get_option('helloasso_token_expires_in_asso'); + $accessToken = get_option('helloasso_access_token_asso'); + if (!$accessToken || !$tokenExpires || time() >= (int) $tokenExpires) { + helloasso_refresh_token_asso(); + } + $order = wc_get_order($order_id); if (!$order) { @@ -898,7 +903,8 @@ public function process_payment($order_id) 'error' => $response->get_error_message(), 'error_code' => $response->get_error_code() )); - echo 'Erreur : ' . esc_html($response->get_error_message()); + wc_add_notice('Une erreur est survenue lors de la connexion à HelloAsso. Veuillez réessayer.', 'error'); + return array('result' => 'failure'); } $response_body = wp_remote_retrieve_body($response); @@ -916,6 +922,8 @@ public function process_payment($order_id) 'response_code' => $response_code, 'response_body' => $response_body )); + wc_add_notice('Une erreur est survenue lors de la création du paiement HelloAsso (code ' . $response_code . '). Veuillez réessayer.', 'error'); + return array('result' => 'failure'); } $response_data = json_decode($response_body); @@ -926,11 +934,8 @@ public function process_payment($order_id) 'response_body' => $response_body, 'response_code' => $response_code )); - - return array( - 'result' => 'failure', - 'messages' => 'Réponse API invalide', - ); + wc_add_notice('Réponse inattendue de HelloAsso. Veuillez réessayer.', 'error'); + return array('result' => 'failure'); } helloasso_log_info('Paiement traité avec succès', array( From 1388f759cafbf3b3e504f5656d70d6e5e3d0728e Mon Sep 17 00:00:00 2001 From: jerome Date: Fri, 10 Jul 2026 11:09:45 +0200 Subject: [PATCH 11/11] fix PHPstan rules --- inc/Gateway/WC_HelloAsso_Gateway.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/inc/Gateway/WC_HelloAsso_Gateway.php b/inc/Gateway/WC_HelloAsso_Gateway.php index b0c5aa1..98d6603 100644 --- a/inc/Gateway/WC_HelloAsso_Gateway.php +++ b/inc/Gateway/WC_HelloAsso_Gateway.php @@ -573,17 +573,22 @@ public function validate_fields(): bool public function process_payment($order_id) { + $order_id = (int) $order_id; + helloasso_log_info('Début du traitement de paiement', array( 'order_id' => $order_id, 'user_id' => get_current_user_id(), 'payment_method' => 'helloasso' )); - $tokenExpires = get_option('helloasso_token_expires_in_asso'); + $tokenExpiresRaw = get_option('helloasso_token_expires_in_asso'); $accessToken = get_option('helloasso_access_token_asso'); + $tokenExpires = is_numeric($tokenExpiresRaw) ? (int) $tokenExpiresRaw : 0; + if (!$accessToken || !$tokenExpires || time() >= (int) $tokenExpires) { helloasso_refresh_token_asso(); - } + } + $order = wc_get_order($order_id); @@ -591,7 +596,7 @@ public function process_payment($order_id) helloasso_log_error('Commande introuvable', array('order_id' => $order_id)); return array('result' => 'failure', 'messages' => 'Commande introuvable'); } - + helloasso_log_info('Récupération des données client', array( 'order_id' => $order_id, 'order_status' => $order->get_status(),