Файловый менеджер - Редактировать - /home/infrafs/INFRABIKEIT/wp-content/plugins/packages.tar
Назад
woocommerce-blocks/vendor/composer/autoload_real.php 0000644 00000003467 15133551764 0017032 0 ustar 00 <?php // autoload_real.php @generated by Composer class ComposerAutoloaderInitb78ca910fe07339d6189615f1734a3e3 { private static $loader; public static function loadClassLoader($class) { if ('Composer\Autoload\ClassLoader' === $class) { require __DIR__ . '/ClassLoader.php'; } } /** * @return \Composer\Autoload\ClassLoader */ public static function getLoader() { if (null !== self::$loader) { return self::$loader; } spl_autoload_register(array('ComposerAutoloaderInitb78ca910fe07339d6189615f1734a3e3', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); spl_autoload_unregister(array('ComposerAutoloaderInitb78ca910fe07339d6189615f1734a3e3', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInitb78ca910fe07339d6189615f1734a3e3::getInitializer($loader)); } else { $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { $loader->set($namespace, $path); } $map = require __DIR__ . '/autoload_psr4.php'; foreach ($map as $namespace => $path) { $loader->setPsr4($namespace, $path); } $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); } } $loader->register(true); return $loader; } } woocommerce-blocks/vendor/composer/installed.json 0000644 00000016073 15133551764 0016355 0 ustar 00 { "packages": [ { "name": "automattic/jetpack-autoloader", "version": "v2.10.4", "version_normalized": "2.10.4.0", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-autoloader.git", "reference": "70cb300a7a215ae87c671f600f77093518f87bac" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/70cb300a7a215ae87c671f600f77093518f87bac", "reference": "70cb300a7a215ae87c671f600f77093518f87bac", "shasum": "" }, "require": { "composer-plugin-api": "^1.1 || ^2.0" }, "require-dev": { "automattic/jetpack-changelogger": "^1.2", "yoast/phpunit-polyfills": "0.2.0" }, "time": "2021-08-10T06:44:08+00:00", "type": "composer-plugin", "extra": { "autotagger": true, "class": "Automattic\\Jetpack\\Autoloader\\CustomAutoloaderPlugin", "mirror-repo": "Automattic/jetpack-autoloader", "changelogger": { "link-template": "https://github.com/Automattic/jetpack-autoloader/compare/v${old}...v${new}" }, "branch-alias": { "dev-master": "2.10.x-dev" } }, "installation-source": "dist", "autoload": { "classmap": [ "src/AutoloadGenerator.php" ], "psr-4": { "Automattic\\Jetpack\\Autoloader\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "GPL-2.0-or-later" ], "description": "Creates a custom autoloader for a plugin or theme.", "support": { "source": "https://github.com/Automattic/jetpack-autoloader/tree/v2.10.4" }, "install-path": "../automattic/jetpack-autoloader" }, { "name": "composer/installers", "version": "v1.11.0", "version_normalized": "1.11.0.0", "source": { "type": "git", "url": "https://github.com/composer/installers.git", "reference": "ae03311f45dfe194412081526be2e003960df74b" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/composer/installers/zipball/ae03311f45dfe194412081526be2e003960df74b", "reference": "ae03311f45dfe194412081526be2e003960df74b", "shasum": "" }, "require": { "composer-plugin-api": "^1.0 || ^2.0" }, "replace": { "roundcube/plugin-installer": "*", "shama/baton": "*" }, "require-dev": { "composer/composer": "1.6.* || ^2.0", "composer/semver": "^1 || ^3", "phpstan/phpstan": "^0.12.55", "phpstan/phpstan-phpunit": "^0.12.16", "symfony/phpunit-bridge": "^4.2 || ^5", "symfony/process": "^2.3" }, "time": "2021-04-28T06:42:17+00:00", "type": "composer-plugin", "extra": { "class": "Composer\\Installers\\Plugin", "branch-alias": { "dev-main": "1.x-dev" } }, "installation-source": "dist", "autoload": { "psr-4": { "Composer\\Installers\\": "src/Composer/Installers" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Kyle Robinson Young", "email": "kyle@dontkry.com", "homepage": "https://github.com/shama" } ], "description": "A multi-framework Composer library installer", "homepage": "https://composer.github.io/installers/", "keywords": [ "Craft", "Dolibarr", "Eliasis", "Hurad", "ImageCMS", "Kanboard", "Lan Management System", "MODX Evo", "MantisBT", "Mautic", "Maya", "OXID", "Plentymarkets", "Porto", "RadPHP", "SMF", "Starbug", "Thelia", "Whmcs", "WolfCMS", "agl", "aimeos", "annotatecms", "attogram", "bitrix", "cakephp", "chef", "cockpit", "codeigniter", "concrete5", "croogo", "dokuwiki", "drupal", "eZ Platform", "elgg", "expressionengine", "fuelphp", "grav", "installer", "itop", "joomla", "known", "kohana", "laravel", "lavalite", "lithium", "magento", "majima", "mako", "mediawiki", "miaoxing", "modulework", "modx", "moodle", "osclass", "phpbb", "piwik", "ppi", "processwire", "puppet", "pxcms", "reindex", "roundcube", "shopware", "silverstripe", "sydes", "sylius", "symfony", "tastyigniter", "typo3", "wordpress", "yawik", "zend", "zikula" ], "support": { "issues": "https://github.com/composer/installers/issues", "source": "https://github.com/composer/installers/tree/v1.11.0" }, "funding": [ { "url": "https://packagist.com", "type": "custom" }, { "url": "https://github.com/composer", "type": "github" }, { "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], "install-path": "./installers" } ], "dev": false, "dev-package-names": [] } woocommerce-blocks/vendor/composer/InstalledVersions.php 0000644 00000033147 15133551764 0017665 0 ustar 00 <?php /* * This file is part of Composer. * * (c) Nils Adermann <naderman@naderman.de> * Jordi Boggiano <j.boggiano@seld.be> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\Autoload\ClassLoader; use Composer\Semver\VersionParser; /** * This class is copied in every Composer installed project and available to all * * See also https://getcomposer.org/doc/07-runtime.md#installed-versions * * To require it's presence, you can require `composer-runtime-api ^2.0` */ class InstalledVersions { private static $installed; private static $canGetVendors; private static $installedByVendor = array(); /** * Returns a list of all package names which are present, either by being installed, replaced or provided * * @return string[] * @psalm-return list<string> */ public static function getInstalledPackages() { $packages = array(); foreach (self::getInstalled() as $installed) { $packages[] = array_keys($installed['versions']); } if (1 === \count($packages)) { return $packages[0]; } return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); } /** * Returns a list of all package names with a specific type e.g. 'library' * * @param string $type * @return string[] * @psalm-return list<string> */ public static function getInstalledPackagesByType($type) { $packagesByType = array(); foreach (self::getInstalled() as $installed) { foreach ($installed['versions'] as $name => $package) { if (isset($package['type']) && $package['type'] === $type) { $packagesByType[] = $name; } } } return $packagesByType; } /** * Checks whether the given package is installed * * This also returns true if the package name is provided or replaced by another package * * @param string $packageName * @param bool $includeDevRequirements * @return bool */ public static function isInstalled($packageName, $includeDevRequirements = true) { foreach (self::getInstalled() as $installed) { if (isset($installed['versions'][$packageName])) { return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); } } 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($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, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string} */ 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, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>} */ public static function getRawData() { @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); if (null === self::$installed) { // only require the installed.php file if this file is loaded from its dumped location, // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 if (substr(__DIR__, -8, 1) !== 'C') { self::$installed = include __DIR__ . '/installed.php'; } else { self::$installed = array(); } } return self::$installed; } /** * Returns the raw data of all installed.php which are currently loaded for custom implementations * * @return array[] * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}> */ public static function getAllRawData() { return self::getInstalled(); } /** * Lets you reload the static array from another file * * This is only useful for complex integrations in which a project needs to use * this class but then also needs to execute another project's autoloader in process, * and wants to ensure both projects have access to their version of installed.php. * * A typical case would be PHPUnit, where it would need to make sure it reads all * the data it needs from this class, then call reload() with * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure * the project in which it runs can then also use this class safely, without * interference between PHPUnit's dependencies and the project's dependencies. * * @param array[] $data A vendor/composer/installed.php data set * @return void * * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>} $data */ public static function reload($data) { self::$installed = $data; self::$installedByVendor = array(); } /** * @return array[] * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}> */ private static function getInstalled() { if (null === self::$canGetVendors) { self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); } $installed = array(); if (self::$canGetVendors) { foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { if (isset(self::$installedByVendor[$vendorDir])) { $installed[] = self::$installedByVendor[$vendorDir]; } elseif (is_file($vendorDir.'/composer/installed.php')) { $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; 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') { self::$installed = require __DIR__ . '/installed.php'; } else { self::$installed = array(); } } $installed[] = self::$installed; return $installed; } } woocommerce-blocks/vendor/composer/autoload_namespaces.php 0000644 00000000225 15133551764 0020213 0 ustar 00 <?php // autoload_namespaces.php @generated by Composer $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( ); woocommerce-blocks/vendor/composer/autoload_classmap.php 0000644 00000000555 15133551764 0017705 0 ustar 00 <?php // autoload_classmap.php @generated by Composer $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( 'Automattic\\Jetpack\\Autoloader\\AutoloadGenerator' => $vendorDir . '/automattic/jetpack-autoloader/src/AutoloadGenerator.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', ); woocommerce-blocks/vendor/composer/installers/phpstan.neon.dist 0000644 00000000325 15133551764 0021154 0 ustar 00 parameters: level: 5 paths: - src - tests excludes_analyse: - tests/Composer/Installers/Test/PolyfillTestCase.php includes: - vendor/phpstan/phpstan-phpunit/extension.neon woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/WHMCSInstaller.php 0000644 00000001506 15133551764 0025624 0 ustar 00 <?php namespace Composer\Installers; class WHMCSInstaller extends BaseInstaller { protected $locations = array( 'addons' => 'modules/addons/{$vendor}_{$name}/', 'fraud' => 'modules/fraud/{$vendor}_{$name}/', 'gateways' => 'modules/gateways/{$vendor}_{$name}/', 'notifications' => 'modules/notifications/{$vendor}_{$name}/', 'registrars' => 'modules/registrars/{$vendor}_{$name}/', 'reports' => 'modules/reports/{$vendor}_{$name}/', 'security' => 'modules/security/{$vendor}_{$name}/', 'servers' => 'modules/servers/{$vendor}_{$name}/', 'social' => 'modules/social/{$vendor}_{$name}/', 'support' => 'modules/support/{$vendor}_{$name}/', 'templates' => 'templates/{$vendor}_{$name}/', 'includes' => 'includes/{$vendor}_{$name}/' ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/MajimaInstaller.php 0000644 00000001502 15133551764 0026135 0 ustar 00 <?php namespace Composer\Installers; /** * Plugin/theme installer for majima * @author David Neustadt */ class MajimaInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'plugins/{$name}/', ); /** * Transforms the names * @param array $vars * @return array */ public function inflectPackageVars($vars) { return $this->correctPluginName($vars); } /** * Change hyphenated names to camelcase * @param array $vars * @return array */ private function correctPluginName($vars) { $camelCasedName = preg_replace_callback('/(-[a-z])/', function ($matches) { return strtoupper($matches[0][1]); }, $vars['name']); $vars['name'] = ucfirst($camelCasedName); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/CakePHPInstaller.php 0000644 00000003362 15133551764 0026160 0 ustar 00 <?php namespace Composer\Installers; use Composer\DependencyResolver\Pool; use Composer\Semver\Constraint\Constraint; class CakePHPInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'Plugin/{$name}/', ); /** * Format package name to CamelCase */ public function inflectPackageVars($vars) { if ($this->matchesCakeVersion('>=', '3.0.0')) { return $vars; } $nameParts = explode('/', $vars['name']); foreach ($nameParts as &$value) { $value = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $value)); $value = str_replace(array('-', '_'), ' ', $value); $value = str_replace(' ', '', ucwords($value)); } $vars['name'] = implode('/', $nameParts); return $vars; } /** * Change the default plugin location when cakephp >= 3.0 */ public function getLocations() { if ($this->matchesCakeVersion('>=', '3.0.0')) { $this->locations['plugin'] = $this->composer->getConfig()->get('vendor-dir') . '/{$vendor}/{$name}/'; } return $this->locations; } /** * Check if CakePHP version matches against a version * * @param string $matcher * @param string $version * @return bool */ protected function matchesCakeVersion($matcher, $version) { $repositoryManager = $this->composer->getRepositoryManager(); if (! $repositoryManager) { return false; } $repos = $repositoryManager->getLocalRepository(); if (!$repos) { return false; } return $repos->findPackage('cakephp/cakephp', new Constraint($matcher, $version)) !== null; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/CockpitInstaller.php 0000644 00000001231 15133551764 0026332 0 ustar 00 <?php namespace Composer\Installers; class CockpitInstaller extends BaseInstaller { protected $locations = array( 'module' => 'cockpit/modules/addons/{$name}/', ); /** * Format module name. * * Strip `module-` prefix from package name. * * {@inheritDoc} */ public function inflectPackageVars($vars) { if ($vars['type'] == 'cockpit-module') { return $this->inflectModuleVars($vars); } return $vars; } public function inflectModuleVars($vars) { $vars['name'] = ucfirst(preg_replace('/cockpit-/i', '', $vars['name'])); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/MoodleInstaller.php 0000644 00000006054 15133551764 0026165 0 ustar 00 <?php namespace Composer\Installers; class MoodleInstaller extends BaseInstaller { protected $locations = array( 'mod' => 'mod/{$name}/', 'admin_report' => 'admin/report/{$name}/', 'atto' => 'lib/editor/atto/plugins/{$name}/', 'tool' => 'admin/tool/{$name}/', 'assignment' => 'mod/assignment/type/{$name}/', 'assignsubmission' => 'mod/assign/submission/{$name}/', 'assignfeedback' => 'mod/assign/feedback/{$name}/', 'auth' => 'auth/{$name}/', 'availability' => 'availability/condition/{$name}/', 'block' => 'blocks/{$name}/', 'booktool' => 'mod/book/tool/{$name}/', 'cachestore' => 'cache/stores/{$name}/', 'cachelock' => 'cache/locks/{$name}/', 'calendartype' => 'calendar/type/{$name}/', 'fileconverter' => 'files/converter/{$name}/', 'format' => 'course/format/{$name}/', 'coursereport' => 'course/report/{$name}/', 'customcertelement' => 'mod/customcert/element/{$name}/', 'datafield' => 'mod/data/field/{$name}/', 'datapreset' => 'mod/data/preset/{$name}/', 'editor' => 'lib/editor/{$name}/', 'enrol' => 'enrol/{$name}/', 'filter' => 'filter/{$name}/', 'gradeexport' => 'grade/export/{$name}/', 'gradeimport' => 'grade/import/{$name}/', 'gradereport' => 'grade/report/{$name}/', 'gradingform' => 'grade/grading/form/{$name}/', 'local' => 'local/{$name}/', 'logstore' => 'admin/tool/log/store/{$name}/', 'ltisource' => 'mod/lti/source/{$name}/', 'ltiservice' => 'mod/lti/service/{$name}/', 'message' => 'message/output/{$name}/', 'mnetservice' => 'mnet/service/{$name}/', 'plagiarism' => 'plagiarism/{$name}/', 'portfolio' => 'portfolio/{$name}/', 'qbehaviour' => 'question/behaviour/{$name}/', 'qformat' => 'question/format/{$name}/', 'qtype' => 'question/type/{$name}/', 'quizaccess' => 'mod/quiz/accessrule/{$name}/', 'quiz' => 'mod/quiz/report/{$name}/', 'report' => 'report/{$name}/', 'repository' => 'repository/{$name}/', 'scormreport' => 'mod/scorm/report/{$name}/', 'search' => 'search/engine/{$name}/', 'theme' => 'theme/{$name}/', 'tinymce' => 'lib/editor/tinymce/plugins/{$name}/', 'profilefield' => 'user/profile/field/{$name}/', 'webservice' => 'webservice/{$name}/', 'workshopallocation' => 'mod/workshop/allocation/{$name}/', 'workshopeval' => 'mod/workshop/eval/{$name}/', 'workshopform' => 'mod/workshop/form/{$name}/' ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/MauticInstaller.php 0000644 00000002225 15133551764 0026164 0 ustar 00 <?php namespace Composer\Installers; use Composer\Package\PackageInterface; class MauticInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'plugins/{$name}/', 'theme' => 'themes/{$name}/', 'core' => 'app/', ); private function getDirectoryName() { $extra = $this->package->getExtra(); if (!empty($extra['install-directory-name'])) { return $extra['install-directory-name']; } return $this->toCamelCase($this->package->getPrettyName()); } /** * @param string $packageName * * @return string */ private function toCamelCase($packageName) { return str_replace(' ', '', ucwords(str_replace('-', ' ', basename($packageName)))); } /** * Format package name of mautic-plugins to CamelCase */ public function inflectPackageVars($vars) { if ($vars['type'] == 'mautic-plugin' || $vars['type'] == 'mautic-theme') { $directoryName = $this->getDirectoryName(); $vars['name'] = $directoryName; } return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/CroogoInstaller.php 0000644 00000000767 15133551764 0026203 0 ustar 00 <?php namespace Composer\Installers; class CroogoInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'Plugin/{$name}/', 'theme' => 'View/Themed/{$name}/', ); /** * Format package name to CamelCase */ public function inflectPackageVars($vars) { $vars['name'] = strtolower(str_replace(array('-', '_'), ' ', $vars['name'])); $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/KnownInstaller.php 0000644 00000000411 15133551764 0026031 0 ustar 00 <?php namespace Composer\Installers; class KnownInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'IdnoPlugins/{$name}/', 'theme' => 'Themes/{$name}/', 'console' => 'ConsolePlugins/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/OxidInstaller.php 0000644 00000002652 15133551764 0025651 0 ustar 00 <?php namespace Composer\Installers; use Composer\Package\PackageInterface; class OxidInstaller extends BaseInstaller { const VENDOR_PATTERN = '/^modules\/(?P<vendor>.+)\/.+/'; protected $locations = array( 'module' => 'modules/{$name}/', 'theme' => 'application/views/{$name}/', 'out' => 'out/{$name}/', ); /** * getInstallPath * * @param PackageInterface $package * @param string $frameworkType * @return string */ public function getInstallPath(PackageInterface $package, $frameworkType = '') { $installPath = parent::getInstallPath($package, $frameworkType); $type = $this->package->getType(); if ($type === 'oxid-module') { $this->prepareVendorDirectory($installPath); } return $installPath; } /** * prepareVendorDirectory * * Makes sure there is a vendormetadata.php file inside * the vendor folder if there is a vendor folder. * * @param string $installPath * @return void */ protected function prepareVendorDirectory($installPath) { $matches = ''; $hasVendorDirectory = preg_match(self::VENDOR_PATTERN, $installPath, $matches); if (!$hasVendorDirectory) { return; } $vendorDirectory = $matches['vendor']; $vendorPath = getcwd() . '/modules/' . $vendorDirectory; if (!file_exists($vendorPath)) { mkdir($vendorPath, 0755, true); } $vendorMetaDataPath = $vendorPath . '/vendormetadata.php'; touch($vendorMetaDataPath); } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/WordPressInstaller.php 0000644 00000000524 15133551764 0026672 0 ustar 00 <?php namespace Composer\Installers; class WordPressInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'wp-content/plugins/{$name}/', 'theme' => 'wp-content/themes/{$name}/', 'muplugin' => 'wp-content/mu-plugins/{$name}/', 'dropin' => 'wp-content/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/ExpressionEngineInstaller.php 0000644 00000001334 15133551764 0030227 0 ustar 00 <?php namespace Composer\Installers; use Composer\Package\PackageInterface; class ExpressionEngineInstaller extends BaseInstaller { protected $locations = array(); private $ee2Locations = array( 'addon' => 'system/expressionengine/third_party/{$name}/', 'theme' => 'themes/third_party/{$name}/', ); private $ee3Locations = array( 'addon' => 'system/user/addons/{$name}/', 'theme' => 'themes/user/{$name}/', ); public function getInstallPath(PackageInterface $package, $frameworkType = '') { $version = "{$frameworkType}Locations"; $this->locations = $this->$version; return parent::getInstallPath($package, $frameworkType); } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/HuradInstaller.php 0000644 00000001276 15133551764 0026012 0 ustar 00 <?php namespace Composer\Installers; class HuradInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'plugins/{$name}/', 'theme' => 'plugins/{$name}/', ); /** * Format package name to CamelCase */ public function inflectPackageVars($vars) { $nameParts = explode('/', $vars['name']); foreach ($nameParts as &$value) { $value = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $value)); $value = str_replace(array('-', '_'), ' ', $value); $value = str_replace(' ', '', ucwords($value)); } $vars['name'] = implode('/', $nameParts); return $vars; } } vendor/composer/installers/src/Composer/Installers/LanManagementSystemInstaller.php 0000644 00000001326 15133551764 0030600 0 ustar 00 woocommerce-blocks <?php namespace Composer\Installers; class LanManagementSystemInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'plugins/{$name}/', 'template' => 'templates/{$name}/', 'document-template' => 'documents/templates/{$name}/', 'userpanel-module' => 'userpanel/modules/{$name}/', ); /** * Format package name to CamelCase */ public function inflectPackageVars($vars) { $vars['name'] = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name'])); $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/Plugin.php 0000644 00000001214 15133551764 0024317 0 ustar 00 <?php namespace Composer\Installers; use Composer\Composer; use Composer\IO\IOInterface; use Composer\Plugin\PluginInterface; class Plugin implements PluginInterface { private $installer; public function activate(Composer $composer, IOInterface $io) { $this->installer = new Installer($io, $composer); $composer->getInstallationManager()->addInstaller($this->installer); } public function deactivate(Composer $composer, IOInterface $io) { $composer->getInstallationManager()->removeInstaller($this->installer); } public function uninstall(Composer $composer, IOInterface $io) { } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/MODULEWorkInstaller.php 0000644 00000000256 15133551764 0026574 0 ustar 00 <?php namespace Composer\Installers; class MODULEWorkInstaller extends BaseInstaller { protected $locations = array( 'module' => 'modules/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/UserFrostingInstaller.php 0000644 00000000265 15133551764 0027376 0 ustar 00 <?php namespace Composer\Installers; class UserFrostingInstaller extends BaseInstaller { protected $locations = array( 'sprinkle' => 'app/sprinkles/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/ZendInstaller.php 0000644 00000000376 15133551764 0025647 0 ustar 00 <?php namespace Composer\Installers; class ZendInstaller extends BaseInstaller { protected $locations = array( 'library' => 'library/{$name}/', 'extra' => 'extras/library/{$name}/', 'module' => 'module/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/VanillaInstaller.php 0000644 00000000325 15133551764 0026327 0 ustar 00 <?php namespace Composer\Installers; class VanillaInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'plugins/{$name}/', 'theme' => 'themes/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/KirbyInstaller.php 0000644 00000000405 15133551764 0026020 0 ustar 00 <?php namespace Composer\Installers; class KirbyInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'site/plugins/{$name}/', 'field' => 'site/fields/{$name}/', 'tag' => 'site/tags/{$name}/' ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/PiwikInstaller.php 0000644 00000001271 15133551764 0026025 0 ustar 00 <?php namespace Composer\Installers; /** * Class PiwikInstaller * * @package Composer\Installers */ class PiwikInstaller extends BaseInstaller { /** * @var array */ protected $locations = array( 'plugin' => 'plugins/{$name}/', ); /** * Format package name to CamelCase * @param array $vars * * @return array */ public function inflectPackageVars($vars) { $vars['name'] = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name'])); $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/DecibelInstaller.php 0000644 00000000272 15133551764 0026271 0 ustar 00 <?php namespace Composer\Installers; class DecibelInstaller extends BaseInstaller { /** @var array */ protected $locations = array( 'app' => 'app/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/PimcoreInstaller.php 0000644 00000001040 15133551764 0026332 0 ustar 00 <?php namespace Composer\Installers; class PimcoreInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'plugins/{$name}/', ); /** * Format package name to CamelCase */ public function inflectPackageVars($vars) { $vars['name'] = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name'])); $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/WolfCMSInstaller.php 0000644 00000000255 15133551764 0026215 0 ustar 00 <?php namespace Composer\Installers; class WolfCMSInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'wolf/plugins/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/OntoWikiInstaller.php 0000644 00000001324 15133551764 0026504 0 ustar 00 <?php namespace Composer\Installers; class OntoWikiInstaller extends BaseInstaller { protected $locations = array( 'extension' => 'extensions/{$name}/', 'theme' => 'extensions/themes/{$name}/', 'translation' => 'extensions/translations/{$name}/', ); /** * Format package name to lower case and remove ".ontowiki" suffix */ public function inflectPackageVars($vars) { $vars['name'] = strtolower($vars['name']); $vars['name'] = preg_replace('/.ontowiki$/', '', $vars['name']); $vars['name'] = preg_replace('/-theme$/', '', $vars['name']); $vars['name'] = preg_replace('/-translation$/', '', $vars['name']); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/MakoInstaller.php 0000644 00000000253 15133551764 0025630 0 ustar 00 <?php namespace Composer\Installers; class MakoInstaller extends BaseInstaller { protected $locations = array( 'package' => 'app/packages/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/CodeIgniterInstaller.php 0000644 00000000465 15133551764 0027142 0 ustar 00 <?php namespace Composer\Installers; class CodeIgniterInstaller extends BaseInstaller { protected $locations = array( 'library' => 'application/libraries/{$name}/', 'third-party' => 'application/third_party/{$name}/', 'module' => 'application/modules/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/CraftInstaller.php 0000644 00000001446 15133551764 0026005 0 ustar 00 <?php namespace Composer\Installers; /** * Installer for Craft Plugins */ class CraftInstaller extends BaseInstaller { const NAME_PREFIX = 'craft'; const NAME_SUFFIX = 'plugin'; protected $locations = array( 'plugin' => 'craft/plugins/{$name}/', ); /** * Strip `craft-` prefix and/or `-plugin` suffix from package names * * @param array $vars * * @return array */ final public function inflectPackageVars($vars) { return $this->inflectPluginVars($vars); } private function inflectPluginVars($vars) { $vars['name'] = preg_replace('/-' . self::NAME_SUFFIX . '$/i', '', $vars['name']); $vars['name'] = preg_replace('/^' . self::NAME_PREFIX . '-/i', '', $vars['name']); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/TYPO3FlowInstaller.php 0000644 00000002300 15133551764 0026442 0 ustar 00 <?php namespace Composer\Installers; /** * An installer to handle TYPO3 Flow specifics when installing packages. */ class TYPO3FlowInstaller extends BaseInstaller { protected $locations = array( 'package' => 'Packages/Application/{$name}/', 'framework' => 'Packages/Framework/{$name}/', 'plugin' => 'Packages/Plugins/{$name}/', 'site' => 'Packages/Sites/{$name}/', 'boilerplate' => 'Packages/Boilerplates/{$name}/', 'build' => 'Build/{$name}/', ); /** * Modify the package name to be a TYPO3 Flow style key. * * @param array $vars * @return array */ public function inflectPackageVars($vars) { $autoload = $this->package->getAutoload(); if (isset($autoload['psr-0']) && is_array($autoload['psr-0'])) { $namespace = key($autoload['psr-0']); $vars['name'] = str_replace('\\', '.', $namespace); } if (isset($autoload['psr-4']) && is_array($autoload['psr-4'])) { $namespace = key($autoload['psr-4']); $vars['name'] = rtrim(str_replace('\\', '.', $namespace), '.'); } return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/PortoInstaller.php 0000644 00000000260 15133551764 0026042 0 ustar 00 <?php namespace Composer\Installers; class PortoInstaller extends BaseInstaller { protected $locations = array( 'container' => 'app/Containers/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/WinterInstaller.php 0000644 00000002676 15133551764 0026224 0 ustar 00 <?php namespace Composer\Installers; class WinterInstaller extends BaseInstaller { protected $locations = array( 'module' => 'modules/{$name}/', 'plugin' => 'plugins/{$vendor}/{$name}/', 'theme' => 'themes/{$name}/' ); /** * Format package name. * * For package type winter-plugin, cut off a trailing '-plugin' if present. * * For package type winter-theme, cut off a trailing '-theme' if present. * */ public function inflectPackageVars($vars) { if ($vars['type'] === 'winter-module') { return $this->inflectModuleVars($vars); } if ($vars['type'] === 'winter-plugin') { return $this->inflectPluginVars($vars); } if ($vars['type'] === 'winter-theme') { return $this->inflectThemeVars($vars); } return $vars; } protected function inflectModuleVars($vars) { $vars['name'] = preg_replace('/^wn-|-module$/', '', $vars['name']); return $vars; } protected function inflectPluginVars($vars) { $vars['name'] = preg_replace('/^wn-|-plugin$/', '', $vars['name']); $vars['vendor'] = preg_replace('/[^a-z0-9_]/i', '', $vars['vendor']); return $vars; } protected function inflectThemeVars($vars) { $vars['name'] = preg_replace('/^wn-|-theme$/', '', $vars['name']); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/JoomlaInstaller.php 0000644 00000000640 15133551764 0026162 0 ustar 00 <?php namespace Composer\Installers; class JoomlaInstaller extends BaseInstaller { protected $locations = array( 'component' => 'components/{$name}/', 'module' => 'modules/{$name}/', 'template' => 'templates/{$name}/', 'plugin' => 'plugins/{$name}/', 'library' => 'libraries/{$name}/', ); // TODO: Add inflector for mod_ and com_ names } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/KodiCMSInstaller.php 0000644 00000000333 15133551764 0026171 0 ustar 00 <?php namespace Composer\Installers; class KodiCMSInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'cms/plugins/{$name}/', 'media' => 'cms/media/vendor/{$name}/' ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/BitrixInstaller.php 0000644 00000010251 15133551764 0026201 0 ustar 00 <?php namespace Composer\Installers; use Composer\Util\Filesystem; /** * Installer for Bitrix Framework. Supported types of extensions: * - `bitrix-d7-module` — copy the module to directory `bitrix/modules/<vendor>.<name>`. * - `bitrix-d7-component` — copy the component to directory `bitrix/components/<vendor>/<name>`. * - `bitrix-d7-template` — copy the template to directory `bitrix/templates/<vendor>_<name>`. * * You can set custom path to directory with Bitrix kernel in `composer.json`: * * ```json * { * "extra": { * "bitrix-dir": "s1/bitrix" * } * } * ``` * * @author Nik Samokhvalov <nik@samokhvalov.info> * @author Denis Kulichkin <onexhovia@gmail.com> */ class BitrixInstaller extends BaseInstaller { protected $locations = array( 'module' => '{$bitrix_dir}/modules/{$name}/', // deprecated, remove on the major release (Backward compatibility will be broken) 'component' => '{$bitrix_dir}/components/{$name}/', // deprecated, remove on the major release (Backward compatibility will be broken) 'theme' => '{$bitrix_dir}/templates/{$name}/', // deprecated, remove on the major release (Backward compatibility will be broken) 'd7-module' => '{$bitrix_dir}/modules/{$vendor}.{$name}/', 'd7-component' => '{$bitrix_dir}/components/{$vendor}/{$name}/', 'd7-template' => '{$bitrix_dir}/templates/{$vendor}_{$name}/', ); /** * @var array Storage for informations about duplicates at all the time of installation packages. */ private static $checkedDuplicates = array(); /** * {@inheritdoc} */ public function inflectPackageVars($vars) { if ($this->composer->getPackage()) { $extra = $this->composer->getPackage()->getExtra(); if (isset($extra['bitrix-dir'])) { $vars['bitrix_dir'] = $extra['bitrix-dir']; } } if (!isset($vars['bitrix_dir'])) { $vars['bitrix_dir'] = 'bitrix'; } return parent::inflectPackageVars($vars); } /** * {@inheritdoc} */ protected function templatePath($path, array $vars = array()) { $templatePath = parent::templatePath($path, $vars); $this->checkDuplicates($templatePath, $vars); return $templatePath; } /** * Duplicates search packages. * * @param string $path * @param array $vars */ protected function checkDuplicates($path, array $vars = array()) { $packageType = substr($vars['type'], strlen('bitrix') + 1); $localDir = explode('/', $vars['bitrix_dir']); array_pop($localDir); $localDir[] = 'local'; $localDir = implode('/', $localDir); $oldPath = str_replace( array('{$bitrix_dir}', '{$name}'), array($localDir, $vars['name']), $this->locations[$packageType] ); if (in_array($oldPath, static::$checkedDuplicates)) { return; } if ($oldPath !== $path && file_exists($oldPath) && $this->io && $this->io->isInteractive()) { $this->io->writeError(' <error>Duplication of packages:</error>'); $this->io->writeError(' <info>Package ' . $oldPath . ' will be called instead package ' . $path . '</info>'); while (true) { switch ($this->io->ask(' <info>Delete ' . $oldPath . ' [y,n,?]?</info> ', '?')) { case 'y': $fs = new Filesystem(); $fs->removeDirectory($oldPath); break 2; case 'n': break 2; case '?': default: $this->io->writeError(array( ' y - delete package ' . $oldPath . ' and to continue with the installation', ' n - don\'t delete and to continue with the installation', )); $this->io->writeError(' ? - print help'); break; } } } static::$checkedDuplicates[] = $oldPath; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/PPIInstaller.php 0000644 00000000244 15133551764 0025371 0 ustar 00 <?php namespace Composer\Installers; class PPIInstaller extends BaseInstaller { protected $locations = array( 'module' => 'modules/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/SyDESInstaller.php 0000644 00000002264 15133551764 0025674 0 ustar 00 <?php namespace Composer\Installers; class SyDESInstaller extends BaseInstaller { protected $locations = array( 'module' => 'app/modules/{$name}/', 'theme' => 'themes/{$name}/', ); /** * Format module name. * * Strip `sydes-` prefix and a trailing '-theme' or '-module' from package name if present. * * {@inerhitDoc} */ public function inflectPackageVars($vars) { if ($vars['type'] == 'sydes-module') { return $this->inflectModuleVars($vars); } if ($vars['type'] === 'sydes-theme') { return $this->inflectThemeVars($vars); } return $vars; } public function inflectModuleVars($vars) { $vars['name'] = preg_replace('/(^sydes-|-module$)/i', '', $vars['name']); $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); return $vars; } protected function inflectThemeVars($vars) { $vars['name'] = preg_replace('/(^sydes-|-theme$)/', '', $vars['name']); $vars['name'] = strtolower($vars['name']); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/PhpBBInstaller.php 0000644 00000000405 15133551764 0025673 0 ustar 00 <?php namespace Composer\Installers; class PhpBBInstaller extends BaseInstaller { protected $locations = array( 'extension' => 'ext/{$vendor}/{$name}/', 'language' => 'language/{$name}/', 'style' => 'styles/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/SMFInstaller.php 0000644 00000000312 15133551764 0025362 0 ustar 00 <?php namespace Composer\Installers; class SMFInstaller extends BaseInstaller { protected $locations = array( 'module' => 'Sources/{$name}/', 'theme' => 'Themes/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/Installer.php 0000644 00000024313 15133551764 0025023 0 ustar 00 <?php namespace Composer\Installers; use Composer\Composer; use Composer\Installer\BinaryInstaller; use Composer\Installer\LibraryInstaller; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use Composer\Repository\InstalledRepositoryInterface; use Composer\Util\Filesystem; use React\Promise\PromiseInterface; class Installer extends LibraryInstaller { /** * Package types to installer class map * * @var array */ private $supportedTypes = array( 'aimeos' => 'AimeosInstaller', 'asgard' => 'AsgardInstaller', 'attogram' => 'AttogramInstaller', 'agl' => 'AglInstaller', 'annotatecms' => 'AnnotateCmsInstaller', 'bitrix' => 'BitrixInstaller', 'bonefish' => 'BonefishInstaller', 'cakephp' => 'CakePHPInstaller', 'chef' => 'ChefInstaller', 'civicrm' => 'CiviCrmInstaller', 'ccframework' => 'ClanCatsFrameworkInstaller', 'cockpit' => 'CockpitInstaller', 'codeigniter' => 'CodeIgniterInstaller', 'concrete5' => 'Concrete5Installer', 'craft' => 'CraftInstaller', 'croogo' => 'CroogoInstaller', 'dframe' => 'DframeInstaller', 'dokuwiki' => 'DokuWikiInstaller', 'dolibarr' => 'DolibarrInstaller', 'decibel' => 'DecibelInstaller', 'drupal' => 'DrupalInstaller', 'elgg' => 'ElggInstaller', 'eliasis' => 'EliasisInstaller', 'ee3' => 'ExpressionEngineInstaller', 'ee2' => 'ExpressionEngineInstaller', 'ezplatform' => 'EzPlatformInstaller', 'fuel' => 'FuelInstaller', 'fuelphp' => 'FuelphpInstaller', 'grav' => 'GravInstaller', 'hurad' => 'HuradInstaller', 'tastyigniter' => 'TastyIgniterInstaller', 'imagecms' => 'ImageCMSInstaller', 'itop' => 'ItopInstaller', 'joomla' => 'JoomlaInstaller', 'kanboard' => 'KanboardInstaller', 'kirby' => 'KirbyInstaller', 'known' => 'KnownInstaller', 'kodicms' => 'KodiCMSInstaller', 'kohana' => 'KohanaInstaller', 'lms' => 'LanManagementSystemInstaller', 'laravel' => 'LaravelInstaller', 'lavalite' => 'LavaLiteInstaller', 'lithium' => 'LithiumInstaller', 'magento' => 'MagentoInstaller', 'majima' => 'MajimaInstaller', 'mantisbt' => 'MantisBTInstaller', 'mako' => 'MakoInstaller', 'maya' => 'MayaInstaller', 'mautic' => 'MauticInstaller', 'mediawiki' => 'MediaWikiInstaller', 'miaoxing' => 'MiaoxingInstaller', 'microweber' => 'MicroweberInstaller', 'modulework' => 'MODULEWorkInstaller', 'modx' => 'ModxInstaller', 'modxevo' => 'MODXEvoInstaller', 'moodle' => 'MoodleInstaller', 'october' => 'OctoberInstaller', 'ontowiki' => 'OntoWikiInstaller', 'oxid' => 'OxidInstaller', 'osclass' => 'OsclassInstaller', 'pxcms' => 'PxcmsInstaller', 'phpbb' => 'PhpBBInstaller', 'pimcore' => 'PimcoreInstaller', 'piwik' => 'PiwikInstaller', 'plentymarkets'=> 'PlentymarketsInstaller', 'ppi' => 'PPIInstaller', 'puppet' => 'PuppetInstaller', 'radphp' => 'RadPHPInstaller', 'phifty' => 'PhiftyInstaller', 'porto' => 'PortoInstaller', 'processwire' => 'ProcessWireInstaller', 'redaxo' => 'RedaxoInstaller', 'redaxo5' => 'Redaxo5Installer', 'reindex' => 'ReIndexInstaller', 'roundcube' => 'RoundcubeInstaller', 'shopware' => 'ShopwareInstaller', 'sitedirect' => 'SiteDirectInstaller', 'silverstripe' => 'SilverStripeInstaller', 'smf' => 'SMFInstaller', 'starbug' => 'StarbugInstaller', 'sydes' => 'SyDESInstaller', 'sylius' => 'SyliusInstaller', 'symfony1' => 'Symfony1Installer', 'tao' => 'TaoInstaller', 'thelia' => 'TheliaInstaller', 'tusk' => 'TuskInstaller', 'typo3-cms' => 'TYPO3CmsInstaller', 'typo3-flow' => 'TYPO3FlowInstaller', 'userfrosting' => 'UserFrostingInstaller', 'vanilla' => 'VanillaInstaller', 'whmcs' => 'WHMCSInstaller', 'winter' => 'WinterInstaller', 'wolfcms' => 'WolfCMSInstaller', 'wordpress' => 'WordPressInstaller', 'yawik' => 'YawikInstaller', 'zend' => 'ZendInstaller', 'zikula' => 'ZikulaInstaller', 'prestashop' => 'PrestashopInstaller' ); /** * Installer constructor. * * Disables installers specified in main composer extra installer-disable * list * * @param IOInterface $io * @param Composer $composer * @param string $type * @param Filesystem|null $filesystem * @param BinaryInstaller|null $binaryInstaller */ public function __construct( IOInterface $io, Composer $composer, $type = 'library', Filesystem $filesystem = null, BinaryInstaller $binaryInstaller = null ) { parent::__construct($io, $composer, $type, $filesystem, $binaryInstaller); $this->removeDisabledInstallers(); } /** * {@inheritDoc} */ public function getInstallPath(PackageInterface $package) { $type = $package->getType(); $frameworkType = $this->findFrameworkType($type); if ($frameworkType === false) { throw new \InvalidArgumentException( 'Sorry the package type of this package is not yet supported.' ); } $class = 'Composer\\Installers\\' . $this->supportedTypes[$frameworkType]; $installer = new $class($package, $this->composer, $this->getIO()); return $installer->getInstallPath($package, $frameworkType); } public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { $installPath = $this->getPackageBasePath($package); $io = $this->io; $outputStatus = function () use ($io, $installPath) { $io->write(sprintf('Deleting %s - %s', $installPath, !file_exists($installPath) ? '<comment>deleted</comment>' : '<error>not deleted</error>')); }; $promise = parent::uninstall($repo, $package); // Composer v2 might return a promise here if ($promise instanceof PromiseInterface) { return $promise->then($outputStatus); } // If not, execute the code right away as parent::uninstall executed synchronously (composer v1, or v2 without async) $outputStatus(); return null; } /** * {@inheritDoc} */ public function supports($packageType) { $frameworkType = $this->findFrameworkType($packageType); if ($frameworkType === false) { return false; } $locationPattern = $this->getLocationPattern($frameworkType); return preg_match('#' . $frameworkType . '-' . $locationPattern . '#', $packageType, $matches) === 1; } /** * Finds a supported framework type if it exists and returns it * * @param string $type * @return string|false */ protected function findFrameworkType($type) { krsort($this->supportedTypes); foreach ($this->supportedTypes as $key => $val) { if ($key === substr($type, 0, strlen($key))) { return substr($type, 0, strlen($key)); } } return false; } /** * Get the second part of the regular expression to check for support of a * package type * * @param string $frameworkType * @return string */ protected function getLocationPattern($frameworkType) { $pattern = false; if (!empty($this->supportedTypes[$frameworkType])) { $frameworkClass = 'Composer\\Installers\\' . $this->supportedTypes[$frameworkType]; /** @var BaseInstaller $framework */ $framework = new $frameworkClass(null, $this->composer, $this->getIO()); $locations = array_keys($framework->getLocations()); $pattern = $locations ? '(' . implode('|', $locations) . ')' : false; } return $pattern ? : '(\w+)'; } /** * Get I/O object * * @return IOInterface */ private function getIO() { return $this->io; } /** * Look for installers set to be disabled in composer's extra config and * remove them from the list of supported installers. * * Globals: * - true, "all", and "*" - disable all installers. * - false - enable all installers (useful with * wikimedia/composer-merge-plugin or similar) * * @return void */ protected function removeDisabledInstallers() { $extra = $this->composer->getPackage()->getExtra(); if (!isset($extra['installer-disable']) || $extra['installer-disable'] === false) { // No installers are disabled return; } // Get installers to disable $disable = $extra['installer-disable']; // Ensure $disabled is an array if (!is_array($disable)) { $disable = array($disable); } // Check which installers should be disabled $all = array(true, "all", "*"); $intersect = array_intersect($all, $disable); if (!empty($intersect)) { // Disable all installers $this->supportedTypes = array(); } else { // Disable specified installers foreach ($disable as $key => $installer) { if (is_string($installer) && key_exists($installer, $this->supportedTypes)) { unset($this->supportedTypes[$installer]); } } } } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/SyliusInstaller.php 0000644 00000000245 15133551764 0026232 0 ustar 00 <?php namespace Composer\Installers; class SyliusInstaller extends BaseInstaller { protected $locations = array( 'theme' => 'themes/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/BonefishInstaller.php 0000644 00000000267 15133551764 0026503 0 ustar 00 <?php namespace Composer\Installers; class BonefishInstaller extends BaseInstaller { protected $locations = array( 'package' => 'Packages/{$vendor}/{$name}/' ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/VgmcpInstaller.php 0000644 00000002457 15133551764 0026025 0 ustar 00 <?php namespace Composer\Installers; class VgmcpInstaller extends BaseInstaller { protected $locations = array( 'bundle' => 'src/{$vendor}/{$name}/', 'theme' => 'themes/{$name}/' ); /** * Format package name. * * For package type vgmcp-bundle, cut off a trailing '-bundle' if present. * * For package type vgmcp-theme, cut off a trailing '-theme' if present. * */ public function inflectPackageVars($vars) { if ($vars['type'] === 'vgmcp-bundle') { return $this->inflectPluginVars($vars); } if ($vars['type'] === 'vgmcp-theme') { return $this->inflectThemeVars($vars); } return $vars; } protected function inflectPluginVars($vars) { $vars['name'] = preg_replace('/-bundle$/', '', $vars['name']); $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); return $vars; } protected function inflectThemeVars($vars) { $vars['name'] = preg_replace('/-theme$/', '', $vars['name']); $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/TaoInstaller.php 0000644 00000001423 15133551764 0025464 0 ustar 00 <?php namespace Composer\Installers; /** * An installer to handle TAO extensions. */ class TaoInstaller extends BaseInstaller { const EXTRA_TAO_EXTENSION_NAME = 'tao-extension-name'; protected $locations = array( 'extension' => '{$name}' ); public function inflectPackageVars($vars) { $extra = $this->package->getExtra(); if (array_key_exists(self::EXTRA_TAO_EXTENSION_NAME, $extra)) { $vars['name'] = $extra[self::EXTRA_TAO_EXTENSION_NAME]; return $vars; } $vars['name'] = str_replace('extension-', '', $vars['name']); $vars['name'] = str_replace('-', ' ', $vars['name']); $vars['name'] = lcfirst(str_replace(' ', '', ucwords($vars['name']))); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/FuelphpInstaller.php 0000644 00000000257 15133551764 0026350 0 ustar 00 <?php namespace Composer\Installers; class FuelphpInstaller extends BaseInstaller { protected $locations = array( 'component' => 'components/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/ReIndexInstaller.php 0000644 00000000324 15133551764 0026276 0 ustar 00 <?php namespace Composer\Installers; class ReIndexInstaller extends BaseInstaller { protected $locations = array( 'theme' => 'themes/{$name}/', 'plugin' => 'plugins/{$name}/' ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/ZikulaInstaller.php 0000644 00000000341 15133551764 0026176 0 ustar 00 <?php namespace Composer\Installers; class ZikulaInstaller extends BaseInstaller { protected $locations = array( 'module' => 'modules/{$vendor}-{$name}/', 'theme' => 'themes/{$vendor}-{$name}/' ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/YawikInstaller.php 0000644 00000001246 15133551764 0026030 0 ustar 00 <?php /** * Created by PhpStorm. * User: cbleek * Date: 25.03.16 * Time: 20:55 */ namespace Composer\Installers; class YawikInstaller extends BaseInstaller { protected $locations = array( 'module' => 'module/{$name}/', ); /** * Format package name to CamelCase * @param array $vars * * @return array */ public function inflectPackageVars($vars) { $vars['name'] = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name'])); $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/TYPO3CmsInstaller.php 0000644 00000000575 15133551764 0026271 0 ustar 00 <?php namespace Composer\Installers; /** * Extension installer for TYPO3 CMS * * @deprecated since 1.0.25, use https://packagist.org/packages/typo3/cms-composer-installers instead * * @author Sascha Egerer <sascha.egerer@dkd.de> */ class TYPO3CmsInstaller extends BaseInstaller { protected $locations = array( 'extension' => 'typo3conf/ext/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/Concrete5Installer.php 0000644 00000000556 15133551764 0026576 0 ustar 00 <?php namespace Composer\Installers; class Concrete5Installer extends BaseInstaller { protected $locations = array( 'core' => 'concrete/', 'block' => 'application/blocks/{$name}/', 'package' => 'packages/{$name}/', 'theme' => 'application/themes/{$name}/', 'update' => 'updates/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/TastyIgniterInstaller.php 0000644 00000001553 15133551764 0027373 0 ustar 00 <?php namespace Composer\Installers; class TastyIgniterInstaller extends BaseInstaller { protected $locations = array( 'extension' => 'extensions/{$vendor}/{$name}/', 'theme' => 'themes/{$name}/', ); /** * Format package name. * * Cut off leading 'ti-ext-' or 'ti-theme-' if present. * Strip vendor name of characters that is not alphanumeric or an underscore * */ public function inflectPackageVars($vars) { if ($vars['type'] === 'tastyigniter-extension') { $vars['vendor'] = preg_replace('/[^a-z0-9_]/i', '', $vars['vendor']); $vars['name'] = preg_replace('/^ti-ext-/', '', $vars['name']); } if ($vars['type'] === 'tastyigniter-theme') { $vars['name'] = preg_replace('/^ti-theme-/', '', $vars['name']); } return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/FuelInstaller.php 0000644 00000000417 15133551764 0025636 0 ustar 00 <?php namespace Composer\Installers; class FuelInstaller extends BaseInstaller { protected $locations = array( 'module' => 'fuel/app/modules/{$name}/', 'package' => 'fuel/packages/{$name}/', 'theme' => 'fuel/app/themes/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/DframeInstaller.php 0000644 00000000263 15133551764 0026140 0 ustar 00 <?php namespace Composer\Installers; class DframeInstaller extends BaseInstaller { protected $locations = array( 'module' => 'modules/{$vendor}/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/ProcessWireInstaller.php 0000644 00000001053 15133551764 0027205 0 ustar 00 <?php namespace Composer\Installers; class ProcessWireInstaller extends BaseInstaller { protected $locations = array( 'module' => 'site/modules/{$name}/', ); /** * Format package name to CamelCase */ public function inflectPackageVars($vars) { $vars['name'] = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name'])); $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/DokuWikiInstaller.php 0000644 00000002354 15133551764 0026473 0 ustar 00 <?php namespace Composer\Installers; class DokuWikiInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'lib/plugins/{$name}/', 'template' => 'lib/tpl/{$name}/', ); /** * Format package name. * * For package type dokuwiki-plugin, cut off a trailing '-plugin', * or leading dokuwiki_ if present. * * For package type dokuwiki-template, cut off a trailing '-template' if present. * */ public function inflectPackageVars($vars) { if ($vars['type'] === 'dokuwiki-plugin') { return $this->inflectPluginVars($vars); } if ($vars['type'] === 'dokuwiki-template') { return $this->inflectTemplateVars($vars); } return $vars; } protected function inflectPluginVars($vars) { $vars['name'] = preg_replace('/-plugin$/', '', $vars['name']); $vars['name'] = preg_replace('/^dokuwiki_?-?/', '', $vars['name']); return $vars; } protected function inflectTemplateVars($vars) { $vars['name'] = preg_replace('/-template$/', '', $vars['name']); $vars['name'] = preg_replace('/^dokuwiki_?-?/', '', $vars['name']); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/AsgardInstaller.php 0000644 00000002456 15133551764 0026151 0 ustar 00 <?php namespace Composer\Installers; class AsgardInstaller extends BaseInstaller { protected $locations = array( 'module' => 'Modules/{$name}/', 'theme' => 'Themes/{$name}/' ); /** * Format package name. * * For package type asgard-module, cut off a trailing '-plugin' if present. * * For package type asgard-theme, cut off a trailing '-theme' if present. * */ public function inflectPackageVars($vars) { if ($vars['type'] === 'asgard-module') { return $this->inflectPluginVars($vars); } if ($vars['type'] === 'asgard-theme') { return $this->inflectThemeVars($vars); } return $vars; } protected function inflectPluginVars($vars) { $vars['name'] = preg_replace('/-module$/', '', $vars['name']); $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); return $vars; } protected function inflectThemeVars($vars) { $vars['name'] = preg_replace('/-theme$/', '', $vars['name']); $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/Symfony1Installer.php 0000644 00000001066 15133551764 0026471 0 ustar 00 <?php namespace Composer\Installers; /** * Plugin installer for symfony 1.x * * @author Jérôme Tamarelle <jerome@tamarelle.net> */ class Symfony1Installer extends BaseInstaller { protected $locations = array( 'plugin' => 'plugins/{$name}/', ); /** * Format package name to CamelCase */ public function inflectPackageVars($vars) { $vars['name'] = preg_replace_callback('/(-[a-z])/', function ($matches) { return strtoupper($matches[0][1]); }, $vars['name']); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/AglInstaller.php 0000644 00000000711 15133551764 0025443 0 ustar 00 <?php namespace Composer\Installers; class AglInstaller extends BaseInstaller { protected $locations = array( 'module' => 'More/{$name}/', ); /** * Format package name to CamelCase */ public function inflectPackageVars($vars) { $vars['name'] = preg_replace_callback('/(?:^|_|-)(.?)/', function ($matches) { return strtoupper($matches[1]); }, $vars['name']); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/DolibarrInstaller.php 0000644 00000000542 15133551764 0026500 0 ustar 00 <?php namespace Composer\Installers; /** * Class DolibarrInstaller * * @package Composer\Installers * @author Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr> */ class DolibarrInstaller extends BaseInstaller { //TODO: Add support for scripts and themes protected $locations = array( 'module' => 'htdocs/custom/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/TheliaInstaller.php 0000644 00000000604 15133551764 0026147 0 ustar 00 <?php namespace Composer\Installers; class TheliaInstaller extends BaseInstaller { protected $locations = array( 'module' => 'local/modules/{$name}/', 'frontoffice-template' => 'templates/frontOffice/{$name}/', 'backoffice-template' => 'templates/backOffice/{$name}/', 'email-template' => 'templates/email/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/OctoberInstaller.php 0000644 00000002377 15133551764 0026347 0 ustar 00 <?php namespace Composer\Installers; class OctoberInstaller extends BaseInstaller { protected $locations = array( 'module' => 'modules/{$name}/', 'plugin' => 'plugins/{$vendor}/{$name}/', 'theme' => 'themes/{$vendor}-{$name}/' ); /** * Format package name. * * For package type october-plugin, cut off a trailing '-plugin' if present. * * For package type october-theme, cut off a trailing '-theme' if present. * */ public function inflectPackageVars($vars) { if ($vars['type'] === 'october-plugin') { return $this->inflectPluginVars($vars); } if ($vars['type'] === 'october-theme') { return $this->inflectThemeVars($vars); } return $vars; } protected function inflectPluginVars($vars) { $vars['name'] = preg_replace('/^oc-|-plugin$/', '', $vars['name']); $vars['vendor'] = preg_replace('/[^a-z0-9_]/i', '', $vars['vendor']); return $vars; } protected function inflectThemeVars($vars) { $vars['name'] = preg_replace('/^oc-|-theme$/', '', $vars['name']); $vars['vendor'] = preg_replace('/[^a-z0-9_]/i', '', $vars['vendor']); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/StarbugInstaller.php 0000644 00000000461 15133551764 0026351 0 ustar 00 <?php namespace Composer\Installers; class StarbugInstaller extends BaseInstaller { protected $locations = array( 'module' => 'modules/{$name}/', 'theme' => 'themes/{$name}/', 'custom-module' => 'app/modules/{$name}/', 'custom-theme' => 'app/themes/{$name}/' ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/MODXEvoInstaller.php 0000644 00000000741 15133551764 0026164 0 ustar 00 <?php namespace Composer\Installers; /** * An installer to handle MODX Evolution specifics when installing packages. */ class MODXEvoInstaller extends BaseInstaller { protected $locations = array( 'snippet' => 'assets/snippets/{$name}/', 'plugin' => 'assets/plugins/{$name}/', 'module' => 'assets/modules/{$name}/', 'template' => 'assets/templates/{$name}/', 'lib' => 'assets/lib/{$name}/' ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/MagentoInstaller.php 0000644 00000000421 15133551764 0026330 0 ustar 00 <?php namespace Composer\Installers; class MagentoInstaller extends BaseInstaller { protected $locations = array( 'theme' => 'app/design/frontend/{$name}/', 'skin' => 'skin/frontend/default/{$name}/', 'library' => 'lib/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/EzPlatformInstaller.php 0000644 00000000354 15133551764 0027026 0 ustar 00 <?php namespace Composer\Installers; class EzPlatformInstaller extends BaseInstaller { protected $locations = array( 'meta-assets' => 'web/assets/ezplatform/', 'assets' => 'web/assets/ezplatform/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/EliasisInstaller.php 0000644 00000000461 15133551764 0026333 0 ustar 00 <?php namespace Composer\Installers; class EliasisInstaller extends BaseInstaller { protected $locations = array( 'component' => 'components/{$name}/', 'module' => 'modules/{$name}/', 'plugin' => 'plugins/{$name}/', 'template' => 'templates/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/PxcmsInstaller.php 0000644 00000003734 15133551764 0026042 0 ustar 00 <?php namespace Composer\Installers; class PxcmsInstaller extends BaseInstaller { protected $locations = array( 'module' => 'app/Modules/{$name}/', 'theme' => 'themes/{$name}/', ); /** * Format package name. * * @param array $vars * * @return array */ public function inflectPackageVars($vars) { if ($vars['type'] === 'pxcms-module') { return $this->inflectModuleVars($vars); } if ($vars['type'] === 'pxcms-theme') { return $this->inflectThemeVars($vars); } return $vars; } /** * For package type pxcms-module, cut off a trailing '-plugin' if present. * * return string */ protected function inflectModuleVars($vars) { $vars['name'] = str_replace('pxcms-', '', $vars['name']); // strip out pxcms- just incase (legacy) $vars['name'] = str_replace('module-', '', $vars['name']); // strip out module- $vars['name'] = preg_replace('/-module$/', '', $vars['name']); // strip out -module $vars['name'] = str_replace('-', '_', $vars['name']); // make -'s be _'s $vars['name'] = ucwords($vars['name']); // make module name camelcased return $vars; } /** * For package type pxcms-module, cut off a trailing '-plugin' if present. * * return string */ protected function inflectThemeVars($vars) { $vars['name'] = str_replace('pxcms-', '', $vars['name']); // strip out pxcms- just incase (legacy) $vars['name'] = str_replace('theme-', '', $vars['name']); // strip out theme- $vars['name'] = preg_replace('/-theme$/', '', $vars['name']); // strip out -theme $vars['name'] = str_replace('-', '_', $vars['name']); // make -'s be _'s $vars['name'] = ucwords($vars['name']); // make module name camelcased return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/TuskInstaller.php 0000644 00000000640 15133551764 0025667 0 ustar 00 <?php namespace Composer\Installers; /** * Composer installer for 3rd party Tusk utilities * @author Drew Ewing <drew@phenocode.com> */ class TuskInstaller extends BaseInstaller { protected $locations = array( 'task' => '.tusk/tasks/{$name}/', 'command' => '.tusk/commands/{$name}/', 'asset' => 'assets/tusk/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/PhiftyInstaller.php 0000644 00000000400 15133551764 0026176 0 ustar 00 <?php namespace Composer\Installers; class PhiftyInstaller extends BaseInstaller { protected $locations = array( 'bundle' => 'bundles/{$name}/', 'library' => 'libraries/{$name}/', 'framework' => 'frameworks/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/ModxInstaller.php 0000644 00000000364 15133551764 0025653 0 ustar 00 <?php namespace Composer\Installers; /** * An installer to handle MODX specifics when installing packages. */ class ModxInstaller extends BaseInstaller { protected $locations = array( 'extra' => 'core/packages/{$name}/' ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/SilverStripeInstaller.php 0000644 00000002127 15133551764 0027376 0 ustar 00 <?php namespace Composer\Installers; use Composer\Package\PackageInterface; class SilverStripeInstaller extends BaseInstaller { protected $locations = array( 'module' => '{$name}/', 'theme' => 'themes/{$name}/', ); /** * Return the install path based on package type. * * Relies on built-in BaseInstaller behaviour with one exception: silverstripe/framework * must be installed to 'sapphire' and not 'framework' if the version is <3.0.0 * * @param PackageInterface $package * @param string $frameworkType * @return string */ public function getInstallPath(PackageInterface $package, $frameworkType = '') { if ( $package->getName() == 'silverstripe/framework' && preg_match('/^\d+\.\d+\.\d+/', $package->getVersion()) && version_compare($package->getVersion(), '2.999.999') < 0 ) { return $this->templatePath($this->locations['module'], array('name' => 'sapphire')); } return parent::getInstallPath($package, $frameworkType); } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/DrupalInstaller.php 0000644 00000001543 15133551764 0026173 0 ustar 00 <?php namespace Composer\Installers; class DrupalInstaller extends BaseInstaller { protected $locations = array( 'core' => 'core/', 'module' => 'modules/{$name}/', 'theme' => 'themes/{$name}/', 'library' => 'libraries/{$name}/', 'profile' => 'profiles/{$name}/', 'database-driver' => 'drivers/lib/Drupal/Driver/Database/{$name}/', 'drush' => 'drush/{$name}/', 'custom-theme' => 'themes/custom/{$name}/', 'custom-module' => 'modules/custom/{$name}/', 'custom-profile' => 'profiles/custom/{$name}/', 'drupal-multisite' => 'sites/{$name}/', 'console' => 'console/{$name}/', 'console-language' => 'console/language/{$name}/', 'config' => 'config/sync/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/AttogramInstaller.php 0000644 00000000251 15133551764 0026515 0 ustar 00 <?php namespace Composer\Installers; class AttogramInstaller extends BaseInstaller { protected $locations = array( 'module' => 'modules/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/PrestashopInstaller.php 0000644 00000000322 15133551764 0027066 0 ustar 00 <?php namespace Composer\Installers; class PrestashopInstaller extends BaseInstaller { protected $locations = array( 'module' => 'modules/{$name}/', 'theme' => 'themes/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/ShopwareInstaller.php 0000644 00000003156 15133551764 0026536 0 ustar 00 <?php namespace Composer\Installers; /** * Plugin/theme installer for shopware * @author Benjamin Boit */ class ShopwareInstaller extends BaseInstaller { protected $locations = array( 'backend-plugin' => 'engine/Shopware/Plugins/Local/Backend/{$name}/', 'core-plugin' => 'engine/Shopware/Plugins/Local/Core/{$name}/', 'frontend-plugin' => 'engine/Shopware/Plugins/Local/Frontend/{$name}/', 'theme' => 'templates/{$name}/', 'plugin' => 'custom/plugins/{$name}/', 'frontend-theme' => 'themes/Frontend/{$name}/', ); /** * Transforms the names * @param array $vars * @return array */ public function inflectPackageVars($vars) { if ($vars['type'] === 'shopware-theme') { return $this->correctThemeName($vars); } return $this->correctPluginName($vars); } /** * Changes the name to a camelcased combination of vendor and name * @param array $vars * @return array */ private function correctPluginName($vars) { $camelCasedName = preg_replace_callback('/(-[a-z])/', function ($matches) { return strtoupper($matches[0][1]); }, $vars['name']); $vars['name'] = ucfirst($vars['vendor']) . ucfirst($camelCasedName); return $vars; } /** * Changes the name to a underscore separated name * @param array $vars * @return array */ private function correctThemeName($vars) { $vars['name'] = str_replace('-', '_', $vars['name']); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/SiteDirectInstaller.php 0000644 00000001216 15133551764 0027000 0 ustar 00 <?php namespace Composer\Installers; class SiteDirectInstaller extends BaseInstaller { protected $locations = array( 'module' => 'modules/{$vendor}/{$name}/', 'plugin' => 'plugins/{$vendor}/{$name}/' ); public function inflectPackageVars($vars) { return $this->parseVars($vars); } protected function parseVars($vars) { $vars['vendor'] = strtolower($vars['vendor']) == 'sitedirect' ? 'SiteDirect' : $vars['vendor']; $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/RadPHPInstaller.php 0000644 00000001223 15133551764 0026015 0 ustar 00 <?php namespace Composer\Installers; class RadPHPInstaller extends BaseInstaller { protected $locations = array( 'bundle' => 'src/{$name}/' ); /** * Format package name to CamelCase */ public function inflectPackageVars($vars) { $nameParts = explode('/', $vars['name']); foreach ($nameParts as &$value) { $value = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $value)); $value = str_replace(array('-', '_'), ' ', $value); $value = str_replace(' ', '', ucwords($value)); } $vars['name'] = implode('/', $nameParts); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/MayaInstaller.php 0000644 00000001427 15133551764 0025634 0 ustar 00 <?php namespace Composer\Installers; class MayaInstaller extends BaseInstaller { protected $locations = array( 'module' => 'modules/{$name}/', ); /** * Format package name. * * For package type maya-module, cut off a trailing '-module' if present. * */ public function inflectPackageVars($vars) { if ($vars['type'] === 'maya-module') { return $this->inflectModuleVars($vars); } return $vars; } protected function inflectModuleVars($vars) { $vars['name'] = preg_replace('/-module$/', '', $vars['name']); $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/Redaxo5Installer.php 0000644 00000000404 15133551764 0026246 0 ustar 00 <?php namespace Composer\Installers; class Redaxo5Installer extends BaseInstaller { protected $locations = array( 'addon' => 'redaxo/src/addons/{$name}/', 'bestyle-plugin' => 'redaxo/src/addons/be_style/plugins/{$name}/' ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/ChefInstaller.php 0000644 00000000336 15133551764 0025610 0 ustar 00 <?php namespace Composer\Installers; class ChefInstaller extends BaseInstaller { protected $locations = array( 'cookbook' => 'Chef/{$vendor}/{$name}/', 'role' => 'Chef/roles/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/ClanCatsFrameworkInstaller.php0000644 00000000326 15133551764 0030310 0 ustar 00 <?php namespace Composer\Installers; class ClanCatsFrameworkInstaller extends BaseInstaller { protected $locations = array( 'ship' => 'CCF/orbit/{$name}/', 'theme' => 'CCF/app/themes/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/MantisBTInstaller.php 0000644 00000001110 15133551764 0026413 0 ustar 00 <?php namespace Composer\Installers; use Composer\DependencyResolver\Pool; class MantisBTInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'plugins/{$name}/', ); /** * Format package name to CamelCase */ public function inflectPackageVars($vars) { $vars['name'] = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $vars['name'])); $vars['name'] = str_replace(array('-', '_'), ' ', $vars['name']); $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/AimeosInstaller.php 0000644 00000000250 15133551764 0026153 0 ustar 00 <?php namespace Composer\Installers; class AimeosInstaller extends BaseInstaller { protected $locations = array( 'extension' => 'ext/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/LaravelInstaller.php 0000644 00000000253 15133551764 0026327 0 ustar 00 <?php namespace Composer\Installers; class LaravelInstaller extends BaseInstaller { protected $locations = array( 'library' => 'libraries/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/LavaLiteInstaller.php 0000644 00000000344 15133551764 0026443 0 ustar 00 <?php namespace Composer\Installers; class LavaLiteInstaller extends BaseInstaller { protected $locations = array( 'package' => 'packages/{$vendor}/{$name}/', 'theme' => 'public/themes/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/PlentymarketsInstaller.php 0000644 00000001311 15133551764 0027577 0 ustar 00 <?php namespace Composer\Installers; class PlentymarketsInstaller extends BaseInstaller { protected $locations = array( 'plugin' => '{$name}/' ); /** * Remove hyphen, "plugin" and format to camelcase * @param array $vars * * @return array */ public function inflectPackageVars($vars) { $vars['name'] = explode("-", $vars['name']); foreach ($vars['name'] as $key => $name) { $vars['name'][$key] = ucfirst($vars['name'][$key]); if (strcasecmp($name, "Plugin") == 0) { unset($vars['name'][$key]); } } $vars['name'] = implode("",$vars['name']); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/ElggInstaller.php 0000644 00000000241 15133551764 0025614 0 ustar 00 <?php namespace Composer\Installers; class ElggInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'mod/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/KohanaInstaller.php 0000644 00000000247 15133551764 0026145 0 ustar 00 <?php namespace Composer\Installers; class KohanaInstaller extends BaseInstaller { protected $locations = array( 'module' => 'modules/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/PuppetInstaller.php 0000644 00000000251 15133551764 0026214 0 ustar 00 <?php namespace Composer\Installers; class PuppetInstaller extends BaseInstaller { protected $locations = array( 'module' => 'modules/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/AnnotateCmsInstaller.php 0000644 00000000436 15133551764 0027160 0 ustar 00 <?php namespace Composer\Installers; class AnnotateCmsInstaller extends BaseInstaller { protected $locations = array( 'module' => 'addons/modules/{$name}/', 'component' => 'addons/components/{$name}/', 'service' => 'addons/services/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/MicroweberInstaller.php 0000644 00000010340 15133551764 0027035 0 ustar 00 <?php namespace Composer\Installers; class MicroweberInstaller extends BaseInstaller { protected $locations = array( 'module' => 'userfiles/modules/{$install_item_dir}/', 'module-skin' => 'userfiles/modules/{$install_item_dir}/templates/', 'template' => 'userfiles/templates/{$install_item_dir}/', 'element' => 'userfiles/elements/{$install_item_dir}/', 'vendor' => 'vendor/{$install_item_dir}/', 'components' => 'components/{$install_item_dir}/' ); /** * Format package name. * * For package type microweber-module, cut off a trailing '-module' if present * * For package type microweber-template, cut off a trailing '-template' if present. * */ public function inflectPackageVars($vars) { if ($this->package->getTargetDir()) { $vars['install_item_dir'] = $this->package->getTargetDir(); } else { $vars['install_item_dir'] = $vars['name']; if ($vars['type'] === 'microweber-template') { return $this->inflectTemplateVars($vars); } if ($vars['type'] === 'microweber-templates') { return $this->inflectTemplatesVars($vars); } if ($vars['type'] === 'microweber-core') { return $this->inflectCoreVars($vars); } if ($vars['type'] === 'microweber-adapter') { return $this->inflectCoreVars($vars); } if ($vars['type'] === 'microweber-module') { return $this->inflectModuleVars($vars); } if ($vars['type'] === 'microweber-modules') { return $this->inflectModulesVars($vars); } if ($vars['type'] === 'microweber-skin') { return $this->inflectSkinVars($vars); } if ($vars['type'] === 'microweber-element' or $vars['type'] === 'microweber-elements') { return $this->inflectElementVars($vars); } } return $vars; } protected function inflectTemplateVars($vars) { $vars['install_item_dir'] = preg_replace('/-template$/', '', $vars['install_item_dir']); $vars['install_item_dir'] = preg_replace('/template-$/', '', $vars['install_item_dir']); return $vars; } protected function inflectTemplatesVars($vars) { $vars['install_item_dir'] = preg_replace('/-templates$/', '', $vars['install_item_dir']); $vars['install_item_dir'] = preg_replace('/templates-$/', '', $vars['install_item_dir']); return $vars; } protected function inflectCoreVars($vars) { $vars['install_item_dir'] = preg_replace('/-providers$/', '', $vars['install_item_dir']); $vars['install_item_dir'] = preg_replace('/-provider$/', '', $vars['install_item_dir']); $vars['install_item_dir'] = preg_replace('/-adapter$/', '', $vars['install_item_dir']); return $vars; } protected function inflectModuleVars($vars) { $vars['install_item_dir'] = preg_replace('/-module$/', '', $vars['install_item_dir']); $vars['install_item_dir'] = preg_replace('/module-$/', '', $vars['install_item_dir']); return $vars; } protected function inflectModulesVars($vars) { $vars['install_item_dir'] = preg_replace('/-modules$/', '', $vars['install_item_dir']); $vars['install_item_dir'] = preg_replace('/modules-$/', '', $vars['install_item_dir']); return $vars; } protected function inflectSkinVars($vars) { $vars['install_item_dir'] = preg_replace('/-skin$/', '', $vars['install_item_dir']); $vars['install_item_dir'] = preg_replace('/skin-$/', '', $vars['install_item_dir']); return $vars; } protected function inflectElementVars($vars) { $vars['install_item_dir'] = preg_replace('/-elements$/', '', $vars['install_item_dir']); $vars['install_item_dir'] = preg_replace('/elements-$/', '', $vars['install_item_dir']); $vars['install_item_dir'] = preg_replace('/-element$/', '', $vars['install_item_dir']); $vars['install_item_dir'] = preg_replace('/element-$/', '', $vars['install_item_dir']); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/RoundcubeInstaller.php 0000644 00000000711 15133551764 0026666 0 ustar 00 <?php namespace Composer\Installers; class RoundcubeInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'plugins/{$name}/', ); /** * Lowercase name and changes the name to a underscores * * @param array $vars * @return array */ public function inflectPackageVars($vars) { $vars['name'] = strtolower(str_replace('-', '_', $vars['name'])); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/CiviCrmInstaller.php 0000644 00000000243 15133551764 0026274 0 ustar 00 <?php namespace Composer\Installers; class CiviCrmInstaller extends BaseInstaller { protected $locations = array( 'ext' => 'ext/{$name}/' ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/ImageCMSInstaller.php 0000644 00000000444 15133551764 0026330 0 ustar 00 <?php namespace Composer\Installers; class ImageCMSInstaller extends BaseInstaller { protected $locations = array( 'template' => 'templates/{$name}/', 'module' => 'application/modules/{$name}/', 'library' => 'application/libraries/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/ItopInstaller.php 0000644 00000000256 15133551764 0025657 0 ustar 00 <?php namespace Composer\Installers; class ItopInstaller extends BaseInstaller { protected $locations = array( 'extension' => 'extensions/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/MediaWikiInstaller.php 0000644 00000002422 15133551764 0026604 0 ustar 00 <?php namespace Composer\Installers; class MediaWikiInstaller extends BaseInstaller { protected $locations = array( 'core' => 'core/', 'extension' => 'extensions/{$name}/', 'skin' => 'skins/{$name}/', ); /** * Format package name. * * For package type mediawiki-extension, cut off a trailing '-extension' if present and transform * to CamelCase keeping existing uppercase chars. * * For package type mediawiki-skin, cut off a trailing '-skin' if present. * */ public function inflectPackageVars($vars) { if ($vars['type'] === 'mediawiki-extension') { return $this->inflectExtensionVars($vars); } if ($vars['type'] === 'mediawiki-skin') { return $this->inflectSkinVars($vars); } return $vars; } protected function inflectExtensionVars($vars) { $vars['name'] = preg_replace('/-extension$/', '', $vars['name']); $vars['name'] = str_replace('-', ' ', $vars['name']); $vars['name'] = str_replace(' ', '', ucwords($vars['name'])); return $vars; } protected function inflectSkinVars($vars) { $vars['name'] = preg_replace('/-skin$/', '', $vars['name']); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/BaseInstaller.php 0000644 00000007753 15133551764 0025627 0 ustar 00 <?php namespace Composer\Installers; use Composer\IO\IOInterface; use Composer\Composer; use Composer\Package\PackageInterface; abstract class BaseInstaller { protected $locations = array(); protected $composer; protected $package; protected $io; /** * Initializes base installer. * * @param PackageInterface $package * @param Composer $composer * @param IOInterface $io */ public function __construct(PackageInterface $package = null, Composer $composer = null, IOInterface $io = null) { $this->composer = $composer; $this->package = $package; $this->io = $io; } /** * Return the install path based on package type. * * @param PackageInterface $package * @param string $frameworkType * @return string */ public function getInstallPath(PackageInterface $package, $frameworkType = '') { $type = $this->package->getType(); $prettyName = $this->package->getPrettyName(); if (strpos($prettyName, '/') !== false) { list($vendor, $name) = explode('/', $prettyName); } else { $vendor = ''; $name = $prettyName; } $availableVars = $this->inflectPackageVars(compact('name', 'vendor', 'type')); $extra = $package->getExtra(); if (!empty($extra['installer-name'])) { $availableVars['name'] = $extra['installer-name']; } if ($this->composer->getPackage()) { $extra = $this->composer->getPackage()->getExtra(); if (!empty($extra['installer-paths'])) { $customPath = $this->mapCustomInstallPaths($extra['installer-paths'], $prettyName, $type, $vendor); if ($customPath !== false) { return $this->templatePath($customPath, $availableVars); } } } $packageType = substr($type, strlen($frameworkType) + 1); $locations = $this->getLocations(); if (!isset($locations[$packageType])) { throw new \InvalidArgumentException(sprintf('Package type "%s" is not supported', $type)); } return $this->templatePath($locations[$packageType], $availableVars); } /** * For an installer to override to modify the vars per installer. * * @param array<string, string> $vars This will normally receive array{name: string, vendor: string, type: string} * @return array<string, string> */ public function inflectPackageVars($vars) { return $vars; } /** * Gets the installer's locations * * @return array<string, string> map of package types => install path */ public function getLocations() { return $this->locations; } /** * Replace vars in a path * * @param string $path * @param array<string, string> $vars * @return string */ protected function templatePath($path, array $vars = array()) { if (strpos($path, '{') !== false) { extract($vars); preg_match_all('@\{\$([A-Za-z0-9_]*)\}@i', $path, $matches); if (!empty($matches[1])) { foreach ($matches[1] as $var) { $path = str_replace('{$' . $var . '}', $$var, $path); } } } return $path; } /** * Search through a passed paths array for a custom install path. * * @param array $paths * @param string $name * @param string $type * @param string $vendor = NULL * @return string|false */ protected function mapCustomInstallPaths(array $paths, $name, $type, $vendor = NULL) { foreach ($paths as $path => $names) { $names = (array) $names; if (in_array($name, $names) || in_array('type:' . $type, $names) || in_array('vendor:' . $vendor, $names)) { return $path; } } return false; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/GravInstaller.php 0000644 00000001274 15133551764 0025644 0 ustar 00 <?php namespace Composer\Installers; class GravInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'user/plugins/{$name}/', 'theme' => 'user/themes/{$name}/', ); /** * Format package name * * @param array $vars * * @return array */ public function inflectPackageVars($vars) { $restrictedWords = implode('|', array_keys($this->locations)); $vars['name'] = strtolower($vars['name']); $vars['name'] = preg_replace('/^(?:grav-)?(?:(?:'.$restrictedWords.')-)?(.*?)(?:-(?:'.$restrictedWords.'))?$/ui', '$1', $vars['name'] ); return $vars; } } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/OsclassInstaller.php 0000644 00000000447 15133551764 0026355 0 ustar 00 <?php namespace Composer\Installers; class OsclassInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'oc-content/plugins/{$name}/', 'theme' => 'oc-content/themes/{$name}/', 'language' => 'oc-content/languages/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/LithiumInstaller.php 0000644 00000000336 15133551764 0026356 0 ustar 00 <?php namespace Composer\Installers; class LithiumInstaller extends BaseInstaller { protected $locations = array( 'library' => 'libraries/{$name}/', 'source' => 'libraries/_source/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/MiaoxingInstaller.php 0000644 00000000252 15133551764 0026513 0 ustar 00 <?php namespace Composer\Installers; class MiaoxingInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'plugins/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/RedaxoInstaller.php 0000644 00000000413 15133551764 0026161 0 ustar 00 <?php namespace Composer\Installers; class RedaxoInstaller extends BaseInstaller { protected $locations = array( 'addon' => 'redaxo/include/addons/{$name}/', 'bestyle-plugin' => 'redaxo/include/addons/be_style/plugins/{$name}/' ); } woocommerce-blocks/vendor/composer/installers/src/Composer/Installers/KanboardInstaller.php 0000644 00000000450 15133551764 0026461 0 ustar 00 <?php namespace Composer\Installers; /** * * Installer for kanboard plugins * * kanboard.net * * Class KanboardInstaller * @package Composer\Installers */ class KanboardInstaller extends BaseInstaller { protected $locations = array( 'plugin' => 'plugins/{$name}/', ); } woocommerce-blocks/vendor/composer/installers/src/bootstrap.php 0000644 00000000724 15133551764 0021174 0 ustar 00 <?php function includeIfExists($file) { if (file_exists($file)) { return include $file; } } if ((!$loader = includeIfExists(__DIR__ . '/../vendor/autoload.php')) && (!$loader = includeIfExists(__DIR__ . '/../../../autoload.php'))) { die('You must set up the project dependencies, run the following commands:'.PHP_EOL. 'curl -s http://getcomposer.org/installer | php'.PHP_EOL. 'php composer.phar install'.PHP_EOL); } return $loader; woocommerce-blocks/vendor/composer/installers/LICENSE 0000644 00000002046 15133551764 0016663 0 ustar 00 Copyright (c) 2012 Kyle Robinson Young 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. woocommerce-blocks/vendor/composer/jetpack_autoload_psr4.php 0000644 00000001343 15133551764 0020467 0 ustar 00 <?php // This file `jetpack_autoload_psr4.php` was auto generated by automattic/jetpack-autoloader. $vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( 'Composer\\Installers\\' => array( 'version' => '1.11.0.0', 'path' => array( $vendorDir . '/composer/installers/src/Composer/Installers' ) ), 'Automattic\\WooCommerce\\Blocks\\Tests\\' => array( 'version' => '6.1.0.0', 'path' => array( $baseDir . '/tests/php' ) ), 'Automattic\\WooCommerce\\Blocks\\' => array( 'version' => '6.1.0.0', 'path' => array( $baseDir . '/src' ) ), 'Automattic\\Jetpack\\Autoloader\\' => array( 'version' => '2.10.4.0', 'path' => array( $vendorDir . '/automattic/jetpack-autoloader/src' ) ), ); woocommerce-blocks/vendor/composer/autoload_psr4.php 0000644 00000000635 15133551764 0016771 0 ustar 00 <?php // autoload_psr4.php @generated by Composer $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( 'Composer\\Installers\\' => array($vendorDir . '/composer/installers/src/Composer/Installers'), 'Automattic\\WooCommerce\\Blocks\\' => array($baseDir . '/src'), 'Automattic\\Jetpack\\Autoloader\\' => array($vendorDir . '/automattic/jetpack-autoloader/src'), ); woocommerce-blocks/vendor/composer/jetpack_autoload_classmap.php 0000644 00000000563 15133551764 0021405 0 ustar 00 <?php // This file `jetpack_autoload_classmap.php` was auto generated by automattic/jetpack-autoloader. $vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( 'Automattic\\Jetpack\\Autoloader\\AutoloadGenerator' => array( 'version' => '2.10.4.0', 'path' => $vendorDir . '/automattic/jetpack-autoloader/src/AutoloadGenerator.php' ), ); woocommerce-blocks/vendor/composer/ClassLoader.php 0000644 00000034065 15133551764 0016411 0 ustar 00 <?php /* * This file is part of Composer. * * (c) Nils Adermann <naderman@naderman.de> * Jordi Boggiano <j.boggiano@seld.be> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Autoload; /** * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. * * $loader = new \Composer\Autoload\ClassLoader(); * * // register classes with namespaces * $loader->add('Symfony\Component', __DIR__.'/component'); * $loader->add('Symfony', __DIR__.'/framework'); * * // activate the autoloader * $loader->register(); * * // to enable searching the include path (eg. for PEAR packages) * $loader->setUseIncludePath(true); * * In this example, if you try to use a class in the Symfony\Component * namespace or one of its children (Symfony\Component\Console for instance), * the autoloader will first look for the class under the component/ * directory, and it will then fallback to the framework/ directory if not * found before giving up. * * This class is loosely based on the Symfony UniversalClassLoader. * * @author Fabien Potencier <fabien@symfony.com> * @author Jordi Boggiano <j.boggiano@seld.be> * @see https://www.php-fig.org/psr/psr-0/ * @see https://www.php-fig.org/psr/psr-4/ */ class ClassLoader { private $vendorDir; // PSR-4 private $prefixLengthsPsr4 = array(); private $prefixDirsPsr4 = array(); private $fallbackDirsPsr4 = array(); // PSR-0 private $prefixesPsr0 = array(); private $fallbackDirsPsr0 = array(); private $useIncludePath = false; private $classMap = array(); private $classMapAuthoritative = false; private $missingClasses = array(); private $apcuPrefix; private static $registeredLoaders = array(); public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; } public function getPrefixes() { if (!empty($this->prefixesPsr0)) { return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); } return array(); } public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } public function getFallbackDirs() { return $this->fallbackDirsPsr0; } public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } public function getClassMap() { return $this->classMap; } /** * @param array $classMap Class to filename map */ 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 array|string $paths The PSR-0 root directories * @param bool $prepend Whether to prepend the directories */ public function add($prefix, $paths, $prepend = false) { if (!$prefix) { if ($prepend) { $this->fallbackDirsPsr0 = array_merge( (array) $paths, $this->fallbackDirsPsr0 ); } else { $this->fallbackDirsPsr0 = array_merge( $this->fallbackDirsPsr0, (array) $paths ); } return; } $first = $prefix[0]; if (!isset($this->prefixesPsr0[$first][$prefix])) { $this->prefixesPsr0[$first][$prefix] = (array) $paths; return; } if ($prepend) { $this->prefixesPsr0[$first][$prefix] = array_merge( (array) $paths, $this->prefixesPsr0[$first][$prefix] ); } else { $this->prefixesPsr0[$first][$prefix] = array_merge( $this->prefixesPsr0[$first][$prefix], (array) $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 array|string $paths The PSR-4 base directories * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException */ public function addPsr4($prefix, $paths, $prepend = false) { if (!$prefix) { // Register directories for the root namespace. if ($prepend) { $this->fallbackDirsPsr4 = array_merge( (array) $paths, $this->fallbackDirsPsr4 ); } else { $this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4, (array) $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] = (array) $paths; } elseif ($prepend) { // Prepend directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( (array) $paths, $this->prefixDirsPsr4[$prefix] ); } else { // Append directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix], (array) $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 array|string $paths The PSR-0 base directories */ 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 array|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException */ 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 */ 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 */ 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 */ 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 */ 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. */ 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($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 indexed by their corresponding vendor directories. * * @return self[] */ public static function getRegisteredLoaders() { return self::$registeredLoaders; } 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; } } /** * Scope isolated include. * * Prevents access to $this/self from included files. */ function includeFile($file) { include $file; } woocommerce-blocks/vendor/composer/installed.php 0000644 00000003562 15133551764 0016172 0 ustar 00 <?php return array( 'root' => array( 'pretty_version' => 'dev-trunk', 'version' => 'dev-trunk', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'reference' => '8967acb720260debeb0b3991589cc3d3f1403d86', 'name' => 'woocommerce/woocommerce-blocks', 'dev' => false, ), 'versions' => array( 'automattic/jetpack-autoloader' => array( 'pretty_version' => 'v2.10.4', 'version' => '2.10.4.0', 'type' => 'composer-plugin', 'install_path' => __DIR__ . '/../automattic/jetpack-autoloader', 'aliases' => array(), 'reference' => '70cb300a7a215ae87c671f600f77093518f87bac', 'dev_requirement' => false, ), 'composer/installers' => array( 'pretty_version' => 'v1.11.0', 'version' => '1.11.0.0', 'type' => 'composer-plugin', 'install_path' => __DIR__ . '/./installers', 'aliases' => array(), 'reference' => 'ae03311f45dfe194412081526be2e003960df74b', 'dev_requirement' => false, ), 'roundcube/plugin-installer' => array( 'dev_requirement' => false, 'replaced' => array( 0 => '*', ), ), 'shama/baton' => array( 'dev_requirement' => false, 'replaced' => array( 0 => '*', ), ), 'woocommerce/woocommerce-blocks' => array( 'pretty_version' => 'dev-trunk', 'version' => 'dev-trunk', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'reference' => '8967acb720260debeb0b3991589cc3d3f1403d86', 'dev_requirement' => false, ), ), ); woocommerce-blocks/vendor/composer/LICENSE 0000644 00000002056 15133551764 0014504 0 ustar 00 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. woocommerce-blocks/vendor/composer/autoload_static.php 0000644 00000003215 15133551764 0017365 0 ustar 00 <?php // autoload_static.php @generated by Composer namespace Composer\Autoload; class ComposerStaticInitb78ca910fe07339d6189615f1734a3e3 { public static $prefixLengthsPsr4 = array ( 'C' => array ( 'Composer\\Installers\\' => 20, ), 'A' => array ( 'Automattic\\WooCommerce\\Blocks\\' => 30, 'Automattic\\Jetpack\\Autoloader\\' => 30, ), ); public static $prefixDirsPsr4 = array ( 'Composer\\Installers\\' => array ( 0 => __DIR__ . '/..' . '/composer/installers/src/Composer/Installers', ), 'Automattic\\WooCommerce\\Blocks\\' => array ( 0 => __DIR__ . '/../..' . '/src', ), 'Automattic\\Jetpack\\Autoloader\\' => array ( 0 => __DIR__ . '/..' . '/automattic/jetpack-autoloader/src', ), ); public static $classMap = array ( 'Automattic\\Jetpack\\Autoloader\\AutoloadGenerator' => __DIR__ . '/..' . '/automattic/jetpack-autoloader/src/AutoloadGenerator.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', ); public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInitb78ca910fe07339d6189615f1734a3e3::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInitb78ca910fe07339d6189615f1734a3e3::$prefixDirsPsr4; $loader->classMap = ComposerStaticInitb78ca910fe07339d6189615f1734a3e3::$classMap; }, null, ClassLoader::class); } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/class-path-processor.php 0000644 00000012557 15133551764 0025163 0 ustar 00 <?php /* HEADER */ // phpcs:ignore /** * This class handles dealing with paths for the autoloader. */ class Path_Processor { /** * Given a path this will replace any of the path constants with a token to represent it. * * @param string $path The path we want to process. * * @return string The tokenized path. */ public function tokenize_path_constants( $path ) { $path = wp_normalize_path( $path ); $constants = $this->get_normalized_constants(); foreach ( $constants as $constant => $constant_path ) { $len = strlen( $constant_path ); if ( substr( $path, 0, $len ) !== $constant_path ) { continue; } return substr_replace( $path, '{{' . $constant . '}}', 0, $len ); } return $path; } /** * Given a path this will replace any of the path constant tokens with the expanded path. * * @param string $tokenized_path The path we want to process. * * @return string The expanded path. */ public function untokenize_path_constants( $tokenized_path ) { $tokenized_path = wp_normalize_path( $tokenized_path ); $constants = $this->get_normalized_constants(); foreach ( $constants as $constant => $constant_path ) { $constant = '{{' . $constant . '}}'; $len = strlen( $constant ); if ( substr( $tokenized_path, 0, $len ) !== $constant ) { continue; } return $this->get_real_path( substr_replace( $tokenized_path, $constant_path, 0, $len ) ); } return $tokenized_path; } /** * Given a file and an array of places it might be, this will find the absolute path and return it. * * @param string $file The plugin or theme file to resolve. * @param array $directories_to_check The directories we should check for the file if it isn't an absolute path. * * @return string|false Returns the absolute path to the directory, otherwise false. */ public function find_directory_with_autoloader( $file, $directories_to_check ) { $file = wp_normalize_path( $file ); if ( ! $this->is_absolute_path( $file ) ) { $file = $this->find_absolute_plugin_path( $file, $directories_to_check ); if ( ! isset( $file ) ) { return false; } } // We need the real path for consistency with __DIR__ paths. $file = $this->get_real_path( $file ); // phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged $directory = @is_file( $file ) ? dirname( $file ) : $file; if ( ! @is_file( $directory . '/vendor/composer/jetpack_autoload_classmap.php' ) ) { return false; } // phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged return $directory; } /** * Fetches an array of normalized paths keyed by the constant they came from. * * @return string[] The normalized paths keyed by the constant. */ private function get_normalized_constants() { $raw_constants = array( // Order the constants from most-specific to least-specific. 'WP_PLUGIN_DIR', 'WPMU_PLUGIN_DIR', 'WP_CONTENT_DIR', 'ABSPATH', ); $constants = array(); foreach ( $raw_constants as $raw ) { if ( ! defined( $raw ) ) { continue; } $path = wp_normalize_path( constant( $raw ) ); if ( isset( $path ) ) { $constants[ $raw ] = $path; } } return $constants; } /** * Indicates whether or not a path is absolute. * * @param string $path The path to check. * * @return bool True if the path is absolute, otherwise false. */ private function is_absolute_path( $path ) { if ( 0 === strlen( $path ) || '.' === $path[0] ) { return false; } // Absolute paths on Windows may begin with a drive letter. if ( preg_match( '/^[a-zA-Z]:[\/\\\\]/', $path ) ) { return true; } // A path starting with / or \ is absolute; anything else is relative. return ( '/' === $path[0] || '\\' === $path[0] ); } /** * Given a file and a list of directories to check, this method will try to figure out * the absolute path to the file in question. * * @param string $normalized_path The normalized path to the plugin or theme file to resolve. * @param array $directories_to_check The directories we should check for the file if it isn't an absolute path. * * @return string|null The absolute path to the plugin file, otherwise null. */ private function find_absolute_plugin_path( $normalized_path, $directories_to_check ) { // We're only able to find the absolute path for plugin/theme PHP files. if ( ! is_string( $normalized_path ) || '.php' !== substr( $normalized_path, -4 ) ) { return null; } foreach ( $directories_to_check as $directory ) { $normalized_check = wp_normalize_path( trailingslashit( $directory ) ) . $normalized_path; // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged if ( @is_file( $normalized_check ) ) { return $normalized_check; } } return null; } /** * Given a path this will figure out the real path that we should be using. * * @param string $path The path to resolve. * * @return string The resolved path. */ private function get_real_path( $path ) { // We want to resolve symbolic links for consistency with __DIR__ paths. // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $real_path = @realpath( $path ); if ( false === $real_path ) { // Let the autoloader deal with paths that don't exist. $real_path = $path; } // Using realpath will make it platform-specific so we must normalize it after. if ( $path !== $real_path ) { $real_path = wp_normalize_path( $real_path ); } return $real_path; } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/class-plugins-handler.php 0000644 00000013071 15133551764 0025276 0 ustar 00 <?php /* HEADER */ // phpcs:ignore /** * This class handles locating and caching all of the active plugins. */ class Plugins_Handler { /** * The transient key for plugin paths. */ const TRANSIENT_KEY = 'jetpack_autoloader_plugin_paths'; /** * The locator for finding plugins in different locations. * * @var Plugin_Locator */ private $plugin_locator; /** * The processor for transforming cached paths. * * @var Path_Processor */ private $path_processor; /** * The constructor. * * @param Plugin_Locator $plugin_locator The locator for finding active plugins. * @param Path_Processor $path_processor The processor for transforming cached paths. */ public function __construct( $plugin_locator, $path_processor ) { $this->plugin_locator = $plugin_locator; $this->path_processor = $path_processor; } /** * Gets all of the active plugins we can find. * * @param bool $include_deactivating When true, plugins deactivating this request will be considered active. * @param bool $record_unknown When true, the current plugin will be marked as active and recorded when unknown. * * @return string[] */ public function get_active_plugins( $include_deactivating, $record_unknown ) { global $jetpack_autoloader_activating_plugins_paths; // We're going to build a unique list of plugins from a few different sources // to find all of our "active" plugins. While we need to return an integer // array, we're going to use an associative array internally to reduce // the amount of time that we're going to spend checking uniqueness // and merging different arrays together to form the output. $active_plugins = array(); // Make sure that plugins which have activated this request are considered as "active" even though // they probably won't be present in any option. if ( is_array( $jetpack_autoloader_activating_plugins_paths ) ) { foreach ( $jetpack_autoloader_activating_plugins_paths as $path ) { $active_plugins[ $path ] = $path; } } // This option contains all of the plugins that have been activated. $plugins = $this->plugin_locator->find_using_option( 'active_plugins' ); foreach ( $plugins as $path ) { $active_plugins[ $path ] = $path; } // This option contains all of the multisite plugins that have been activated. if ( is_multisite() ) { $plugins = $this->plugin_locator->find_using_option( 'active_sitewide_plugins', true ); foreach ( $plugins as $path ) { $active_plugins[ $path ] = $path; } } // These actions contain plugins that are being activated/deactivated during this request. $plugins = $this->plugin_locator->find_using_request_action( array( 'activate', 'activate-selected', 'deactivate', 'deactivate-selected' ) ); foreach ( $plugins as $path ) { $active_plugins[ $path ] = $path; } // When the current plugin isn't considered "active" there's a problem. // Since we're here, the plugin is active and currently being loaded. // We can support this case (mu-plugins and non-standard activation) // by adding the current plugin to the active list and marking it // as an unknown (activating) plugin. This also has the benefit // of causing a reset because the active plugins list has // been changed since it was saved in the global. $current_plugin = $this->plugin_locator->find_current_plugin(); if ( $record_unknown && ! in_array( $current_plugin, $active_plugins, true ) ) { $active_plugins[ $current_plugin ] = $current_plugin; $jetpack_autoloader_activating_plugins_paths[] = $current_plugin; } // When deactivating plugins aren't desired we should entirely remove them from the active list. if ( ! $include_deactivating ) { // These actions contain plugins that are being deactivated during this request. $plugins = $this->plugin_locator->find_using_request_action( array( 'deactivate', 'deactivate-selected' ) ); foreach ( $plugins as $path ) { unset( $active_plugins[ $path ] ); } } // Transform the array so that we don't have to worry about the keys interacting with other array types later. return array_values( $active_plugins ); } /** * Gets all of the cached plugins if there are any. * * @return string[] */ public function get_cached_plugins() { $cached = get_transient( self::TRANSIENT_KEY ); if ( ! is_array( $cached ) || empty( $cached ) ) { return array(); } // We need to expand the tokens to an absolute path for this webserver. return array_map( array( $this->path_processor, 'untokenize_path_constants' ), $cached ); } /** * Saves the plugin list to the cache. * * @param array $plugins The plugin list to save to the cache. */ public function cache_plugins( $plugins ) { // We store the paths in a tokenized form so that that webservers with different absolute paths don't break. $plugins = array_map( array( $this->path_processor, 'tokenize_path_constants' ), $plugins ); set_transient( self::TRANSIENT_KEY, $plugins ); } /** * Checks to see whether or not the plugin list given has changed when compared to the * shared `$jetpack_autoloader_cached_plugin_paths` global. This allows us to deal * with cases where the active list may change due to filtering.. * * @param string[] $plugins The plugins list to check against the global cache. * * @return bool True if the plugins have changed, otherwise false. */ public function have_plugins_changed( $plugins ) { global $jetpack_autoloader_cached_plugin_paths; if ( $jetpack_autoloader_cached_plugin_paths !== $plugins ) { $jetpack_autoloader_cached_plugin_paths = $plugins; return true; } return false; } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/class-shutdown-handler.php 0000644 00000005203 15133551764 0025466 0 ustar 00 <?php /* HEADER */ // phpcs:ignore /** * This class handles the shutdown of the autoloader. */ class Shutdown_Handler { /** * The Plugins_Handler instance. * * @var Plugins_Handler */ private $plugins_handler; /** * The plugins cached by this autoloader. * * @var string[] */ private $cached_plugins; /** * Indicates whether or not this autoloader was included by another. * * @var bool */ private $was_included_by_autoloader; /** * Constructor. * * @param Plugins_Handler $plugins_handler The Plugins_Handler instance to use. * @param string[] $cached_plugins The plugins cached by the autoloaer. * @param bool $was_included_by_autoloader Indicates whether or not the autoloader was included by another. */ public function __construct( $plugins_handler, $cached_plugins, $was_included_by_autoloader ) { $this->plugins_handler = $plugins_handler; $this->cached_plugins = $cached_plugins; $this->was_included_by_autoloader = $was_included_by_autoloader; } /** * Handles the shutdown of the autoloader. */ public function __invoke() { // Don't save a broken cache if an error happens during some plugin's initialization. if ( ! did_action( 'plugins_loaded' ) ) { // Ensure that the cache is emptied to prevent consecutive failures if the cache is to blame. if ( ! empty( $this->cached_plugins ) ) { $this->plugins_handler->cache_plugins( array() ); } return; } // Load the active plugins fresh since the list we pulled earlier might not contain // plugins that were activated but did not reset the autoloader. This happens // when a plugin is in the cache but not "active" when the autoloader loads. // We also want to make sure that plugins which are deactivating are not // considered "active" so that they will be removed from the cache now. try { $active_plugins = $this->plugins_handler->get_active_plugins( false, ! $this->was_included_by_autoloader ); } catch ( \Exception $ex ) { // When the package is deleted before shutdown it will throw an exception. // In the event this happens we should erase the cache. if ( ! empty( $this->cached_plugins ) ) { $this->plugins_handler->cache_plugins( array() ); } return; } // The paths should be sorted for easy comparisons with those loaded from the cache. // Note we don't need to sort the cached entries because they're already sorted. sort( $active_plugins ); // We don't want to waste time saving a cache that hasn't changed. if ( $this->cached_plugins === $active_plugins ) { return; } $this->plugins_handler->cache_plugins( $active_plugins ); } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/ManifestGenerator.php 0000644 00000007132 15133551764 0024515 0 ustar 00 <?php // phpcs:ignore WordPress.Files.FileName /** * Manifest Generator. * * @package automattic/jetpack-autoloader */ // phpcs:disable WordPress.Files.FileName.InvalidClassFileName // phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase // phpcs:disable WordPress.NamingConventions.ValidVariableName.InterpolatedVariableNotSnakeCase // phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase // phpcs:disable WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase // phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_var_export namespace Automattic\Jetpack\Autoloader; /** * Class ManifestGenerator. */ class ManifestGenerator { /** * Builds a manifest file for the given autoloader type. * * @param string $autoloaderType The type of autoloader to build a manifest for. * @param string $fileName The filename of the manifest. * @param array $content The manifest content to generate using. * * @return string|null $manifestFile * @throws \InvalidArgumentException When an invalid autoloader type is given. */ public static function buildManifest( $autoloaderType, $fileName, $content ) { if ( empty( $content ) ) { return null; } switch ( $autoloaderType ) { case 'classmap': case 'files': return self::buildStandardManifest( $fileName, $content ); case 'psr-4': return self::buildPsr4Manifest( $fileName, $content ); } throw new \InvalidArgumentException( 'An invalid manifest type of ' . $autoloaderType . ' was passed!' ); } /** * Builds the contents for the standard manifest file. * * @param string $fileName The filename we are building. * @param array $manifestData The formatted data for the manifest. * * @return string|null $manifestFile */ private static function buildStandardManifest( $fileName, $manifestData ) { $fileContent = PHP_EOL; foreach ( $manifestData as $key => $data ) { $key = var_export( $key, true ); $versionCode = var_export( $data['version'], true ); $fileContent .= <<<MANIFEST_CODE $key => array( 'version' => $versionCode, 'path' => {$data['path']} ), MANIFEST_CODE; $fileContent .= PHP_EOL; } return self::buildFile( $fileName, $fileContent ); } /** * Builds the contents for the PSR-4 manifest file. * * @param string $fileName The filename we are building. * @param array $namespaces The formatted PSR-4 data for the manifest. * * @return string|null $manifestFile */ private static function buildPsr4Manifest( $fileName, $namespaces ) { $fileContent = PHP_EOL; foreach ( $namespaces as $namespace => $data ) { $namespaceCode = var_export( $namespace, true ); $versionCode = var_export( $data['version'], true ); $pathCode = 'array( ' . implode( ', ', $data['path'] ) . ' )'; $fileContent .= <<<MANIFEST_CODE $namespaceCode => array( 'version' => $versionCode, 'path' => $pathCode ), MANIFEST_CODE; $fileContent .= PHP_EOL; } return self::buildFile( $fileName, $fileContent ); } /** * Generate the PHP that will be used in the file. * * @param string $fileName The filename we are building. * @param string $content The content to be written into the file. * * @return string $fileContent */ private static function buildFile( $fileName, $content ) { return <<<INCLUDE_FILE <?php // This file `$fileName` was auto generated by automattic/jetpack-autoloader. \$vendorDir = dirname(__DIR__); \$baseDir = dirname(\$vendorDir); return array($content); INCLUDE_FILE; } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/class-version-loader.php 0000644 00000007664 15133551764 0025146 0 ustar 00 <?php /* HEADER */ // phpcs:ignore /** * This class loads other classes based on given parameters. */ class Version_Loader { /** * The Version_Selector object. * * @var Version_Selector */ private $version_selector; /** * A map of available classes and their version and file path. * * @var array */ private $classmap; /** * A map of PSR-4 namespaces and their version and directory path. * * @var array */ private $psr4_map; /** * A map of all the files that we should load. * * @var array */ private $filemap; /** * The constructor. * * @param Version_Selector $version_selector The Version_Selector object. * @param array $classmap The verioned classmap to load using. * @param array $psr4_map The versioned PSR-4 map to load using. * @param array $filemap The versioned filemap to load. */ public function __construct( $version_selector, $classmap, $psr4_map, $filemap ) { $this->version_selector = $version_selector; $this->classmap = $classmap; $this->psr4_map = $psr4_map; $this->filemap = $filemap; } /** * Finds the file path for the given class. * * @param string $class_name The class to find. * * @return string|null $file_path The path to the file if found, null if no class was found. */ public function find_class_file( $class_name ) { $data = $this->select_newest_file( isset( $this->classmap[ $class_name ] ) ? $this->classmap[ $class_name ] : null, $this->find_psr4_file( $class_name ) ); if ( ! isset( $data ) ) { return null; } return $data['path']; } /** * Load all of the files in the filemap. */ public function load_filemap() { if ( empty( $this->filemap ) ) { return; } foreach ( $this->filemap as $file_identifier => $file_data ) { if ( empty( $GLOBALS['__composer_autoload_files'][ $file_identifier ] ) ) { require_once $file_data['path']; $GLOBALS['__composer_autoload_files'][ $file_identifier ] = true; } } } /** * Compares different class sources and returns the newest. * * @param array|null $classmap_data The classmap class data. * @param array|null $psr4_data The PSR-4 class data. * * @return array|null $data */ private function select_newest_file( $classmap_data, $psr4_data ) { if ( ! isset( $classmap_data ) ) { return $psr4_data; } elseif ( ! isset( $psr4_data ) ) { return $classmap_data; } if ( $this->version_selector->is_version_update_required( $classmap_data['version'], $psr4_data['version'] ) ) { return $psr4_data; } return $classmap_data; } /** * Finds the file for a given class in a PSR-4 namespace. * * @param string $class_name The class to find. * * @return array|null $data The version and path path to the file if found, null otherwise. */ private function find_psr4_file( $class_name ) { if ( ! isset( $this->psr4_map ) ) { return null; } // Don't bother with classes that have no namespace. $class_index = strrpos( $class_name, '\\' ); if ( ! $class_index ) { return null; } $class_for_path = str_replace( '\\', '/', $class_name ); // Search for the namespace by iteratively cutting off the last segment until // we find a match. This allows us to check the most-specific namespaces // first as well as minimize the amount of time spent looking. for ( $class_namespace = substr( $class_name, 0, $class_index ); ! empty( $class_namespace ); $class_namespace = substr( $class_namespace, 0, strrpos( $class_namespace, '\\' ) ) ) { $namespace = $class_namespace . '\\'; if ( ! isset( $this->psr4_map[ $namespace ] ) ) { continue; } $data = $this->psr4_map[ $namespace ]; foreach ( $data['path'] as $path ) { $path .= '/' . substr( $class_for_path, strlen( $namespace ) ) . '.php'; if ( file_exists( $path ) ) { return array( 'version' => $data['version'], 'path' => $path, ); } } } return null; } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/CustomAutoloaderPlugin.php 0000644 00000014057 15133551764 0025555 0 ustar 00 <?php //phpcs:ignore WordPress.Files.FileName.NotHyphenatedLowercase /** * Custom Autoloader Composer Plugin, hooks into composer events to generate the custom autoloader. * * @package automattic/jetpack-autoloader */ // phpcs:disable PHPCompatibility.Keywords.NewKeywords.t_useFound // phpcs:disable PHPCompatibility.LanguageConstructs.NewLanguageConstructs.t_ns_separatorFound // phpcs:disable PHPCompatibility.Keywords.NewKeywords.t_namespaceFound // phpcs:disable WordPress.Files.FileName.NotHyphenatedLowercase // phpcs:disable WordPress.Files.FileName.InvalidClassFileName // phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase namespace Automattic\Jetpack\Autoloader; use Composer\Composer; use Composer\EventDispatcher\EventSubscriberInterface; use Composer\IO\IOInterface; use Composer\Plugin\PluginInterface; use Composer\Script\Event; use Composer\Script\ScriptEvents; /** * Class CustomAutoloaderPlugin. * * @package automattic/jetpack-autoloader */ class CustomAutoloaderPlugin implements PluginInterface, EventSubscriberInterface { /** * IO object. * * @var IOInterface IO object. */ private $io; /** * Composer object. * * @var Composer Composer object. */ private $composer; /** * Do nothing. * * @param Composer $composer Composer object. * @param IOInterface $io IO object. */ public function activate( Composer $composer, IOInterface $io ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable $this->composer = $composer; $this->io = $io; } /** * Do nothing. * phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable * * @param Composer $composer Composer object. * @param IOInterface $io IO object. */ public function deactivate( Composer $composer, IOInterface $io ) { /* * Intentionally left empty. This is a PluginInterface method. * phpcs:enable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable */ } /** * Do nothing. * phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable * * @param Composer $composer Composer object. * @param IOInterface $io IO object. */ public function uninstall( Composer $composer, IOInterface $io ) { /* * Intentionally left empty. This is a PluginInterface method. * phpcs:enable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable */ } /** * Tell composer to listen for events and do something with them. * * @return array List of subscribed events. */ public static function getSubscribedEvents() { return array( ScriptEvents::POST_AUTOLOAD_DUMP => 'postAutoloadDump', ); } /** * Generate the custom autolaoder. * * @param Event $event Script event object. */ public function postAutoloadDump( Event $event ) { // When the autoloader is not required by the root package we don't want to execute it. // This prevents unwanted transitive execution that generates unused autoloaders or // at worst throws fatal executions. if ( ! $this->isRequiredByRoot() ) { return; } $config = $this->composer->getConfig(); if ( 'vendor' !== $config->raw()['config']['vendor-dir'] ) { $this->io->writeError( "\n<error>An error occurred while generating the autoloader files:", true ); $this->io->writeError( 'The project\'s composer.json or composer environment set a non-default vendor directory.', true ); $this->io->writeError( 'The default composer vendor directory must be used.</error>', true ); exit(); } $installationManager = $this->composer->getInstallationManager(); $repoManager = $this->composer->getRepositoryManager(); $localRepo = $repoManager->getLocalRepository(); $package = $this->composer->getPackage(); $optimize = $event->getFlags()['optimize']; $suffix = $this->determineSuffix(); $generator = new AutoloadGenerator( $this->io ); $generator->dump( $this->composer, $config, $localRepo, $package, $installationManager, 'composer', $optimize, $suffix ); $this->generated = true; } /** * Determine the suffix for the autoloader class. * * Reuses an existing suffix from vendor/autoload_packages.php or vendor/autoload.php if possible. * * @return string Suffix. */ private function determineSuffix() { $config = $this->composer->getConfig(); $vendorPath = $config->get( 'vendor-dir' ); // Command line. $suffix = $config->get( 'autoloader-suffix' ); if ( $suffix ) { return $suffix; } // Reuse our own suffix, if any. if ( is_readable( $vendorPath . '/autoload_packages.php' ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents $content = file_get_contents( $vendorPath . '/autoload_packages.php' ); if ( preg_match( '/^namespace Automattic\\\\Jetpack\\\\Autoloader\\\\jp([^;\s]+);/m', $content, $match ) ) { return $match[1]; } } // Reuse Composer's suffix, if any. if ( is_readable( $vendorPath . '/autoload.php' ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents $content = file_get_contents( $vendorPath . '/autoload.php' ); if ( preg_match( '{ComposerAutoloaderInit([^:\s]+)::}', $content, $match ) ) { return $match[1]; } } // Generate a random suffix. return md5( uniqid( '', true ) ); } /** * Checks to see whether or not the root package is the one that required the autoloader. * * @return bool */ private function isRequiredByRoot() { $package = $this->composer->getPackage(); $requires = $package->getRequires(); if ( ! is_array( $requires ) ) { $requires = array(); } $devRequires = $package->getDevRequires(); if ( ! is_array( $devRequires ) ) { $devRequires = array(); } $requires = array_merge( $requires, $devRequires ); if ( empty( $requires ) ) { $this->io->writeError( "\n<error>The package is not required and this should never happen?</error>", true ); exit(); } foreach ( $requires as $require ) { if ( 'automattic/jetpack-autoloader' === $require->getTarget() ) { return true; } } return false; } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/class-latest-autoloader-guard.php 0000644 00000005062 15133551764 0026734 0 ustar 00 <?php /* HEADER */ // phpcs:ignore /** * This class ensures that we're only executing the latest autoloader. */ class Latest_Autoloader_Guard { /** * The Plugins_Handler instance. * * @var Plugins_Handler */ private $plugins_handler; /** * The Autoloader_Handler instance. * * @var Autoloader_Handler */ private $autoloader_handler; /** * The Autoloader_locator instance. * * @var Autoloader_Locator */ private $autoloader_locator; /** * The constructor. * * @param Plugins_Handler $plugins_handler The Plugins_Handler instance. * @param Autoloader_Handler $autoloader_handler The Autoloader_Handler instance. * @param Autoloader_Locator $autoloader_locator The Autoloader_Locator instance. */ public function __construct( $plugins_handler, $autoloader_handler, $autoloader_locator ) { $this->plugins_handler = $plugins_handler; $this->autoloader_handler = $autoloader_handler; $this->autoloader_locator = $autoloader_locator; } /** * Indicates whether or not the autoloader should be initialized. Note that this function * has the side-effect of actually loading the latest autoloader in the event that this * is not it. * * @param string $current_plugin The current plugin we're checking. * @param string[] $plugins The active plugins to check for autoloaders in. * @param bool $was_included_by_autoloader Indicates whether or not this autoloader was included by another. * * @return bool True if we should stop initialization, otherwise false. */ public function should_stop_init( $current_plugin, $plugins, $was_included_by_autoloader ) { global $jetpack_autoloader_latest_version; // We need to reset the autoloader when the plugins change because // that means the autoloader was generated with a different list. if ( $this->plugins_handler->have_plugins_changed( $plugins ) ) { $this->autoloader_handler->reset_autoloader(); } // When the latest autoloader has already been found we don't need to search for it again. // We should take care however because this will also trigger if the autoloader has been // included by an older one. if ( isset( $jetpack_autoloader_latest_version ) && ! $was_included_by_autoloader ) { return true; } $latest_plugin = $this->autoloader_locator->find_latest_autoloader( $plugins, $jetpack_autoloader_latest_version ); if ( isset( $latest_plugin ) && $latest_plugin !== $current_plugin ) { require $this->autoloader_locator->get_autoloader_path( $latest_plugin ); return true; } return false; } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/class-container.php 0000644 00000011157 15133551764 0024167 0 ustar 00 <?php /* HEADER */ // phpcs:ignore /** * This class manages the files and dependencies of the autoloader. */ class Container { /** * Since each autoloader's class files exist within their own namespace we need a map to * convert between the local class and a shared key. Note that no version checking is * performed on these dependencies and the first autoloader to register will be the * one that is utilized. */ const SHARED_DEPENDENCY_KEYS = array( Hook_Manager::class => 'Hook_Manager', ); /** * A map of all the dependencies we've registered with the container and created. * * @var array */ protected $dependencies; /** * The constructor. */ public function __construct() { $this->dependencies = array(); $this->register_shared_dependencies(); $this->register_dependencies(); $this->initialize_globals(); } /** * Gets a dependency out of the container. * * @param string $class The class to fetch. * * @return mixed * @throws \InvalidArgumentException When a class that isn't registered with the container is fetched. */ public function get( $class ) { if ( ! isset( $this->dependencies[ $class ] ) ) { throw new \InvalidArgumentException( "Class '$class' is not registered with the container." ); } return $this->dependencies[ $class ]; } /** * Registers all of the dependencies that are shared between all instances of the autoloader. */ private function register_shared_dependencies() { global $jetpack_autoloader_container_shared; if ( ! isset( $jetpack_autoloader_container_shared ) ) { $jetpack_autoloader_container_shared = array(); } $key = self::SHARED_DEPENDENCY_KEYS[ Hook_Manager::class ]; if ( ! isset( $jetpack_autoloader_container_shared[ $key ] ) ) { require_once __DIR__ . '/class-hook-manager.php'; $jetpack_autoloader_container_shared[ $key ] = new Hook_Manager(); } $this->dependencies[ Hook_Manager::class ] = &$jetpack_autoloader_container_shared[ $key ]; } /** * Registers all of the dependencies with the container. */ private function register_dependencies() { require_once __DIR__ . '/class-path-processor.php'; $this->dependencies[ Path_Processor::class ] = new Path_Processor(); require_once __DIR__ . '/class-plugin-locator.php'; $this->dependencies[ Plugin_Locator::class ] = new Plugin_Locator( $this->get( Path_Processor::class ) ); require_once __DIR__ . '/class-version-selector.php'; $this->dependencies[ Version_Selector::class ] = new Version_Selector(); require_once __DIR__ . '/class-autoloader-locator.php'; $this->dependencies[ Autoloader_Locator::class ] = new Autoloader_Locator( $this->get( Version_Selector::class ) ); require_once __DIR__ . '/class-php-autoloader.php'; $this->dependencies[ PHP_Autoloader::class ] = new PHP_Autoloader(); require_once __DIR__ . '/class-manifest-reader.php'; $this->dependencies[ Manifest_Reader::class ] = new Manifest_Reader( $this->get( Version_Selector::class ) ); require_once __DIR__ . '/class-plugins-handler.php'; $this->dependencies[ Plugins_Handler::class ] = new Plugins_Handler( $this->get( Plugin_Locator::class ), $this->get( Path_Processor::class ) ); require_once __DIR__ . '/class-autoloader-handler.php'; $this->dependencies[ Autoloader_Handler::class ] = new Autoloader_Handler( $this->get( PHP_Autoloader::class ), $this->get( Hook_Manager::class ), $this->get( Manifest_Reader::class ), $this->get( Version_Selector::class ) ); require_once __DIR__ . '/class-latest-autoloader-guard.php'; $this->dependencies[ Latest_Autoloader_Guard::class ] = new Latest_Autoloader_Guard( $this->get( Plugins_Handler::class ), $this->get( Autoloader_Handler::class ), $this->get( Autoloader_Locator::class ) ); // Register any classes that we will use elsewhere. require_once __DIR__ . '/class-version-loader.php'; require_once __DIR__ . '/class-shutdown-handler.php'; } /** * Initializes any of the globals needed by the autoloader. */ private function initialize_globals() { /* * This global was retired in version 2.9. The value is set to 'false' to maintain * compatibility with older versions of the autoloader. */ global $jetpack_autoloader_including_latest; $jetpack_autoloader_including_latest = false; // Not all plugins can be found using the locator. In cases where a plugin loads the autoloader // but was not discoverable, we will record them in this array to track them as "active". global $jetpack_autoloader_activating_plugins_paths; if ( ! isset( $jetpack_autoloader_activating_plugins_paths ) ) { $jetpack_autoloader_activating_plugins_paths = array(); } } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/class-plugin-locator.php 0000644 00000010174 15133551764 0025142 0 ustar 00 <?php /* HEADER */ // phpcs:ignore /** * This class scans the WordPress installation to find active plugins. */ class Plugin_Locator { /** * The path processor for finding plugin paths. * * @var Path_Processor */ private $path_processor; /** * The constructor. * * @param Path_Processor $path_processor The Path_Processor instance. */ public function __construct( $path_processor ) { $this->path_processor = $path_processor; } /** * Finds the path to the current plugin. * * @return string $path The path to the current plugin. * * @throws \RuntimeException If the current plugin does not have an autoloader. */ public function find_current_plugin() { // Escape from `vendor/__DIR__` to root plugin directory. $plugin_directory = dirname( dirname( __DIR__ ) ); // Use the path processor to ensure that this is an autoloader we're referencing. $path = $this->path_processor->find_directory_with_autoloader( $plugin_directory, array() ); if ( false === $path ) { throw new \RuntimeException( 'Failed to locate plugin ' . $plugin_directory ); } return $path; } /** * Checks a given option for plugin paths. * * @param string $option_name The option that we want to check for plugin information. * @param bool $site_option Indicates whether or not we want to check the site option. * * @return array $plugin_paths The list of absolute paths we've found. */ public function find_using_option( $option_name, $site_option = false ) { $raw = $site_option ? get_site_option( $option_name ) : get_option( $option_name ); if ( false === $raw ) { return array(); } return $this->convert_plugins_to_paths( $raw ); } /** * Checks for plugins in the `action` request parameter. * * @param string[] $allowed_actions The actions that we're allowed to return plugins for. * * @return array $plugin_paths The list of absolute paths we've found. */ public function find_using_request_action( $allowed_actions ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended /** * Note: we're not actually checking the nonce here because it's too early * in the execution. The pluggable functions are not yet loaded to give * plugins a chance to plug their versions. Therefore we're doing the bare * minimum: checking whether the nonce exists and it's in the right place. * The request will fail later if the nonce doesn't pass the check. */ if ( empty( $_REQUEST['_wpnonce'] ) ) { return array(); } $action = isset( $_REQUEST['action'] ) ? wp_unslash( $_REQUEST['action'] ) : false; if ( ! in_array( $action, $allowed_actions, true ) ) { return array(); } $plugin_slugs = array(); switch ( $action ) { case 'activate': case 'deactivate': if ( empty( $_REQUEST['plugin'] ) ) { break; } $plugin_slugs[] = wp_unslash( $_REQUEST['plugin'] ); break; case 'activate-selected': case 'deactivate-selected': if ( empty( $_REQUEST['checked'] ) ) { break; } $plugin_slugs = wp_unslash( $_REQUEST['checked'] ); break; } // phpcs:enable WordPress.Security.NonceVerification.Recommended return $this->convert_plugins_to_paths( $plugin_slugs ); } /** * Given an array of plugin slugs or paths, this will convert them to absolute paths and filter * out the plugins that are not directory plugins. Note that array keys will also be included * if they are plugin paths! * * @param string[] $plugins Plugin paths or slugs to filter. * * @return string[] */ private function convert_plugins_to_paths( $plugins ) { if ( ! is_array( $plugins ) || empty( $plugins ) ) { return array(); } // We're going to look for plugins in the standard directories. $path_constants = array( WP_PLUGIN_DIR, WPMU_PLUGIN_DIR ); $plugin_paths = array(); foreach ( $plugins as $key => $value ) { $path = $this->path_processor->find_directory_with_autoloader( $key, $path_constants ); if ( $path ) { $plugin_paths[] = $path; } $path = $this->path_processor->find_directory_with_autoloader( $value, $path_constants ); if ( $path ) { $plugin_paths[] = $path; } } return $plugin_paths; } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/autoload.php 0000644 00000000173 15133551764 0022706 0 ustar 00 <?php /* HEADER */ // phpcs:ignore require_once __DIR__ . '/jetpack-autoloader/class-autoloader.php'; Autoloader::init(); woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/AutoloadFileWriter.php 0000644 00000006211 15133551764 0024642 0 ustar 00 <?php // phpcs:ignore WordPress.Files.FileName /** * Autoloader file writer. * * @package automattic/jetpack-autoloader */ // phpcs:disable WordPress.Files.FileName.InvalidClassFileName // phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase // phpcs:disable WordPress.NamingConventions.ValidVariableName.InterpolatedVariableNotSnakeCase // phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase // phpcs:disable WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase // phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_var_export // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_fopen // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_fwrite namespace Automattic\Jetpack\Autoloader; /** * Class AutoloadFileWriter. */ class AutoloadFileWriter { /** * The file comment to use. */ const COMMENT = <<<AUTOLOADER_COMMENT /** * This file was automatically generated by automattic/jetpack-autoloader. * * @package automattic/jetpack-autoloader */ AUTOLOADER_COMMENT; /** * Copies autoloader files and replaces any placeholders in them. * * @param IOInterface|null $io An IO for writing to. * @param string $outDir The directory to place the autoloader files in. * @param string $suffix The suffix to use in the autoloader's namespace. */ public static function copyAutoloaderFiles( $io, $outDir, $suffix ) { $renameList = array( 'autoload.php' => '../autoload_packages.php', ); $ignoreList = array( 'AutoloadGenerator.php', 'AutoloadProcessor.php', 'CustomAutoloaderPlugin.php', 'ManifestGenerator.php', 'AutoloadFileWriter.php', ); // Copy all of the autoloader files. $files = scandir( __DIR__ ); foreach ( $files as $file ) { // Only PHP files will be copied. if ( substr( $file, -4 ) !== '.php' ) { continue; } if ( in_array( $file, $ignoreList, true ) ) { continue; } $newFile = isset( $renameList[ $file ] ) ? $renameList[ $file ] : $file; $content = self::prepareAutoloaderFile( $file, $suffix ); $written = file_put_contents( $outDir . '/' . $newFile, $content ); if ( $io ) { if ( $written ) { $io->writeError( " <info>Generated: $newFile</info>" ); } else { $io->writeError( " <error>Error: $newFile</error>" ); } } } } /** * Prepares an autoloader file to be written to the destination. * * @param String $filename a file to prepare. * @param String $suffix Unique suffix used in the namespace. * * @return string */ private static function prepareAutoloaderFile( $filename, $suffix ) { $header = self::COMMENT; $header .= PHP_EOL; $header .= 'namespace Automattic\Jetpack\Autoloader\jp' . $suffix . ';'; $header .= PHP_EOL . PHP_EOL; $sourceLoader = fopen( __DIR__ . '/' . $filename, 'r' ); $file_contents = stream_get_contents( $sourceLoader ); return str_replace( '/* HEADER */', $header, $file_contents ); } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/class-version-selector.php 0000644 00000003165 15133551764 0025510 0 ustar 00 <?php /* HEADER */ // phpcs:ignore /** * Used to select package versions. */ class Version_Selector { /** * Checks whether the selected package version should be updated. Composer development * package versions ('9999999-dev' or versions that start with 'dev-') are favored * when the JETPACK_AUTOLOAD_DEV constant is set to true. * * @param String $selected_version The currently selected package version. * @param String $compare_version The package version that is being evaluated to * determine if the version needs to be updated. * * @return bool Returns true if the selected package version should be updated, * else false. */ public function is_version_update_required( $selected_version, $compare_version ) { $use_dev_versions = defined( 'JETPACK_AUTOLOAD_DEV' ) && JETPACK_AUTOLOAD_DEV; if ( is_null( $selected_version ) ) { return true; } if ( $use_dev_versions && $this->is_dev_version( $selected_version ) ) { return false; } if ( $this->is_dev_version( $compare_version ) ) { if ( $use_dev_versions ) { return true; } else { return false; } } if ( version_compare( $selected_version, $compare_version, '<' ) ) { return true; } return false; } /** * Checks whether the given package version is a development version. * * @param String $version The package version. * * @return bool True if the version is a dev version, else false. */ public function is_dev_version( $version ) { if ( 'dev-' === substr( $version, 0, 4 ) || '9999999-dev' === $version ) { return true; } return false; } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/class-autoloader.php 0000644 00000007573 15133551764 0024353 0 ustar 00 <?php /* HEADER */ // phpcs:ignore /** * This class handles management of the actual PHP autoloader. */ class Autoloader { /** * Checks to see whether or not the autoloader should be initialized and then initializes it if so. * * @param Container|null $container The container we want to use for autoloader initialization. If none is given * then a container will be created automatically. */ public static function init( $container = null ) { // The container holds and manages the lifecycle of our dependencies // to make them easier to work with and increase flexibility. if ( ! isset( $container ) ) { require_once __DIR__ . '/class-container.php'; $container = new Container(); } // phpcs:disable Generic.Commenting.DocComment.MissingShort /** @var Autoloader_Handler $autoloader_handler */ $autoloader_handler = $container->get( Autoloader_Handler::class ); // If the autoloader is already initializing it means that it has included us as the latest. $was_included_by_autoloader = $autoloader_handler->is_initializing(); /** @var Plugin_Locator $plugin_locator */ $plugin_locator = $container->get( Plugin_Locator::class ); /** @var Plugins_Handler $plugins_handler */ $plugins_handler = $container->get( Plugins_Handler::class ); // The current plugin is the one that we are attempting to initialize here. $current_plugin = $plugin_locator->find_current_plugin(); // The active plugins are those that we were able to discover on the site. This list will not // include mu-plugins, those activated by code, or those who are hidden by filtering. We also // want to take care to not consider the current plugin unknown if it was included by an // autoloader. This avoids the case where a plugin will be marked "active" while deactivated // due to it having the latest autoloader. $active_plugins = $plugins_handler->get_active_plugins( true, ! $was_included_by_autoloader ); // The cached plugins are all of those that were active or discovered by the autoloader during a previous request. // Note that it's possible this list will include plugins that have since been deactivated, but after a request // the cache should be updated and the deactivated plugins will be removed. $cached_plugins = $plugins_handler->get_cached_plugins(); // We combine the active list and cached list to preemptively load classes for plugins that are // presently unknown but will be loaded during the request. While this may result in us considering packages in // deactivated plugins there shouldn't be any problems as a result and the eventual consistency is sufficient. $all_plugins = array_merge( $active_plugins, $cached_plugins ); // In particular we also include the current plugin to address the case where it is the latest autoloader // but also unknown (and not cached). We don't want it in the active list because we don't know that it // is active but we need it in the all plugins list so that it is considered by the autoloader. $all_plugins[] = $current_plugin; // We require uniqueness in the array to avoid processing the same plugin more than once. $all_plugins = array_values( array_unique( $all_plugins ) ); /** @var Latest_Autoloader_Guard $guard */ $guard = $container->get( Latest_Autoloader_Guard::class ); if ( $guard->should_stop_init( $current_plugin, $all_plugins, $was_included_by_autoloader ) ) { return; } // Initialize the autoloader using the handler now that we're ready. $autoloader_handler->activate_autoloader( $all_plugins ); /** @var Hook_Manager $hook_manager */ $hook_manager = $container->get( Hook_Manager::class ); // Register a shutdown handler to clean up the autoloader. $hook_manager->add_action( 'shutdown', new Shutdown_Handler( $plugins_handler, $cached_plugins, $was_included_by_autoloader ) ); // phpcs:enable Generic.Commenting.DocComment.MissingShort } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/class-php-autoloader.php 0000644 00000005071 15133551764 0025127 0 ustar 00 <?php /* HEADER */ // phpcs:ignore /** * This class handles management of the actual PHP autoloader. */ class PHP_Autoloader { /** * Registers the autoloader with PHP so that it can begin autoloading classes. * * @param Version_Loader $version_loader The class loader to use in the autoloader. */ public function register_autoloader( $version_loader ) { // Make sure no other autoloaders are registered. $this->unregister_autoloader(); // Set the global so that it can be used to load classes. global $jetpack_autoloader_loader; $jetpack_autoloader_loader = $version_loader; // Ensure that the autoloader is first to avoid contention with others. spl_autoload_register( array( self::class, 'load_class' ), true, true ); } /** * Unregisters the active autoloader so that it will no longer autoload classes. */ public function unregister_autoloader() { // Remove any v2 autoloader that we've already registered. $autoload_chain = spl_autoload_functions(); foreach ( $autoload_chain as $autoloader ) { // We can identify a v2 autoloader using the namespace. $namespace_check = null; // Functions are recorded as strings. if ( is_string( $autoloader ) ) { $namespace_check = $autoloader; } elseif ( is_array( $autoloader ) && is_string( $autoloader[0] ) ) { // Static method calls have the class as the first array element. $namespace_check = $autoloader[0]; } else { // Since the autoloader has only ever been a function or a static method we don't currently need to check anything else. continue; } // Check for the namespace without the generated suffix. if ( 'Automattic\\Jetpack\\Autoloader\\jp' === substr( $namespace_check, 0, 32 ) ) { spl_autoload_unregister( $autoloader ); } } // Clear the global now that the autoloader has been unregistered. global $jetpack_autoloader_loader; $jetpack_autoloader_loader = null; } /** * Loads a class file if one could be found. * * Note: This function is static so that the autoloader can be easily unregistered. If * it was a class method we would have to unwrap the object to check the namespace. * * @param string $class_name The name of the class to autoload. * * @return bool Indicates whether or not a class file was loaded. */ public static function load_class( $class_name ) { global $jetpack_autoloader_loader; if ( ! isset( $jetpack_autoloader_loader ) ) { return; } $file = $jetpack_autoloader_loader->find_class_file( $class_name ); if ( ! isset( $file ) ) { return false; } require $file; return true; } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/class-manifest-reader.php 0000644 00000004673 15133551764 0025260 0 ustar 00 <?php /* HEADER */ // phpcs:ignore /** * This class reads autoloader manifest files. */ class Manifest_Reader { /** * The Version_Selector object. * * @var Version_Selector */ private $version_selector; /** * The constructor. * * @param Version_Selector $version_selector The Version_Selector object. */ public function __construct( $version_selector ) { $this->version_selector = $version_selector; } /** * Reads all of the manifests in the given plugin paths. * * @param array $plugin_paths The paths to the plugins we're loading the manifest in. * @param string $manifest_path The path that we're loading the manifest from in each plugin. * @param array $path_map The path map to add the contents of the manifests to. * * @return array $path_map The path map we've built using the manifests in each plugin. */ public function read_manifests( $plugin_paths, $manifest_path, &$path_map ) { $file_paths = array_map( function ( $path ) use ( $manifest_path ) { return trailingslashit( $path ) . $manifest_path; }, $plugin_paths ); foreach ( $file_paths as $path ) { $this->register_manifest( $path, $path_map ); } return $path_map; } /** * Registers a plugin's manifest file with the path map. * * @param string $manifest_path The absolute path to the manifest that we're loading. * @param array $path_map The path map to add the contents of the manifest to. */ protected function register_manifest( $manifest_path, &$path_map ) { if ( ! is_readable( $manifest_path ) ) { return; } $manifest = require $manifest_path; if ( ! is_array( $manifest ) ) { return; } foreach ( $manifest as $key => $data ) { $this->register_record( $key, $data, $path_map ); } } /** * Registers an entry from the manifest in the path map. * * @param string $key The identifier for the entry we're registering. * @param array $data The data for the entry we're registering. * @param array $path_map The path map to add the contents of the manifest to. */ protected function register_record( $key, $data, &$path_map ) { if ( isset( $path_map[ $key ]['version'] ) ) { $selected_version = $path_map[ $key ]['version']; } else { $selected_version = null; } if ( $this->version_selector->is_version_update_required( $selected_version, $data['version'] ) ) { $path_map[ $key ] = array( 'version' => $data['version'], 'path' => $data['path'], ); } } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/class-autoloader-locator.php 0000644 00000003616 15133551764 0026006 0 ustar 00 <?php /* HEADER */ // phpcs:ignore use Automattic\Jetpack\Autoloader\AutoloadGenerator; /** * This class locates autoloaders. */ class Autoloader_Locator { /** * The object for comparing autoloader versions. * * @var Version_Selector */ private $version_selector; /** * The constructor. * * @param Version_Selector $version_selector The version selector object. */ public function __construct( $version_selector ) { $this->version_selector = $version_selector; } /** * Finds the path to the plugin with the latest autoloader. * * @param array $plugin_paths An array of plugin paths. * @param string $latest_version The latest version reference. * * @return string|null */ public function find_latest_autoloader( $plugin_paths, &$latest_version ) { $latest_plugin = null; foreach ( $plugin_paths as $plugin_path ) { $version = $this->get_autoloader_version( $plugin_path ); if ( ! $this->version_selector->is_version_update_required( $latest_version, $version ) ) { continue; } $latest_version = $version; $latest_plugin = $plugin_path; } return $latest_plugin; } /** * Gets the path to the autoloader. * * @param string $plugin_path The path to the plugin. * * @return string */ public function get_autoloader_path( $plugin_path ) { return trailingslashit( $plugin_path ) . 'vendor/autoload_packages.php'; } /** * Gets the version for the autoloader. * * @param string $plugin_path The path to the plugin. * * @return string|null */ public function get_autoloader_version( $plugin_path ) { $classmap = trailingslashit( $plugin_path ) . 'vendor/composer/jetpack_autoload_classmap.php'; if ( ! file_exists( $classmap ) ) { return null; } $classmap = require $classmap; if ( isset( $classmap[ AutoloadGenerator::class ] ) ) { return $classmap[ AutoloadGenerator::class ]['version']; } return null; } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/AutoloadProcessor.php 0000644 00000012357 15133551764 0024555 0 ustar 00 <?php // phpcs:ignore WordPress.Files.FileName /** * Autoload Processor. * * @package automattic/jetpack-autoloader */ // phpcs:disable WordPress.Files.FileName.InvalidClassFileName // phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase // phpcs:disable WordPress.NamingConventions.ValidVariableName.InterpolatedVariableNotSnakeCase // phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase // phpcs:disable WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase namespace Automattic\Jetpack\Autoloader; /** * Class AutoloadProcessor. */ class AutoloadProcessor { /** * A callable for scanning a directory for all of its classes. * * @var callable */ private $classmapScanner; /** * A callable for transforming a path into one to be used in code. * * @var callable */ private $pathCodeTransformer; /** * The constructor. * * @param callable $classmapScanner A callable for scanning a directory for all of its classes. * @param callable $pathCodeTransformer A callable for transforming a path into one to be used in code. */ public function __construct( $classmapScanner, $pathCodeTransformer ) { $this->classmapScanner = $classmapScanner; $this->pathCodeTransformer = $pathCodeTransformer; } /** * Processes the classmap autoloads into a relative path format including the version for each file. * * @param array $autoloads The autoloads we are processing. * @param bool $scanPsrPackages Whether or not PSR packages should be converted to a classmap. * * @return array $processed */ public function processClassmap( $autoloads, $scanPsrPackages ) { // We can't scan PSR packages if we don't actually have any. if ( empty( $autoloads['psr-4'] ) ) { $scanPsrPackages = false; } if ( empty( $autoloads['classmap'] ) && ! $scanPsrPackages ) { return null; } $excludedClasses = null; if ( ! empty( $autoloads['exclude-from-classmap'] ) ) { $excludedClasses = '{(' . implode( '|', $autoloads['exclude-from-classmap'] ) . ')}'; } $processed = array(); if ( $scanPsrPackages ) { foreach ( $autoloads['psr-4'] as $namespace => $sources ) { $namespace = empty( $namespace ) ? null : $namespace; foreach ( $sources as $source ) { $classmap = call_user_func( $this->classmapScanner, $source['path'], $excludedClasses, $namespace ); foreach ( $classmap as $class => $path ) { $processed[ $class ] = array( 'version' => $source['version'], 'path' => call_user_func( $this->pathCodeTransformer, $path ), ); } } } } /* * PSR-0 namespaces are converted to classmaps for both optimized and unoptimized autoloaders because any new * development should use classmap or PSR-4 autoloading. */ if ( ! empty( $autoloads['psr-0'] ) ) { foreach ( $autoloads['psr-0'] as $namespace => $sources ) { $namespace = empty( $namespace ) ? null : $namespace; foreach ( $sources as $source ) { $classmap = call_user_func( $this->classmapScanner, $source['path'], $excludedClasses, $namespace ); foreach ( $classmap as $class => $path ) { $processed[ $class ] = array( 'version' => $source['version'], 'path' => call_user_func( $this->pathCodeTransformer, $path ), ); } } } } if ( ! empty( $autoloads['classmap'] ) ) { foreach ( $autoloads['classmap'] as $package ) { $classmap = call_user_func( $this->classmapScanner, $package['path'], $excludedClasses, null ); foreach ( $classmap as $class => $path ) { $processed[ $class ] = array( 'version' => $package['version'], 'path' => call_user_func( $this->pathCodeTransformer, $path ), ); } } } return $processed; } /** * Processes the PSR-4 autoloads into a relative path format including the version for each file. * * @param array $autoloads The autoloads we are processing. * @param bool $scanPsrPackages Whether or not PSR packages should be converted to a classmap. * * @return array $processed */ public function processPsr4Packages( $autoloads, $scanPsrPackages ) { if ( $scanPsrPackages || empty( $autoloads['psr-4'] ) ) { return null; } $processed = array(); foreach ( $autoloads['psr-4'] as $namespace => $packages ) { $namespace = empty( $namespace ) ? null : $namespace; $paths = array(); foreach ( $packages as $package ) { $paths[] = call_user_func( $this->pathCodeTransformer, $package['path'] ); } $processed[ $namespace ] = array( 'version' => $package['version'], 'path' => $paths, ); } return $processed; } /** * Processes the file autoloads into a relative format including the version for each file. * * @param array $autoloads The autoloads we are processing. * * @return array|null $processed */ public function processFiles( $autoloads ) { if ( empty( $autoloads['files'] ) ) { return null; } $processed = array(); foreach ( $autoloads['files'] as $file_id => $package ) { $processed[ $file_id ] = array( 'version' => $package['version'], 'path' => call_user_func( $this->pathCodeTransformer, $package['path'] ), ); } return $processed; } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/class-hook-manager.php 0000644 00000003643 15133551764 0024556 0 ustar 00 <?php /* HEADER */ // phpcs:ignore /** * Allows the latest autoloader to register hooks that can be removed when the autoloader is reset. */ class Hook_Manager { /** * An array containing all of the hooks that we've registered. * * @var array */ private $registered_hooks; /** * The constructor. */ public function __construct() { $this->registered_hooks = array(); } /** * Adds an action to WordPress and registers it internally. * * @param string $tag The name of the action which is hooked. * @param callable $callable The function to call. * @param int $priority Used to specify the priority of the action. * @param int $accepted_args Used to specify the number of arguments the callable accepts. */ public function add_action( $tag, $callable, $priority = 10, $accepted_args = 1 ) { $this->registered_hooks[ $tag ][] = array( 'priority' => $priority, 'callable' => $callable, ); add_action( $tag, $callable, $priority, $accepted_args ); } /** * Adds a filter to WordPress and registers it internally. * * @param string $tag The name of the filter which is hooked. * @param callable $callable The function to call. * @param int $priority Used to specify the priority of the filter. * @param int $accepted_args Used to specify the number of arguments the callable accepts. */ public function add_filter( $tag, $callable, $priority = 10, $accepted_args = 1 ) { $this->registered_hooks[ $tag ][] = array( 'priority' => $priority, 'callable' => $callable, ); add_filter( $tag, $callable, $priority, $accepted_args ); } /** * Removes all of the registered hooks. */ public function reset() { foreach ( $this->registered_hooks as $tag => $hooks ) { foreach ( $hooks as $hook ) { remove_filter( $tag, $hook['callable'], $hook['priority'] ); } } $this->registered_hooks = array(); } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/class-autoloader-handler.php 0000644 00000010465 15133551764 0025760 0 ustar 00 <?php /* HEADER */ // phpcs:ignore use Automattic\Jetpack\Autoloader\AutoloadGenerator; /** * This class selects the package version for the autoloader. */ class Autoloader_Handler { /** * The PHP_Autoloader instance. * * @var PHP_Autoloader */ private $php_autoloader; /** * The Hook_Manager instance. * * @var Hook_Manager */ private $hook_manager; /** * The Manifest_Reader instance. * * @var Manifest_Reader */ private $manifest_reader; /** * The Version_Selector instance. * * @var Version_Selector */ private $version_selector; /** * The constructor. * * @param PHP_Autoloader $php_autoloader The PHP_Autoloader instance. * @param Hook_Manager $hook_manager The Hook_Manager instance. * @param Manifest_Reader $manifest_reader The Manifest_Reader instance. * @param Version_Selector $version_selector The Version_Selector instance. */ public function __construct( $php_autoloader, $hook_manager, $manifest_reader, $version_selector ) { $this->php_autoloader = $php_autoloader; $this->hook_manager = $hook_manager; $this->manifest_reader = $manifest_reader; $this->version_selector = $version_selector; } /** * Checks to see whether or not an autoloader is currently in the process of initializing. * * @return bool */ public function is_initializing() { // If no version has been set it means that no autoloader has started initializing yet. global $jetpack_autoloader_latest_version; if ( ! isset( $jetpack_autoloader_latest_version ) ) { return false; } // When the version is set but the classmap is not it ALWAYS means that this is the // latest autoloader and is being included by an older one. global $jetpack_packages_classmap; if ( empty( $jetpack_packages_classmap ) ) { return true; } // Version 2.4.0 added a new global and altered the reset semantics. We need to check // the other global as well since it may also point at initialization. // Note: We don't need to check for the class first because every autoloader that // will set the latest version global requires this class in the classmap. $replacing_version = $jetpack_packages_classmap[ AutoloadGenerator::class ]['version']; if ( $this->version_selector->is_dev_version( $replacing_version ) || version_compare( $replacing_version, '2.4.0.0', '>=' ) ) { global $jetpack_autoloader_loader; if ( ! isset( $jetpack_autoloader_loader ) ) { return true; } } return false; } /** * Activates an autoloader using the given plugins and activates it. * * @param string[] $plugins The plugins to initialize the autoloader for. */ public function activate_autoloader( $plugins ) { global $jetpack_packages_psr4; $jetpack_packages_psr4 = array(); $this->manifest_reader->read_manifests( $plugins, 'vendor/composer/jetpack_autoload_psr4.php', $jetpack_packages_psr4 ); global $jetpack_packages_classmap; $jetpack_packages_classmap = array(); $this->manifest_reader->read_manifests( $plugins, 'vendor/composer/jetpack_autoload_classmap.php', $jetpack_packages_classmap ); global $jetpack_packages_filemap; $jetpack_packages_filemap = array(); $this->manifest_reader->read_manifests( $plugins, 'vendor/composer/jetpack_autoload_filemap.php', $jetpack_packages_filemap ); $loader = new Version_Loader( $this->version_selector, $jetpack_packages_classmap, $jetpack_packages_psr4, $jetpack_packages_filemap ); $this->php_autoloader->register_autoloader( $loader ); // Now that the autoloader is active we can load the filemap. $loader->load_filemap(); } /** * Resets the active autoloader and all related global state. */ public function reset_autoloader() { $this->php_autoloader->unregister_autoloader(); $this->hook_manager->reset(); // Clear all of the autoloader globals so that older autoloaders don't do anything strange. global $jetpack_autoloader_latest_version; $jetpack_autoloader_latest_version = null; global $jetpack_packages_classmap; $jetpack_packages_classmap = array(); // Must be array to avoid exceptions in old autoloaders! global $jetpack_packages_psr4; $jetpack_packages_psr4 = array(); // Must be array to avoid exceptions in old autoloaders! global $jetpack_packages_filemap; $jetpack_packages_filemap = array(); // Must be array to avoid exceptions in old autoloaders! } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/src/AutoloadGenerator.php 0000644 00000033623 15133551764 0024523 0 ustar 00 <?php // phpcs:ignore WordPress.Files.FileName /** * Autoloader Generator. * * @package automattic/jetpack-autoloader */ // phpcs:disable PHPCompatibility.Keywords.NewKeywords.t_useFound // phpcs:disable PHPCompatibility.LanguageConstructs.NewLanguageConstructs.t_ns_separatorFound // phpcs:disable PHPCompatibility.FunctionDeclarations.NewClosure.Found // phpcs:disable PHPCompatibility.Keywords.NewKeywords.t_namespaceFound // phpcs:disable PHPCompatibility.Keywords.NewKeywords.t_dirFound // phpcs:disable WordPress.Files.FileName.InvalidClassFileName // phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_var_export // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_fopen // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_fwrite // phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase // phpcs:disable WordPress.NamingConventions.ValidVariableName.InterpolatedVariableNotSnakeCase // phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase // phpcs:disable WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase namespace Automattic\Jetpack\Autoloader; use Composer\Autoload\ClassMapGenerator; use Composer\Composer; use Composer\Config; use Composer\Installer\InstallationManager; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use Composer\Repository\InstalledRepositoryInterface; use Composer\Util\Filesystem; use Composer\Util\PackageSorter; /** * Class AutoloadGenerator. */ class AutoloadGenerator { /** * The filesystem utility. * * @var Filesystem */ private $filesystem; /** * Instantiate an AutoloadGenerator object. * * @param IOInterface $io IO object. */ public function __construct( IOInterface $io = null ) { $this->io = $io; $this->filesystem = new Filesystem(); } /** * Dump the Jetpack autoloader files. * * @param Composer $composer The Composer object. * @param Config $config Config object. * @param InstalledRepositoryInterface $localRepo Installed Repository object. * @param PackageInterface $mainPackage Main Package object. * @param InstallationManager $installationManager Manager for installing packages. * @param string $targetDir Path to the current target directory. * @param bool $scanPsrPackages Whether or not PSR packages should be converted to a classmap. * @param string $suffix The autoloader suffix. */ public function dump( Composer $composer, Config $config, InstalledRepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir, $scanPsrPackages = false, $suffix = null ) { $this->filesystem->ensureDirectoryExists( $config->get( 'vendor-dir' ) ); $packageMap = $composer->getAutoloadGenerator()->buildPackageMap( $installationManager, $mainPackage, $localRepo->getCanonicalPackages() ); $autoloads = $this->parseAutoloads( $packageMap, $mainPackage ); // Convert the autoloads into a format that the manifest generator can consume more easily. $basePath = $this->filesystem->normalizePath( realpath( getcwd() ) ); $vendorPath = $this->filesystem->normalizePath( realpath( $config->get( 'vendor-dir' ) ) ); $processedAutoloads = $this->processAutoloads( $autoloads, $scanPsrPackages, $vendorPath, $basePath ); unset( $packageMap, $autoloads ); // Make sure none of the legacy files remain that can lead to problems with the autoloader. $this->removeLegacyFiles( $vendorPath ); // Write all of the files now that we're done. $this->writeAutoloaderFiles( $vendorPath . '/jetpack-autoloader/', $suffix ); $this->writeManifests( $vendorPath . '/' . $targetDir, $processedAutoloads ); if ( ! $scanPsrPackages ) { $this->io->writeError( '<warning>You are generating an unoptimized autoloader. If this is a production build, consider using the -o option.</warning>' ); } } /** * Compiles an ordered list of namespace => path mappings * * @param array $packageMap Array of array(package, installDir-relative-to-composer.json). * @param PackageInterface $mainPackage Main package instance. * * @return array The list of path mappings. */ public function parseAutoloads( array $packageMap, PackageInterface $mainPackage ) { $rootPackageMap = array_shift( $packageMap ); $sortedPackageMap = $this->sortPackageMap( $packageMap ); $sortedPackageMap[] = $rootPackageMap; array_unshift( $packageMap, $rootPackageMap ); $psr0 = $this->parseAutoloadsType( $packageMap, 'psr-0', $mainPackage ); $psr4 = $this->parseAutoloadsType( $packageMap, 'psr-4', $mainPackage ); $classmap = $this->parseAutoloadsType( array_reverse( $sortedPackageMap ), 'classmap', $mainPackage ); $files = $this->parseAutoloadsType( $sortedPackageMap, 'files', $mainPackage ); krsort( $psr0 ); krsort( $psr4 ); return array( 'psr-0' => $psr0, 'psr-4' => $psr4, 'classmap' => $classmap, 'files' => $files, ); } /** * Sorts packages by dependency weight * * Packages of equal weight retain the original order * * @param array $packageMap The package map. * * @return array */ protected function sortPackageMap( array $packageMap ) { $packages = array(); $paths = array(); foreach ( $packageMap as $item ) { list( $package, $path ) = $item; $name = $package->getName(); $packages[ $name ] = $package; $paths[ $name ] = $path; } $sortedPackages = PackageSorter::sortPackages( $packages ); $sortedPackageMap = array(); foreach ( $sortedPackages as $package ) { $name = $package->getName(); $sortedPackageMap[] = array( $packages[ $name ], $paths[ $name ] ); } return $sortedPackageMap; } /** * Returns the file identifier. * * @param PackageInterface $package The package instance. * @param string $path The path. */ protected function getFileIdentifier( PackageInterface $package, $path ) { return md5( $package->getName() . ':' . $path ); } /** * Returns the path code for the given path. * * @param Filesystem $filesystem The filesystem instance. * @param string $basePath The base path. * @param string $vendorPath The vendor path. * @param string $path The path. * * @return string The path code. */ protected function getPathCode( Filesystem $filesystem, $basePath, $vendorPath, $path ) { if ( ! $filesystem->isAbsolutePath( $path ) ) { $path = $basePath . '/' . $path; } $path = $filesystem->normalizePath( $path ); $baseDir = ''; if ( 0 === strpos( $path . '/', $vendorPath . '/' ) ) { $path = substr( $path, strlen( $vendorPath ) ); $baseDir = '$vendorDir'; if ( false !== $path ) { $baseDir .= ' . '; } } else { $path = $filesystem->normalizePath( $filesystem->findShortestPath( $basePath, $path, true ) ); if ( ! $filesystem->isAbsolutePath( $path ) ) { $baseDir = '$baseDir . '; $path = '/' . $path; } } if ( strpos( $path, '.phar' ) !== false ) { $baseDir = "'phar://' . " . $baseDir; } return $baseDir . ( ( false !== $path ) ? var_export( $path, true ) : '' ); } /** * This function differs from the composer parseAutoloadsType in that beside returning the path. * It also return the path and the version of a package. * * Supports PSR-4, PSR-0, and classmap parsing. * * @param array $packageMap Map of all the packages. * @param string $type Type of autoloader to use. * @param PackageInterface $mainPackage Instance of the Package Object. * * @return array */ protected function parseAutoloadsType( array $packageMap, $type, PackageInterface $mainPackage ) { $autoloads = array(); foreach ( $packageMap as $item ) { list($package, $installPath) = $item; $autoload = $package->getAutoload(); if ( $package === $mainPackage ) { $autoload = array_merge_recursive( $autoload, $package->getDevAutoload() ); } if ( null !== $package->getTargetDir() && $package !== $mainPackage ) { $installPath = substr( $installPath, 0, -strlen( '/' . $package->getTargetDir() ) ); } if ( in_array( $type, array( 'psr-4', 'psr-0' ), true ) && isset( $autoload[ $type ] ) && is_array( $autoload[ $type ] ) ) { foreach ( $autoload[ $type ] as $namespace => $paths ) { $paths = is_array( $paths ) ? $paths : array( $paths ); foreach ( $paths as $path ) { $relativePath = empty( $installPath ) ? ( empty( $path ) ? '.' : $path ) : $installPath . '/' . $path; $autoloads[ $namespace ][] = array( 'path' => $relativePath, 'version' => $package->getVersion(), // Version of the class comes from the package - should we try to parse it? ); } } } if ( 'classmap' === $type && isset( $autoload['classmap'] ) && is_array( $autoload['classmap'] ) ) { foreach ( $autoload['classmap'] as $paths ) { $paths = is_array( $paths ) ? $paths : array( $paths ); foreach ( $paths as $path ) { $relativePath = empty( $installPath ) ? ( empty( $path ) ? '.' : $path ) : $installPath . '/' . $path; $autoloads[] = array( 'path' => $relativePath, 'version' => $package->getVersion(), // Version of the class comes from the package - should we try to parse it? ); } } } if ( 'files' === $type && isset( $autoload['files'] ) && is_array( $autoload['files'] ) ) { foreach ( $autoload['files'] as $paths ) { $paths = is_array( $paths ) ? $paths : array( $paths ); foreach ( $paths as $path ) { $relativePath = empty( $installPath ) ? ( empty( $path ) ? '.' : $path ) : $installPath . '/' . $path; $autoloads[ $this->getFileIdentifier( $package, $path ) ] = array( 'path' => $relativePath, 'version' => $package->getVersion(), // Version of the file comes from the package - should we try to parse it? ); } } } } return $autoloads; } /** * Given Composer's autoloads this will convert them to a version that we can use to generate the manifests. * * When the $scanPsrPackages argument is true, PSR-4 namespaces are converted to classmaps. When $scanPsrPackages * is false, PSR-4 namespaces are not converted to classmaps. * * PSR-0 namespaces are always converted to classmaps. * * @param array $autoloads The autoloads we want to process. * @param bool $scanPsrPackages Whether or not PSR-4 packages should be converted to a classmap. * @param string $vendorPath The path to the vendor directory. * @param string $basePath The path to the current directory. * * @return array $processedAutoloads */ private function processAutoloads( $autoloads, $scanPsrPackages, $vendorPath, $basePath ) { $processor = new AutoloadProcessor( function ( $path, $excludedClasses, $namespace ) use ( $basePath ) { $dir = $this->filesystem->normalizePath( $this->filesystem->isAbsolutePath( $path ) ? $path : $basePath . '/' . $path ); return ClassMapGenerator::createMap( $dir, $excludedClasses, null, // Don't pass the IOInterface since the normal autoload generation will have reported already. empty( $namespace ) ? null : $namespace ); }, function ( $path ) use ( $basePath, $vendorPath ) { return $this->getPathCode( $this->filesystem, $basePath, $vendorPath, $path ); } ); return array( 'psr-4' => $processor->processPsr4Packages( $autoloads, $scanPsrPackages ), 'classmap' => $processor->processClassmap( $autoloads, $scanPsrPackages ), 'files' => $processor->processFiles( $autoloads ), ); } /** * Removes all of the legacy autoloader files so they don't cause any problems. * * @param string $outDir The directory legacy files are written to. */ private function removeLegacyFiles( $outDir ) { $files = array( 'autoload_functions.php', 'class-autoloader-handler.php', 'class-classes-handler.php', 'class-files-handler.php', 'class-plugins-handler.php', 'class-version-selector.php', ); foreach ( $files as $file ) { $this->filesystem->remove( $outDir . '/' . $file ); } } /** * Writes all of the autoloader files to disk. * * @param string $outDir The directory to write to. * @param string $suffix The unique autoloader suffix. */ private function writeAutoloaderFiles( $outDir, $suffix ) { $this->io->writeError( "<info>Generating jetpack autoloader ($outDir)</info>" ); // We will remove all autoloader files to generate this again. $this->filesystem->emptyDirectory( $outDir ); // Write the autoloader files. AutoloadFileWriter::copyAutoloaderFiles( $this->io, $outDir, $suffix ); } /** * Writes all of the manifest files to disk. * * @param string $outDir The directory to write to. * @param array $processedAutoloads The processed autoloads. */ private function writeManifests( $outDir, $processedAutoloads ) { $this->io->writeError( "<info>Generating jetpack autoloader manifests ($outDir)</info>" ); $manifestFiles = array( 'classmap' => 'jetpack_autoload_classmap.php', 'psr-4' => 'jetpack_autoload_psr4.php', 'files' => 'jetpack_autoload_filemap.php', ); foreach ( $manifestFiles as $key => $file ) { // Make sure the file doesn't exist so it isn't there if we don't write it. $this->filesystem->remove( $outDir . '/' . $file ); if ( empty( $processedAutoloads[ $key ] ) ) { continue; } $content = ManifestGenerator::buildManifest( $key, $file, $processedAutoloads[ $key ] ); if ( empty( $content ) ) { continue; } if ( file_put_contents( $outDir . '/' . $file, $content ) ) { $this->io->writeError( " <info>Generated: $file</info>" ); } else { $this->io->writeError( " <error>Error: $file</error>" ); } } } } woocommerce-blocks/vendor/automattic/jetpack-autoloader/LICENSE.txt 0000644 00000043760 15133551764 0021432 0 ustar 00 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =================================== GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. <one line to give the program's name and a brief idea of what it does.> Copyright (C) <year> <name of author> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. <signature of Ty Coon>, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. woocommerce-blocks/vendor/autoload_packages.php 0000644 00000000475 15133551764 0016032 0 ustar 00 <?php /** * This file was automatically generated by automattic/jetpack-autoloader. * * @package automattic/jetpack-autoloader */ namespace Automattic\Jetpack\Autoloader\jpb78ca910fe07339d6189615f1734a3e3; // phpcs:ignore require_once __DIR__ . '/jetpack-autoloader/class-autoloader.php'; Autoloader::init(); woocommerce-blocks/vendor/jetpack-autoloader/class-container.php 0000644 00000011461 15133551764 0021224 0 ustar 00 <?php /** * This file was automatically generated by automattic/jetpack-autoloader. * * @package automattic/jetpack-autoloader */ namespace Automattic\Jetpack\Autoloader\jpb78ca910fe07339d6189615f1734a3e3; // phpcs:ignore /** * This class manages the files and dependencies of the autoloader. */ class Container { /** * Since each autoloader's class files exist within their own namespace we need a map to * convert between the local class and a shared key. Note that no version checking is * performed on these dependencies and the first autoloader to register will be the * one that is utilized. */ const SHARED_DEPENDENCY_KEYS = array( Hook_Manager::class => 'Hook_Manager', ); /** * A map of all the dependencies we've registered with the container and created. * * @var array */ protected $dependencies; /** * The constructor. */ public function __construct() { $this->dependencies = array(); $this->register_shared_dependencies(); $this->register_dependencies(); $this->initialize_globals(); } /** * Gets a dependency out of the container. * * @param string $class The class to fetch. * * @return mixed * @throws \InvalidArgumentException When a class that isn't registered with the container is fetched. */ public function get( $class ) { if ( ! isset( $this->dependencies[ $class ] ) ) { throw new \InvalidArgumentException( "Class '$class' is not registered with the container." ); } return $this->dependencies[ $class ]; } /** * Registers all of the dependencies that are shared between all instances of the autoloader. */ private function register_shared_dependencies() { global $jetpack_autoloader_container_shared; if ( ! isset( $jetpack_autoloader_container_shared ) ) { $jetpack_autoloader_container_shared = array(); } $key = self::SHARED_DEPENDENCY_KEYS[ Hook_Manager::class ]; if ( ! isset( $jetpack_autoloader_container_shared[ $key ] ) ) { require_once __DIR__ . '/class-hook-manager.php'; $jetpack_autoloader_container_shared[ $key ] = new Hook_Manager(); } $this->dependencies[ Hook_Manager::class ] = &$jetpack_autoloader_container_shared[ $key ]; } /** * Registers all of the dependencies with the container. */ private function register_dependencies() { require_once __DIR__ . '/class-path-processor.php'; $this->dependencies[ Path_Processor::class ] = new Path_Processor(); require_once __DIR__ . '/class-plugin-locator.php'; $this->dependencies[ Plugin_Locator::class ] = new Plugin_Locator( $this->get( Path_Processor::class ) ); require_once __DIR__ . '/class-version-selector.php'; $this->dependencies[ Version_Selector::class ] = new Version_Selector(); require_once __DIR__ . '/class-autoloader-locator.php'; $this->dependencies[ Autoloader_Locator::class ] = new Autoloader_Locator( $this->get( Version_Selector::class ) ); require_once __DIR__ . '/class-php-autoloader.php'; $this->dependencies[ PHP_Autoloader::class ] = new PHP_Autoloader(); require_once __DIR__ . '/class-manifest-reader.php'; $this->dependencies[ Manifest_Reader::class ] = new Manifest_Reader( $this->get( Version_Selector::class ) ); require_once __DIR__ . '/class-plugins-handler.php'; $this->dependencies[ Plugins_Handler::class ] = new Plugins_Handler( $this->get( Plugin_Locator::class ), $this->get( Path_Processor::class ) ); require_once __DIR__ . '/class-autoloader-handler.php'; $this->dependencies[ Autoloader_Handler::class ] = new Autoloader_Handler( $this->get( PHP_Autoloader::class ), $this->get( Hook_Manager::class ), $this->get( Manifest_Reader::class ), $this->get( Version_Selector::class ) ); require_once __DIR__ . '/class-latest-autoloader-guard.php'; $this->dependencies[ Latest_Autoloader_Guard::class ] = new Latest_Autoloader_Guard( $this->get( Plugins_Handler::class ), $this->get( Autoloader_Handler::class ), $this->get( Autoloader_Locator::class ) ); // Register any classes that we will use elsewhere. require_once __DIR__ . '/class-version-loader.php'; require_once __DIR__ . '/class-shutdown-handler.php'; } /** * Initializes any of the globals needed by the autoloader. */ private function initialize_globals() { /* * This global was retired in version 2.9. The value is set to 'false' to maintain * compatibility with older versions of the autoloader. */ global $jetpack_autoloader_including_latest; $jetpack_autoloader_including_latest = false; // Not all plugins can be found using the locator. In cases where a plugin loads the autoloader // but was not discoverable, we will record them in this array to track them as "active". global $jetpack_autoloader_activating_plugins_paths; if ( ! isset( $jetpack_autoloader_activating_plugins_paths ) ) { $jetpack_autoloader_activating_plugins_paths = array(); } } } woocommerce-blocks/vendor/jetpack-autoloader/class-latest-autoloader-guard.php 0000644 00000005364 15133551764 0024000 0 ustar 00 <?php /** * This file was automatically generated by automattic/jetpack-autoloader. * * @package automattic/jetpack-autoloader */ namespace Automattic\Jetpack\Autoloader\jpb78ca910fe07339d6189615f1734a3e3; // phpcs:ignore /** * This class ensures that we're only executing the latest autoloader. */ class Latest_Autoloader_Guard { /** * The Plugins_Handler instance. * * @var Plugins_Handler */ private $plugins_handler; /** * The Autoloader_Handler instance. * * @var Autoloader_Handler */ private $autoloader_handler; /** * The Autoloader_locator instance. * * @var Autoloader_Locator */ private $autoloader_locator; /** * The constructor. * * @param Plugins_Handler $plugins_handler The Plugins_Handler instance. * @param Autoloader_Handler $autoloader_handler The Autoloader_Handler instance. * @param Autoloader_Locator $autoloader_locator The Autoloader_Locator instance. */ public function __construct( $plugins_handler, $autoloader_handler, $autoloader_locator ) { $this->plugins_handler = $plugins_handler; $this->autoloader_handler = $autoloader_handler; $this->autoloader_locator = $autoloader_locator; } /** * Indicates whether or not the autoloader should be initialized. Note that this function * has the side-effect of actually loading the latest autoloader in the event that this * is not it. * * @param string $current_plugin The current plugin we're checking. * @param string[] $plugins The active plugins to check for autoloaders in. * @param bool $was_included_by_autoloader Indicates whether or not this autoloader was included by another. * * @return bool True if we should stop initialization, otherwise false. */ public function should_stop_init( $current_plugin, $plugins, $was_included_by_autoloader ) { global $jetpack_autoloader_latest_version; // We need to reset the autoloader when the plugins change because // that means the autoloader was generated with a different list. if ( $this->plugins_handler->have_plugins_changed( $plugins ) ) { $this->autoloader_handler->reset_autoloader(); } // When the latest autoloader has already been found we don't need to search for it again. // We should take care however because this will also trigger if the autoloader has been // included by an older one. if ( isset( $jetpack_autoloader_latest_version ) && ! $was_included_by_autoloader ) { return true; } $latest_plugin = $this->autoloader_locator->find_latest_autoloader( $plugins, $jetpack_autoloader_latest_version ); if ( isset( $latest_plugin ) && $latest_plugin !== $current_plugin ) { require $this->autoloader_locator->get_autoloader_path( $latest_plugin ); return true; } return false; } } woocommerce-blocks/vendor/jetpack-autoloader/class-autoloader.php 0000644 00000010075 15133551764 0021401 0 ustar 00 <?php /** * This file was automatically generated by automattic/jetpack-autoloader. * * @package automattic/jetpack-autoloader */ namespace Automattic\Jetpack\Autoloader\jpb78ca910fe07339d6189615f1734a3e3; // phpcs:ignore /** * This class handles management of the actual PHP autoloader. */ class Autoloader { /** * Checks to see whether or not the autoloader should be initialized and then initializes it if so. * * @param Container|null $container The container we want to use for autoloader initialization. If none is given * then a container will be created automatically. */ public static function init( $container = null ) { // The container holds and manages the lifecycle of our dependencies // to make them easier to work with and increase flexibility. if ( ! isset( $container ) ) { require_once __DIR__ . '/class-container.php'; $container = new Container(); } // phpcs:disable Generic.Commenting.DocComment.MissingShort /** @var Autoloader_Handler $autoloader_handler */ $autoloader_handler = $container->get( Autoloader_Handler::class ); // If the autoloader is already initializing it means that it has included us as the latest. $was_included_by_autoloader = $autoloader_handler->is_initializing(); /** @var Plugin_Locator $plugin_locator */ $plugin_locator = $container->get( Plugin_Locator::class ); /** @var Plugins_Handler $plugins_handler */ $plugins_handler = $container->get( Plugins_Handler::class ); // The current plugin is the one that we are attempting to initialize here. $current_plugin = $plugin_locator->find_current_plugin(); // The active plugins are those that we were able to discover on the site. This list will not // include mu-plugins, those activated by code, or those who are hidden by filtering. We also // want to take care to not consider the current plugin unknown if it was included by an // autoloader. This avoids the case where a plugin will be marked "active" while deactivated // due to it having the latest autoloader. $active_plugins = $plugins_handler->get_active_plugins( true, ! $was_included_by_autoloader ); // The cached plugins are all of those that were active or discovered by the autoloader during a previous request. // Note that it's possible this list will include plugins that have since been deactivated, but after a request // the cache should be updated and the deactivated plugins will be removed. $cached_plugins = $plugins_handler->get_cached_plugins(); // We combine the active list and cached list to preemptively load classes for plugins that are // presently unknown but will be loaded during the request. While this may result in us considering packages in // deactivated plugins there shouldn't be any problems as a result and the eventual consistency is sufficient. $all_plugins = array_merge( $active_plugins, $cached_plugins ); // In particular we also include the current plugin to address the case where it is the latest autoloader // but also unknown (and not cached). We don't want it in the active list because we don't know that it // is active but we need it in the all plugins list so that it is considered by the autoloader. $all_plugins[] = $current_plugin; // We require uniqueness in the array to avoid processing the same plugin more than once. $all_plugins = array_values( array_unique( $all_plugins ) ); /** @var Latest_Autoloader_Guard $guard */ $guard = $container->get( Latest_Autoloader_Guard::class ); if ( $guard->should_stop_init( $current_plugin, $all_plugins, $was_included_by_autoloader ) ) { return; } // Initialize the autoloader using the handler now that we're ready. $autoloader_handler->activate_autoloader( $all_plugins ); /** @var Hook_Manager $hook_manager */ $hook_manager = $container->get( Hook_Manager::class ); // Register a shutdown handler to clean up the autoloader. $hook_manager->add_action( 'shutdown', new Shutdown_Handler( $plugins_handler, $cached_plugins, $was_included_by_autoloader ) ); // phpcs:enable Generic.Commenting.DocComment.MissingShort } } woocommerce-blocks/vendor/jetpack-autoloader/class-php-autoloader.php 0000644 00000005373 15133551764 0022173 0 ustar 00 <?php /** * This file was automatically generated by automattic/jetpack-autoloader. * * @package automattic/jetpack-autoloader */ namespace Automattic\Jetpack\Autoloader\jpb78ca910fe07339d6189615f1734a3e3; // phpcs:ignore /** * This class handles management of the actual PHP autoloader. */ class PHP_Autoloader { /** * Registers the autoloader with PHP so that it can begin autoloading classes. * * @param Version_Loader $version_loader The class loader to use in the autoloader. */ public function register_autoloader( $version_loader ) { // Make sure no other autoloaders are registered. $this->unregister_autoloader(); // Set the global so that it can be used to load classes. global $jetpack_autoloader_loader; $jetpack_autoloader_loader = $version_loader; // Ensure that the autoloader is first to avoid contention with others. spl_autoload_register( array( self::class, 'load_class' ), true, true ); } /** * Unregisters the active autoloader so that it will no longer autoload classes. */ public function unregister_autoloader() { // Remove any v2 autoloader that we've already registered. $autoload_chain = spl_autoload_functions(); foreach ( $autoload_chain as $autoloader ) { // We can identify a v2 autoloader using the namespace. $namespace_check = null; // Functions are recorded as strings. if ( is_string( $autoloader ) ) { $namespace_check = $autoloader; } elseif ( is_array( $autoloader ) && is_string( $autoloader[0] ) ) { // Static method calls have the class as the first array element. $namespace_check = $autoloader[0]; } else { // Since the autoloader has only ever been a function or a static method we don't currently need to check anything else. continue; } // Check for the namespace without the generated suffix. if ( 'Automattic\\Jetpack\\Autoloader\\jp' === substr( $namespace_check, 0, 32 ) ) { spl_autoload_unregister( $autoloader ); } } // Clear the global now that the autoloader has been unregistered. global $jetpack_autoloader_loader; $jetpack_autoloader_loader = null; } /** * Loads a class file if one could be found. * * Note: This function is static so that the autoloader can be easily unregistered. If * it was a class method we would have to unwrap the object to check the namespace. * * @param string $class_name The name of the class to autoload. * * @return bool Indicates whether or not a class file was loaded. */ public static function load_class( $class_name ) { global $jetpack_autoloader_loader; if ( ! isset( $jetpack_autoloader_loader ) ) { return; } $file = $jetpack_autoloader_loader->find_class_file( $class_name ); if ( ! isset( $file ) ) { return false; } require $file; return true; } } woocommerce-blocks/vendor/jetpack-autoloader/class-plugins-handler.php 0000644 00000013373 15133551764 0022342 0 ustar 00 <?php /** * This file was automatically generated by automattic/jetpack-autoloader. * * @package automattic/jetpack-autoloader */ namespace Automattic\Jetpack\Autoloader\jpb78ca910fe07339d6189615f1734a3e3; // phpcs:ignore /** * This class handles locating and caching all of the active plugins. */ class Plugins_Handler { /** * The transient key for plugin paths. */ const TRANSIENT_KEY = 'jetpack_autoloader_plugin_paths'; /** * The locator for finding plugins in different locations. * * @var Plugin_Locator */ private $plugin_locator; /** * The processor for transforming cached paths. * * @var Path_Processor */ private $path_processor; /** * The constructor. * * @param Plugin_Locator $plugin_locator The locator for finding active plugins. * @param Path_Processor $path_processor The processor for transforming cached paths. */ public function __construct( $plugin_locator, $path_processor ) { $this->plugin_locator = $plugin_locator; $this->path_processor = $path_processor; } /** * Gets all of the active plugins we can find. * * @param bool $include_deactivating When true, plugins deactivating this request will be considered active. * @param bool $record_unknown When true, the current plugin will be marked as active and recorded when unknown. * * @return string[] */ public function get_active_plugins( $include_deactivating, $record_unknown ) { global $jetpack_autoloader_activating_plugins_paths; // We're going to build a unique list of plugins from a few different sources // to find all of our "active" plugins. While we need to return an integer // array, we're going to use an associative array internally to reduce // the amount of time that we're going to spend checking uniqueness // and merging different arrays together to form the output. $active_plugins = array(); // Make sure that plugins which have activated this request are considered as "active" even though // they probably won't be present in any option. if ( is_array( $jetpack_autoloader_activating_plugins_paths ) ) { foreach ( $jetpack_autoloader_activating_plugins_paths as $path ) { $active_plugins[ $path ] = $path; } } // This option contains all of the plugins that have been activated. $plugins = $this->plugin_locator->find_using_option( 'active_plugins' ); foreach ( $plugins as $path ) { $active_plugins[ $path ] = $path; } // This option contains all of the multisite plugins that have been activated. if ( is_multisite() ) { $plugins = $this->plugin_locator->find_using_option( 'active_sitewide_plugins', true ); foreach ( $plugins as $path ) { $active_plugins[ $path ] = $path; } } // These actions contain plugins that are being activated/deactivated during this request. $plugins = $this->plugin_locator->find_using_request_action( array( 'activate', 'activate-selected', 'deactivate', 'deactivate-selected' ) ); foreach ( $plugins as $path ) { $active_plugins[ $path ] = $path; } // When the current plugin isn't considered "active" there's a problem. // Since we're here, the plugin is active and currently being loaded. // We can support this case (mu-plugins and non-standard activation) // by adding the current plugin to the active list and marking it // as an unknown (activating) plugin. This also has the benefit // of causing a reset because the active plugins list has // been changed since it was saved in the global. $current_plugin = $this->plugin_locator->find_current_plugin(); if ( $record_unknown && ! in_array( $current_plugin, $active_plugins, true ) ) { $active_plugins[ $current_plugin ] = $current_plugin; $jetpack_autoloader_activating_plugins_paths[] = $current_plugin; } // When deactivating plugins aren't desired we should entirely remove them from the active list. if ( ! $include_deactivating ) { // These actions contain plugins that are being deactivated during this request. $plugins = $this->plugin_locator->find_using_request_action( array( 'deactivate', 'deactivate-selected' ) ); foreach ( $plugins as $path ) { unset( $active_plugins[ $path ] ); } } // Transform the array so that we don't have to worry about the keys interacting with other array types later. return array_values( $active_plugins ); } /** * Gets all of the cached plugins if there are any. * * @return string[] */ public function get_cached_plugins() { $cached = get_transient( self::TRANSIENT_KEY ); if ( ! is_array( $cached ) || empty( $cached ) ) { return array(); } // We need to expand the tokens to an absolute path for this webserver. return array_map( array( $this->path_processor, 'untokenize_path_constants' ), $cached ); } /** * Saves the plugin list to the cache. * * @param array $plugins The plugin list to save to the cache. */ public function cache_plugins( $plugins ) { // We store the paths in a tokenized form so that that webservers with different absolute paths don't break. $plugins = array_map( array( $this->path_processor, 'tokenize_path_constants' ), $plugins ); set_transient( self::TRANSIENT_KEY, $plugins ); } /** * Checks to see whether or not the plugin list given has changed when compared to the * shared `$jetpack_autoloader_cached_plugin_paths` global. This allows us to deal * with cases where the active list may change due to filtering.. * * @param string[] $plugins The plugins list to check against the global cache. * * @return bool True if the plugins have changed, otherwise false. */ public function have_plugins_changed( $plugins ) { global $jetpack_autoloader_cached_plugin_paths; if ( $jetpack_autoloader_cached_plugin_paths !== $plugins ) { $jetpack_autoloader_cached_plugin_paths = $plugins; return true; } return false; } } woocommerce-blocks/vendor/jetpack-autoloader/class-version-loader.php 0000644 00000010166 15133551764 0022174 0 ustar 00 <?php /** * This file was automatically generated by automattic/jetpack-autoloader. * * @package automattic/jetpack-autoloader */ namespace Automattic\Jetpack\Autoloader\jpb78ca910fe07339d6189615f1734a3e3; // phpcs:ignore /** * This class loads other classes based on given parameters. */ class Version_Loader { /** * The Version_Selector object. * * @var Version_Selector */ private $version_selector; /** * A map of available classes and their version and file path. * * @var array */ private $classmap; /** * A map of PSR-4 namespaces and their version and directory path. * * @var array */ private $psr4_map; /** * A map of all the files that we should load. * * @var array */ private $filemap; /** * The constructor. * * @param Version_Selector $version_selector The Version_Selector object. * @param array $classmap The verioned classmap to load using. * @param array $psr4_map The versioned PSR-4 map to load using. * @param array $filemap The versioned filemap to load. */ public function __construct( $version_selector, $classmap, $psr4_map, $filemap ) { $this->version_selector = $version_selector; $this->classmap = $classmap; $this->psr4_map = $psr4_map; $this->filemap = $filemap; } /** * Finds the file path for the given class. * * @param string $class_name The class to find. * * @return string|null $file_path The path to the file if found, null if no class was found. */ public function find_class_file( $class_name ) { $data = $this->select_newest_file( isset( $this->classmap[ $class_name ] ) ? $this->classmap[ $class_name ] : null, $this->find_psr4_file( $class_name ) ); if ( ! isset( $data ) ) { return null; } return $data['path']; } /** * Load all of the files in the filemap. */ public function load_filemap() { if ( empty( $this->filemap ) ) { return; } foreach ( $this->filemap as $file_identifier => $file_data ) { if ( empty( $GLOBALS['__composer_autoload_files'][ $file_identifier ] ) ) { require_once $file_data['path']; $GLOBALS['__composer_autoload_files'][ $file_identifier ] = true; } } } /** * Compares different class sources and returns the newest. * * @param array|null $classmap_data The classmap class data. * @param array|null $psr4_data The PSR-4 class data. * * @return array|null $data */ private function select_newest_file( $classmap_data, $psr4_data ) { if ( ! isset( $classmap_data ) ) { return $psr4_data; } elseif ( ! isset( $psr4_data ) ) { return $classmap_data; } if ( $this->version_selector->is_version_update_required( $classmap_data['version'], $psr4_data['version'] ) ) { return $psr4_data; } return $classmap_data; } /** * Finds the file for a given class in a PSR-4 namespace. * * @param string $class_name The class to find. * * @return array|null $data The version and path path to the file if found, null otherwise. */ private function find_psr4_file( $class_name ) { if ( ! isset( $this->psr4_map ) ) { return null; } // Don't bother with classes that have no namespace. $class_index = strrpos( $class_name, '\\' ); if ( ! $class_index ) { return null; } $class_for_path = str_replace( '\\', '/', $class_name ); // Search for the namespace by iteratively cutting off the last segment until // we find a match. This allows us to check the most-specific namespaces // first as well as minimize the amount of time spent looking. for ( $class_namespace = substr( $class_name, 0, $class_index ); ! empty( $class_namespace ); $class_namespace = substr( $class_namespace, 0, strrpos( $class_namespace, '\\' ) ) ) { $namespace = $class_namespace . '\\'; if ( ! isset( $this->psr4_map[ $namespace ] ) ) { continue; } $data = $this->psr4_map[ $namespace ]; foreach ( $data['path'] as $path ) { $path .= '/' . substr( $class_for_path, strlen( $namespace ) ) . '.php'; if ( file_exists( $path ) ) { return array( 'version' => $data['version'], 'path' => $path, ); } } } return null; } } woocommerce-blocks/vendor/jetpack-autoloader/class-autoloader-locator.php 0000644 00000004120 15133551764 0023034 0 ustar 00 <?php /** * This file was automatically generated by automattic/jetpack-autoloader. * * @package automattic/jetpack-autoloader */ namespace Automattic\Jetpack\Autoloader\jpb78ca910fe07339d6189615f1734a3e3; // phpcs:ignore use Automattic\Jetpack\Autoloader\AutoloadGenerator; /** * This class locates autoloaders. */ class Autoloader_Locator { /** * The object for comparing autoloader versions. * * @var Version_Selector */ private $version_selector; /** * The constructor. * * @param Version_Selector $version_selector The version selector object. */ public function __construct( $version_selector ) { $this->version_selector = $version_selector; } /** * Finds the path to the plugin with the latest autoloader. * * @param array $plugin_paths An array of plugin paths. * @param string $latest_version The latest version reference. * * @return string|null */ public function find_latest_autoloader( $plugin_paths, &$latest_version ) { $latest_plugin = null; foreach ( $plugin_paths as $plugin_path ) { $version = $this->get_autoloader_version( $plugin_path ); if ( ! $this->version_selector->is_version_update_required( $latest_version, $version ) ) { continue; } $latest_version = $version; $latest_plugin = $plugin_path; } return $latest_plugin; } /** * Gets the path to the autoloader. * * @param string $plugin_path The path to the plugin. * * @return string */ public function get_autoloader_path( $plugin_path ) { return trailingslashit( $plugin_path ) . 'vendor/autoload_packages.php'; } /** * Gets the version for the autoloader. * * @param string $plugin_path The path to the plugin. * * @return string|null */ public function get_autoloader_version( $plugin_path ) { $classmap = trailingslashit( $plugin_path ) . 'vendor/composer/jetpack_autoload_classmap.php'; if ( ! file_exists( $classmap ) ) { return null; } $classmap = require $classmap; if ( isset( $classmap[ AutoloadGenerator::class ] ) ) { return $classmap[ AutoloadGenerator::class ]['version']; } return null; } } woocommerce-blocks/vendor/jetpack-autoloader/class-shutdown-handler.php 0000644 00000005505 15133551764 0022532 0 ustar 00 <?php /** * This file was automatically generated by automattic/jetpack-autoloader. * * @package automattic/jetpack-autoloader */ namespace Automattic\Jetpack\Autoloader\jpb78ca910fe07339d6189615f1734a3e3; // phpcs:ignore /** * This class handles the shutdown of the autoloader. */ class Shutdown_Handler { /** * The Plugins_Handler instance. * * @var Plugins_Handler */ private $plugins_handler; /** * The plugins cached by this autoloader. * * @var string[] */ private $cached_plugins; /** * Indicates whether or not this autoloader was included by another. * * @var bool */ private $was_included_by_autoloader; /** * Constructor. * * @param Plugins_Handler $plugins_handler The Plugins_Handler instance to use. * @param string[] $cached_plugins The plugins cached by the autoloaer. * @param bool $was_included_by_autoloader Indicates whether or not the autoloader was included by another. */ public function __construct( $plugins_handler, $cached_plugins, $was_included_by_autoloader ) { $this->plugins_handler = $plugins_handler; $this->cached_plugins = $cached_plugins; $this->was_included_by_autoloader = $was_included_by_autoloader; } /** * Handles the shutdown of the autoloader. */ public function __invoke() { // Don't save a broken cache if an error happens during some plugin's initialization. if ( ! did_action( 'plugins_loaded' ) ) { // Ensure that the cache is emptied to prevent consecutive failures if the cache is to blame. if ( ! empty( $this->cached_plugins ) ) { $this->plugins_handler->cache_plugins( array() ); } return; } // Load the active plugins fresh since the list we pulled earlier might not contain // plugins that were activated but did not reset the autoloader. This happens // when a plugin is in the cache but not "active" when the autoloader loads. // We also want to make sure that plugins which are deactivating are not // considered "active" so that they will be removed from the cache now. try { $active_plugins = $this->plugins_handler->get_active_plugins( false, ! $this->was_included_by_autoloader ); } catch ( \Exception $ex ) { // When the package is deleted before shutdown it will throw an exception. // In the event this happens we should erase the cache. if ( ! empty( $this->cached_plugins ) ) { $this->plugins_handler->cache_plugins( array() ); } return; } // The paths should be sorted for easy comparisons with those loaded from the cache. // Note we don't need to sort the cached entries because they're already sorted. sort( $active_plugins ); // We don't want to waste time saving a cache that hasn't changed. if ( $this->cached_plugins === $active_plugins ) { return; } $this->plugins_handler->cache_plugins( $active_plugins ); } } woocommerce-blocks/vendor/jetpack-autoloader/class-manifest-reader.php 0000644 00000005175 15133551764 0022315 0 ustar 00 <?php /** * This file was automatically generated by automattic/jetpack-autoloader. * * @package automattic/jetpack-autoloader */ namespace Automattic\Jetpack\Autoloader\jpb78ca910fe07339d6189615f1734a3e3; // phpcs:ignore /** * This class reads autoloader manifest files. */ class Manifest_Reader { /** * The Version_Selector object. * * @var Version_Selector */ private $version_selector; /** * The constructor. * * @param Version_Selector $version_selector The Version_Selector object. */ public function __construct( $version_selector ) { $this->version_selector = $version_selector; } /** * Reads all of the manifests in the given plugin paths. * * @param array $plugin_paths The paths to the plugins we're loading the manifest in. * @param string $manifest_path The path that we're loading the manifest from in each plugin. * @param array $path_map The path map to add the contents of the manifests to. * * @return array $path_map The path map we've built using the manifests in each plugin. */ public function read_manifests( $plugin_paths, $manifest_path, &$path_map ) { $file_paths = array_map( function ( $path ) use ( $manifest_path ) { return trailingslashit( $path ) . $manifest_path; }, $plugin_paths ); foreach ( $file_paths as $path ) { $this->register_manifest( $path, $path_map ); } return $path_map; } /** * Registers a plugin's manifest file with the path map. * * @param string $manifest_path The absolute path to the manifest that we're loading. * @param array $path_map The path map to add the contents of the manifest to. */ protected function register_manifest( $manifest_path, &$path_map ) { if ( ! is_readable( $manifest_path ) ) { return; } $manifest = require $manifest_path; if ( ! is_array( $manifest ) ) { return; } foreach ( $manifest as $key => $data ) { $this->register_record( $key, $data, $path_map ); } } /** * Registers an entry from the manifest in the path map. * * @param string $key The identifier for the entry we're registering. * @param array $data The data for the entry we're registering. * @param array $path_map The path map to add the contents of the manifest to. */ protected function register_record( $key, $data, &$path_map ) { if ( isset( $path_map[ $key ]['version'] ) ) { $selected_version = $path_map[ $key ]['version']; } else { $selected_version = null; } if ( $this->version_selector->is_version_update_required( $selected_version, $data['version'] ) ) { $path_map[ $key ] = array( 'version' => $data['version'], 'path' => $data['path'], ); } } } woocommerce-blocks/vendor/jetpack-autoloader/class-path-processor.php 0000644 00000013061 15133551764 0022211 0 ustar 00 <?php /** * This file was automatically generated by automattic/jetpack-autoloader. * * @package automattic/jetpack-autoloader */ namespace Automattic\Jetpack\Autoloader\jpb78ca910fe07339d6189615f1734a3e3; // phpcs:ignore /** * This class handles dealing with paths for the autoloader. */ class Path_Processor { /** * Given a path this will replace any of the path constants with a token to represent it. * * @param string $path The path we want to process. * * @return string The tokenized path. */ public function tokenize_path_constants( $path ) { $path = wp_normalize_path( $path ); $constants = $this->get_normalized_constants(); foreach ( $constants as $constant => $constant_path ) { $len = strlen( $constant_path ); if ( substr( $path, 0, $len ) !== $constant_path ) { continue; } return substr_replace( $path, '{{' . $constant . '}}', 0, $len ); } return $path; } /** * Given a path this will replace any of the path constant tokens with the expanded path. * * @param string $tokenized_path The path we want to process. * * @return string The expanded path. */ public function untokenize_path_constants( $tokenized_path ) { $tokenized_path = wp_normalize_path( $tokenized_path ); $constants = $this->get_normalized_constants(); foreach ( $constants as $constant => $constant_path ) { $constant = '{{' . $constant . '}}'; $len = strlen( $constant ); if ( substr( $tokenized_path, 0, $len ) !== $constant ) { continue; } return $this->get_real_path( substr_replace( $tokenized_path, $constant_path, 0, $len ) ); } return $tokenized_path; } /** * Given a file and an array of places it might be, this will find the absolute path and return it. * * @param string $file The plugin or theme file to resolve. * @param array $directories_to_check The directories we should check for the file if it isn't an absolute path. * * @return string|false Returns the absolute path to the directory, otherwise false. */ public function find_directory_with_autoloader( $file, $directories_to_check ) { $file = wp_normalize_path( $file ); if ( ! $this->is_absolute_path( $file ) ) { $file = $this->find_absolute_plugin_path( $file, $directories_to_check ); if ( ! isset( $file ) ) { return false; } } // We need the real path for consistency with __DIR__ paths. $file = $this->get_real_path( $file ); // phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged $directory = @is_file( $file ) ? dirname( $file ) : $file; if ( ! @is_file( $directory . '/vendor/composer/jetpack_autoload_classmap.php' ) ) { return false; } // phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged return $directory; } /** * Fetches an array of normalized paths keyed by the constant they came from. * * @return string[] The normalized paths keyed by the constant. */ private function get_normalized_constants() { $raw_constants = array( // Order the constants from most-specific to least-specific. 'WP_PLUGIN_DIR', 'WPMU_PLUGIN_DIR', 'WP_CONTENT_DIR', 'ABSPATH', ); $constants = array(); foreach ( $raw_constants as $raw ) { if ( ! defined( $raw ) ) { continue; } $path = wp_normalize_path( constant( $raw ) ); if ( isset( $path ) ) { $constants[ $raw ] = $path; } } return $constants; } /** * Indicates whether or not a path is absolute. * * @param string $path The path to check. * * @return bool True if the path is absolute, otherwise false. */ private function is_absolute_path( $path ) { if ( 0 === strlen( $path ) || '.' === $path[0] ) { return false; } // Absolute paths on Windows may begin with a drive letter. if ( preg_match( '/^[a-zA-Z]:[\/\\\\]/', $path ) ) { return true; } // A path starting with / or \ is absolute; anything else is relative. return ( '/' === $path[0] || '\\' === $path[0] ); } /** * Given a file and a list of directories to check, this method will try to figure out * the absolute path to the file in question. * * @param string $normalized_path The normalized path to the plugin or theme file to resolve. * @param array $directories_to_check The directories we should check for the file if it isn't an absolute path. * * @return string|null The absolute path to the plugin file, otherwise null. */ private function find_absolute_plugin_path( $normalized_path, $directories_to_check ) { // We're only able to find the absolute path for plugin/theme PHP files. if ( ! is_string( $normalized_path ) || '.php' !== substr( $normalized_path, -4 ) ) { return null; } foreach ( $directories_to_check as $directory ) { $normalized_check = wp_normalize_path( trailingslashit( $directory ) ) . $normalized_path; // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged if ( @is_file( $normalized_check ) ) { return $normalized_check; } } return null; } /** * Given a path this will figure out the real path that we should be using. * * @param string $path The path to resolve. * * @return string The resolved path. */ private function get_real_path( $path ) { // We want to resolve symbolic links for consistency with __DIR__ paths. // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $real_path = @realpath( $path ); if ( false === $real_path ) { // Let the autoloader deal with paths that don't exist. $real_path = $path; } // Using realpath will make it platform-specific so we must normalize it after. if ( $path !== $real_path ) { $real_path = wp_normalize_path( $real_path ); } return $real_path; } } woocommerce-blocks/vendor/jetpack-autoloader/class-hook-manager.php 0000644 00000004145 15133551764 0021613 0 ustar 00 <?php /** * This file was automatically generated by automattic/jetpack-autoloader. * * @package automattic/jetpack-autoloader */ namespace Automattic\Jetpack\Autoloader\jpb78ca910fe07339d6189615f1734a3e3; // phpcs:ignore /** * Allows the latest autoloader to register hooks that can be removed when the autoloader is reset. */ class Hook_Manager { /** * An array containing all of the hooks that we've registered. * * @var array */ private $registered_hooks; /** * The constructor. */ public function __construct() { $this->registered_hooks = array(); } /** * Adds an action to WordPress and registers it internally. * * @param string $tag The name of the action which is hooked. * @param callable $callable The function to call. * @param int $priority Used to specify the priority of the action. * @param int $accepted_args Used to specify the number of arguments the callable accepts. */ public function add_action( $tag, $callable, $priority = 10, $accepted_args = 1 ) { $this->registered_hooks[ $tag ][] = array( 'priority' => $priority, 'callable' => $callable, ); add_action( $tag, $callable, $priority, $accepted_args ); } /** * Adds a filter to WordPress and registers it internally. * * @param string $tag The name of the filter which is hooked. * @param callable $callable The function to call. * @param int $priority Used to specify the priority of the filter. * @param int $accepted_args Used to specify the number of arguments the callable accepts. */ public function add_filter( $tag, $callable, $priority = 10, $accepted_args = 1 ) { $this->registered_hooks[ $tag ][] = array( 'priority' => $priority, 'callable' => $callable, ); add_filter( $tag, $callable, $priority, $accepted_args ); } /** * Removes all of the registered hooks. */ public function reset() { foreach ( $this->registered_hooks as $tag => $hooks ) { foreach ( $hooks as $hook ) { remove_filter( $tag, $hook['callable'], $hook['priority'] ); } } $this->registered_hooks = array(); } } woocommerce-blocks/vendor/jetpack-autoloader/class-version-selector.php 0000644 00000003467 15133551764 0022554 0 ustar 00 <?php /** * This file was automatically generated by automattic/jetpack-autoloader. * * @package automattic/jetpack-autoloader */ namespace Automattic\Jetpack\Autoloader\jpb78ca910fe07339d6189615f1734a3e3; // phpcs:ignore /** * Used to select package versions. */ class Version_Selector { /** * Checks whether the selected package version should be updated. Composer development * package versions ('9999999-dev' or versions that start with 'dev-') are favored * when the JETPACK_AUTOLOAD_DEV constant is set to true. * * @param String $selected_version The currently selected package version. * @param String $compare_version The package version that is being evaluated to * determine if the version needs to be updated. * * @return bool Returns true if the selected package version should be updated, * else false. */ public function is_version_update_required( $selected_version, $compare_version ) { $use_dev_versions = defined( 'JETPACK_AUTOLOAD_DEV' ) && JETPACK_AUTOLOAD_DEV; if ( is_null( $selected_version ) ) { return true; } if ( $use_dev_versions && $this->is_dev_version( $selected_version ) ) { return false; } if ( $this->is_dev_version( $compare_version ) ) { if ( $use_dev_versions ) { return true; } else { return false; } } if ( version_compare( $selected_version, $compare_version, '<' ) ) { return true; } return false; } /** * Checks whether the given package version is a development version. * * @param String $version The package version. * * @return bool True if the version is a dev version, else false. */ public function is_dev_version( $version ) { if ( 'dev-' === substr( $version, 0, 4 ) || '9999999-dev' === $version ) { return true; } return false; } } woocommerce-blocks/vendor/jetpack-autoloader/class-plugin-locator.php 0000644 00000010476 15133551764 0022206 0 ustar 00 <?php /** * This file was automatically generated by automattic/jetpack-autoloader. * * @package automattic/jetpack-autoloader */ namespace Automattic\Jetpack\Autoloader\jpb78ca910fe07339d6189615f1734a3e3; // phpcs:ignore /** * This class scans the WordPress installation to find active plugins. */ class Plugin_Locator { /** * The path processor for finding plugin paths. * * @var Path_Processor */ private $path_processor; /** * The constructor. * * @param Path_Processor $path_processor The Path_Processor instance. */ public function __construct( $path_processor ) { $this->path_processor = $path_processor; } /** * Finds the path to the current plugin. * * @return string $path The path to the current plugin. * * @throws \RuntimeException If the current plugin does not have an autoloader. */ public function find_current_plugin() { // Escape from `vendor/__DIR__` to root plugin directory. $plugin_directory = dirname( dirname( __DIR__ ) ); // Use the path processor to ensure that this is an autoloader we're referencing. $path = $this->path_processor->find_directory_with_autoloader( $plugin_directory, array() ); if ( false === $path ) { throw new \RuntimeException( 'Failed to locate plugin ' . $plugin_directory ); } return $path; } /** * Checks a given option for plugin paths. * * @param string $option_name The option that we want to check for plugin information. * @param bool $site_option Indicates whether or not we want to check the site option. * * @return array $plugin_paths The list of absolute paths we've found. */ public function find_using_option( $option_name, $site_option = false ) { $raw = $site_option ? get_site_option( $option_name ) : get_option( $option_name ); if ( false === $raw ) { return array(); } return $this->convert_plugins_to_paths( $raw ); } /** * Checks for plugins in the `action` request parameter. * * @param string[] $allowed_actions The actions that we're allowed to return plugins for. * * @return array $plugin_paths The list of absolute paths we've found. */ public function find_using_request_action( $allowed_actions ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended /** * Note: we're not actually checking the nonce here because it's too early * in the execution. The pluggable functions are not yet loaded to give * plugins a chance to plug their versions. Therefore we're doing the bare * minimum: checking whether the nonce exists and it's in the right place. * The request will fail later if the nonce doesn't pass the check. */ if ( empty( $_REQUEST['_wpnonce'] ) ) { return array(); } $action = isset( $_REQUEST['action'] ) ? wp_unslash( $_REQUEST['action'] ) : false; if ( ! in_array( $action, $allowed_actions, true ) ) { return array(); } $plugin_slugs = array(); switch ( $action ) { case 'activate': case 'deactivate': if ( empty( $_REQUEST['plugin'] ) ) { break; } $plugin_slugs[] = wp_unslash( $_REQUEST['plugin'] ); break; case 'activate-selected': case 'deactivate-selected': if ( empty( $_REQUEST['checked'] ) ) { break; } $plugin_slugs = wp_unslash( $_REQUEST['checked'] ); break; } // phpcs:enable WordPress.Security.NonceVerification.Recommended return $this->convert_plugins_to_paths( $plugin_slugs ); } /** * Given an array of plugin slugs or paths, this will convert them to absolute paths and filter * out the plugins that are not directory plugins. Note that array keys will also be included * if they are plugin paths! * * @param string[] $plugins Plugin paths or slugs to filter. * * @return string[] */ private function convert_plugins_to_paths( $plugins ) { if ( ! is_array( $plugins ) || empty( $plugins ) ) { return array(); } // We're going to look for plugins in the standard directories. $path_constants = array( WP_PLUGIN_DIR, WPMU_PLUGIN_DIR ); $plugin_paths = array(); foreach ( $plugins as $key => $value ) { $path = $this->path_processor->find_directory_with_autoloader( $key, $path_constants ); if ( $path ) { $plugin_paths[] = $path; } $path = $this->path_processor->find_directory_with_autoloader( $value, $path_constants ); if ( $path ) { $plugin_paths[] = $path; } } return $plugin_paths; } } woocommerce-blocks/vendor/jetpack-autoloader/class-autoloader-handler.php 0000644 00000010767 15133551764 0023024 0 ustar 00 <?php /** * This file was automatically generated by automattic/jetpack-autoloader. * * @package automattic/jetpack-autoloader */ namespace Automattic\Jetpack\Autoloader\jpb78ca910fe07339d6189615f1734a3e3; // phpcs:ignore use Automattic\Jetpack\Autoloader\AutoloadGenerator; /** * This class selects the package version for the autoloader. */ class Autoloader_Handler { /** * The PHP_Autoloader instance. * * @var PHP_Autoloader */ private $php_autoloader; /** * The Hook_Manager instance. * * @var Hook_Manager */ private $hook_manager; /** * The Manifest_Reader instance. * * @var Manifest_Reader */ private $manifest_reader; /** * The Version_Selector instance. * * @var Version_Selector */ private $version_selector; /** * The constructor. * * @param PHP_Autoloader $php_autoloader The PHP_Autoloader instance. * @param Hook_Manager $hook_manager The Hook_Manager instance. * @param Manifest_Reader $manifest_reader The Manifest_Reader instance. * @param Version_Selector $version_selector The Version_Selector instance. */ public function __construct( $php_autoloader, $hook_manager, $manifest_reader, $version_selector ) { $this->php_autoloader = $php_autoloader; $this->hook_manager = $hook_manager; $this->manifest_reader = $manifest_reader; $this->version_selector = $version_selector; } /** * Checks to see whether or not an autoloader is currently in the process of initializing. * * @return bool */ public function is_initializing() { // If no version has been set it means that no autoloader has started initializing yet. global $jetpack_autoloader_latest_version; if ( ! isset( $jetpack_autoloader_latest_version ) ) { return false; } // When the version is set but the classmap is not it ALWAYS means that this is the // latest autoloader and is being included by an older one. global $jetpack_packages_classmap; if ( empty( $jetpack_packages_classmap ) ) { return true; } // Version 2.4.0 added a new global and altered the reset semantics. We need to check // the other global as well since it may also point at initialization. // Note: We don't need to check for the class first because every autoloader that // will set the latest version global requires this class in the classmap. $replacing_version = $jetpack_packages_classmap[ AutoloadGenerator::class ]['version']; if ( $this->version_selector->is_dev_version( $replacing_version ) || version_compare( $replacing_version, '2.4.0.0', '>=' ) ) { global $jetpack_autoloader_loader; if ( ! isset( $jetpack_autoloader_loader ) ) { return true; } } return false; } /** * Activates an autoloader using the given plugins and activates it. * * @param string[] $plugins The plugins to initialize the autoloader for. */ public function activate_autoloader( $plugins ) { global $jetpack_packages_psr4; $jetpack_packages_psr4 = array(); $this->manifest_reader->read_manifests( $plugins, 'vendor/composer/jetpack_autoload_psr4.php', $jetpack_packages_psr4 ); global $jetpack_packages_classmap; $jetpack_packages_classmap = array(); $this->manifest_reader->read_manifests( $plugins, 'vendor/composer/jetpack_autoload_classmap.php', $jetpack_packages_classmap ); global $jetpack_packages_filemap; $jetpack_packages_filemap = array(); $this->manifest_reader->read_manifests( $plugins, 'vendor/composer/jetpack_autoload_filemap.php', $jetpack_packages_filemap ); $loader = new Version_Loader( $this->version_selector, $jetpack_packages_classmap, $jetpack_packages_psr4, $jetpack_packages_filemap ); $this->php_autoloader->register_autoloader( $loader ); // Now that the autoloader is active we can load the filemap. $loader->load_filemap(); } /** * Resets the active autoloader and all related global state. */ public function reset_autoloader() { $this->php_autoloader->unregister_autoloader(); $this->hook_manager->reset(); // Clear all of the autoloader globals so that older autoloaders don't do anything strange. global $jetpack_autoloader_latest_version; $jetpack_autoloader_latest_version = null; global $jetpack_packages_classmap; $jetpack_packages_classmap = array(); // Must be array to avoid exceptions in old autoloaders! global $jetpack_packages_psr4; $jetpack_packages_psr4 = array(); // Must be array to avoid exceptions in old autoloaders! global $jetpack_packages_filemap; $jetpack_packages_filemap = array(); // Must be array to avoid exceptions in old autoloaders! } } woocommerce-blocks/vendor/autoload.php 0000644 00000000262 15133551764 0014166 0 ustar 00 <?php // autoload.php @generated by Composer require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInitb78ca910fe07339d6189615f1734a3e3::getLoader(); woocommerce-blocks/packages/checkout/style.scss 0000644 00000000030 15133551764 0015761 0 ustar 00 /* stylelint-disable */ woocommerce-blocks/packages/checkout/totals/index.js 0000644 00000000304 15133551764 0016703 0 ustar 00 export { default as TotalsItem } from './item'; export { default as Subtotal } from './subtotal'; export { default as TotalsTaxes } from './taxes'; export { default as TotalsFees } from './fees'; woocommerce-blocks/packages/checkout/totals/item/style.scss 0000644 00000000610 15133551764 0020231 0 ustar 00 .wc-block-components-totals-item { display: flex; flex-wrap: wrap; margin: em($gap-small) 0 0; width: 100%; &:first-child { margin-top: 0; } } .wc-block-components-totals-item__label { flex-grow: 1; } .wc-block-components-totals-item__value { font-weight: bold; white-space: nowrap; } .wc-block-components-totals-item__description { @include font-size(small); width: 100%; } woocommerce-blocks/packages/checkout/totals/item/index.tsx 0000644 00000003044 15133551764 0020047 0 ustar 00 /** * External dependencies */ import classnames from 'classnames'; import { isValidElement } from '@wordpress/element'; import FormattedMonetaryAmount from '@woocommerce/base-components/formatted-monetary-amount'; import type { ReactElement, ReactNode } from 'react'; import type { Currency } from '@woocommerce/price-format'; /** * Internal dependencies */ import './style.scss'; interface TotalsItemProps { className?: string; currency: Currency; label: string; // Value may be a number, or react node. Numbers are passed to FormattedMonetaryAmount. value: number | ReactNode; description?: ReactNode; } const TotalsItemValue = ( { value, currency, }: Partial< TotalsItemProps > ): ReactElement | null => { if ( isValidElement( value ) ) { return ( <div className="wc-block-components-totals-item__value"> { value } </div> ); } return Number.isFinite( value ) ? ( <FormattedMonetaryAmount className="wc-block-components-totals-item__value" currency={ currency || {} } value={ value as number } /> ) : null; }; const TotalsItem = ( { className, currency, label, value, description, }: TotalsItemProps ): ReactElement => { return ( <div className={ classnames( 'wc-block-components-totals-item', className ) } > <span className="wc-block-components-totals-item__label"> { label } </span> <TotalsItemValue value={ value } currency={ currency } /> <div className="wc-block-components-totals-item__description"> { description } </div> </div> ); }; export default TotalsItem; woocommerce-blocks/packages/checkout/totals/item/stories/index.js 0000644 00000001253 15133551764 0021335 0 ustar 00 /** * External dependencies */ import { text } from '@storybook/addon-knobs'; import { currencyKnob } from '@woocommerce/knobs'; /** * Internal dependencies */ import TotalsItem from '../'; export default { title: 'WooCommerce Blocks/@blocks-checkout/TotalsItem', component: TotalsItem, }; export const Default = () => { const currency = currencyKnob(); const description = text( 'Description', 'This is the amount that will be charged to your card.' ); const label = text( 'Label', 'Amount' ); const value = text( 'Value', '1000' ); return ( <TotalsItem currency={ currency } description={ description } label={ label } value={ value } /> ); }; woocommerce-blocks/packages/checkout/totals/subtotal/index.tsx 0000644 00000002032 15133551764 0020742 0 ustar 00 /** * External dependencies */ import { __ } from '@wordpress/i18n'; import type { Currency } from '@woocommerce/price-format'; import type { ReactElement } from 'react'; import { getSetting } from '@woocommerce/settings'; /** * Internal dependencies */ import TotalsItem from '../item'; interface Values { total_items: string; total_items_tax: string; } interface SubtotalProps { className?: string; currency: Currency; values: Values | Record< string, never >; } const Subtotal = ( { currency, values, className, }: SubtotalProps ): ReactElement => { const { total_items: totalItems, total_items_tax: totalItemsTax } = values; const itemsValue = parseInt( totalItems, 10 ); const itemsTaxValue = parseInt( totalItemsTax, 10 ); return ( <TotalsItem className={ className } currency={ currency } label={ __( 'Subtotal', 'woo-gutenberg-products-block' ) } value={ getSetting( 'displayCartPricesIncludingTax', false ) ? itemsValue + itemsTaxValue : itemsValue } /> ); }; export default Subtotal; woocommerce-blocks/packages/checkout/totals/subtotal/stories/index.js 0000644 00000001144 15133551764 0022233 0 ustar 00 /** * External dependencies */ import { text } from '@storybook/addon-knobs'; import { currencyKnob } from '@woocommerce/knobs'; /** * Internal dependencies */ import Subtotal from '../'; export default { title: 'WooCommerce Blocks/@blocks-checkout/Subtotal', component: Subtotal, }; export const Default = () => { const currency = currencyKnob(); const totalItems = text( 'Total items', '1000' ); const totalItemsTax = text( 'Total items tax', '200' ); return ( <Subtotal currency={ currency } values={ { total_items: totalItems, total_items_tax: totalItemsTax, } } /> ); }; woocommerce-blocks/packages/checkout/totals/fees/index.tsx 0000644 00000002443 15133551764 0020035 0 ustar 00 /** * External dependencies */ import classnames from 'classnames'; import { __ } from '@wordpress/i18n'; import { getSetting } from '@woocommerce/settings'; import type { Currency } from '@woocommerce/price-format'; import type { CartFeeItem } from '@woocommerce/type-defs/cart'; import type { ReactElement } from 'react'; /** * Internal dependencies */ import TotalsItem from '../item'; interface TotalsFeesProps { currency: Currency; cartFees: CartFeeItem[]; className?: string; } const TotalsFees = ( { currency, cartFees, className, }: TotalsFeesProps ): ReactElement | null => { return ( <> { cartFees.map( ( { id, name, totals }, index ) => { const feesValue = parseInt( totals.total, 10 ); if ( ! feesValue ) { return null; } const feesTaxValue = parseInt( totals.total_tax, 10 ); return ( <TotalsItem key={ id || `${ index }-${ name }` } className={ classnames( 'wc-block-components-totals-fees', className ) } currency={ currency } label={ name || __( 'Fee', 'woo-gutenberg-products-block' ) } value={ getSetting( 'displayCartPricesIncludingTax', false ) ? feesValue + feesTaxValue : feesValue } /> ); } ) } </> ); }; export default TotalsFees; woocommerce-blocks/packages/checkout/totals/fees/stories/index.js 0000644 00000001244 15133551764 0021321 0 ustar 00 /** * External dependencies */ import { text } from '@storybook/addon-knobs'; import { currencyKnob } from '@woocommerce/knobs'; /** * Internal dependencies */ import TotalsFees from '../'; export default { title: 'WooCommerce Blocks/@blocks-checkout/TotalsFees', component: TotalsFees, }; export const Default = () => { const currency = currencyKnob(); const totalFees = text( 'Total fee', '1000' ); const totalFeesTax = text( 'Total fee tax', '200' ); return ( <TotalsFees currency={ currency } cartFees={ [ { id: 'fee', name: 'Fee', totals: { total: totalFees, total_tax: totalFeesTax, }, }, ] } /> ); }; woocommerce-blocks/packages/checkout/totals/taxes/style.scss 0000644 00000000273 15133551764 0020424 0 ustar 00 .wc-block-components-totals-item.wc-block-components-totals-taxes__grouped-rate { margin: $gap-smallest 0; &:first-child { margin-top: 0; } &:last-child { margin-bottom: 0; } } woocommerce-blocks/packages/checkout/totals/taxes/stories/index.js 0000644 00000001021 15133551764 0021514 0 ustar 00 /** * External dependencies */ import { text } from '@storybook/addon-knobs'; import { currencyKnob } from '@woocommerce/knobs'; /** * Internal dependencies */ import TotalsTaxes from '../'; export default { title: 'WooCommerce Blocks/@blocks-checkout/TotalsTaxes', component: TotalsTaxes, }; export const Default = () => { const currency = currencyKnob(); const totalTaxes = text( 'Total taxes', '1000' ); return ( <TotalsTaxes currency={ currency } values={ { total_tax: totalTaxes, } } /> ); }; woocommerce-blocks/packages/checkout/totals/taxes/index.tsx 0000644 00000003744 15133551764 0020244 0 ustar 00 /** * External dependencies */ import classnames from 'classnames'; import { __ } from '@wordpress/i18n'; import { getSetting } from '@woocommerce/settings'; import type { Currency } from '@woocommerce/price-format'; import type { CartTotalsTaxLineItem } from '@woocommerce/type-defs/cart'; import { ReactElement } from 'react'; /** * Internal dependencies */ import TotalsItem from '../item'; import './style.scss'; interface Values { tax_lines: CartTotalsTaxLineItem[]; total_tax: string; } interface TotalsTaxesProps { className?: string; currency: Currency; showRateAfterTaxName: boolean; values: Values | Record< string, never >; } const TotalsTaxes = ( { currency, values, className, showRateAfterTaxName, }: TotalsTaxesProps ): ReactElement | null => { const { total_tax: totalTax, tax_lines: taxLines } = values; if ( ! getSetting( 'taxesEnabled', true ) && parseInt( totalTax, 10 ) <= 0 ) { return null; } const showItemisedTaxes = getSetting( 'displayItemizedTaxes', false ) as boolean; const itemisedTaxItems: ReactElement | null = showItemisedTaxes && taxLines.length > 0 ? ( <div className={ classnames( 'wc-block-components-totals-taxes', className ) } > { taxLines.map( ( { name, rate, price }, i ) => { const label = `${ name }${ showRateAfterTaxName ? ` ${ rate }` : '' }`; return ( <TotalsItem key={ `tax-line-${ i }` } className="wc-block-components-totals-taxes__grouped-rate" currency={ currency } label={ label } value={ parseInt( price, 10 ) } /> ); } ) }{ ' ' } </div> ) : null; return showItemisedTaxes ? ( itemisedTaxItems ) : ( <> <TotalsItem className={ classnames( 'wc-block-components-totals-taxes', className ) } currency={ currency } label={ __( 'Taxes', 'woo-gutenberg-products-block' ) } value={ parseInt( totalTax, 10 ) } description={ null } /> </> ); }; export default TotalsTaxes; woocommerce-blocks/packages/checkout/order-shipping-packages/index.js 0000644 00000001721 15133551764 0022107 0 ustar 00 /** * External dependencies */ import classnames from 'classnames'; /** * Internal dependencies */ import { createSlotFill, useSlot } from '../slot'; const slotName = '__experimentalOrderShippingPackages'; const { Fill: ExperimentalOrderShippingPackages, Slot: OrderShippingPackagesSlot, } = createSlotFill( slotName ); const Slot = ( { className, collapsible, noResultsMessage, renderOption, extensions, cart, components, } ) => { const { fills } = useSlot( slotName ); const hasMultiplePackages = fills.length > 1; return ( <OrderShippingPackagesSlot className={ classnames( 'wc-block-components-shipping-rates-control', className ) } fillProps={ { collapsible, collapse: hasMultiplePackages, showItems: hasMultiplePackages, noResultsMessage, renderOption, extensions, cart, components, } } /> ); }; ExperimentalOrderShippingPackages.Slot = Slot; export default ExperimentalOrderShippingPackages; woocommerce-blocks/packages/checkout/discounts-meta/index.js 0000644 00000001461 15133551764 0020341 0 ustar 00 /** * External dependencies */ import classnames from 'classnames'; /** * Internal dependencies */ import { createSlotFill, hasValidFills, useSlot } from '../slot'; import TotalsWrapper from '../wrapper'; const slotName = '__experimentalDiscountsMeta'; const { Fill: ExperimentalDiscountsMeta, Slot: DiscountsMetaSlot, } = createSlotFill( slotName ); const Slot = ( { className, extensions, cart } ) => { const { fills } = useSlot( slotName ); return ( hasValidFills( fills ) && ( <TotalsWrapper slotWrapper={ true }> <DiscountsMetaSlot className={ classnames( className, 'wc-block-components-discounts-meta' ) } fillProps={ { extensions, cart } } /> </TotalsWrapper> ) ); }; ExperimentalDiscountsMeta.Slot = Slot; export default ExperimentalDiscountsMeta; woocommerce-blocks/packages/checkout/panel/style.scss 0000644 00000003156 15133551764 0017074 0 ustar 00 .wc-block-components-panel.has-border { @include with-translucent-border(1px 0); + .wc-block-components-panel.has-border::after { border-top-width: 0; } } .wc-block-components-panel.has-border.no-top-border { @include with-translucent-border(1px 0); &::after { border-top-width: 0; } } .wc-block-components-panel__button { @include reset-box(); height: auto; line-height: 1; margin-top: em(6px); padding-right: #{24px + $gap-smaller}; padding-top: em($gap-small - 6px); position: relative; text-align: left; width: 100%; word-break: break-word; &[aria-expanded="true"] { padding-bottom: em($gap-small - 6px); margin-bottom: em(6px); } &, &:hover, &:focus, &:active { @include reset-typography(); background: transparent; box-shadow: none; } > .wc-block-components-panel__button-icon { fill: currentColor; position: absolute; right: 0; top: 50%; transform: translateY(-50%); width: auto; } } .wc-block-components-panel__content { padding-bottom: em($gap); // Ensures the panel contents are not visible for any theme that tweaked the // `display` property of div elements. &[hidden] { display: none; } } // Extra classes for specificity. .theme-twentytwentyone.theme-twentytwentyone.theme-twentytwentyone .wc-block-components-panel__button { background-color: inherit; color: inherit; } .theme-twentytwenty .wc-block-components-panel__button, .theme-twentyseventeen .wc-block-components-panel__button { background: none transparent; color: inherit; &.wc-block-components-panel__button:hover, &.wc-block-components-panel__button:focus { background: none transparent; } } woocommerce-blocks/packages/checkout/panel/index.tsx 0000644 00000002462 15133551764 0016705 0 ustar 00 /** * External dependencies */ import { useState } from '@wordpress/element'; import type { ReactNode, ReactElement } from 'react'; import classNames from 'classnames'; import { Icon, chevronUp, chevronDown } from '@woocommerce/icons'; /** * Internal dependencies */ import './style.scss'; interface PanelProps { children?: ReactNode; className?: string; initialOpen?: boolean; hasBorder?: boolean; title?: ReactNode; titleTag?: keyof JSX.IntrinsicElements; } const Panel = ( { children, className, initialOpen = false, hasBorder = false, title, titleTag: TitleTag = 'div', }: PanelProps ): ReactElement => { const [ isOpen, setIsOpen ] = useState< boolean >( initialOpen ); return ( <div className={ classNames( className, 'wc-block-components-panel', { 'has-border': hasBorder, } ) } > <TitleTag> <button aria-expanded={ isOpen } className="wc-block-components-panel__button" onClick={ () => setIsOpen( ! isOpen ) } > <Icon aria-hidden="true" className="wc-block-components-panel__button-icon" srcElement={ isOpen ? chevronUp : chevronDown } /> { title } </button> </TitleTag> { isOpen && ( <div className="wc-block-components-panel__content"> { children } </div> ) } </div> ); }; export default Panel; woocommerce-blocks/packages/checkout/wrapper/style.scss 0000644 00000000770 15133551764 0017454 0 ustar 00 .wc-block-components-totals-wrapper { @include with-translucent-border(1px 0 0); padding: $gap 0; &.has-bottom-border { &::after { border-bottom-width: 1px; } } &.slot-wrapper { padding: 0; > * > * { @include with-translucent-border(0 0 1px); padding: $gap 0; &:last-child::after { border-bottom-width: 0; } } } } .wc-block-components-discounts-meta { .wc-block-components-totals-wrapper { &:first-child { @include with-translucent-border(1px 0 0); } } } woocommerce-blocks/packages/checkout/wrapper/index.tsx 0000644 00000001132 15133551764 0017257 0 ustar 00 /** * External dependencies */ import { Children, ReactNode } from 'react'; /** * Internal dependencies */ import './style.scss'; interface TotalsWrapperProps { children: ReactNode; /* If this TotalsWrapper is being used to wrap a Slot */ slotWrapper?: boolean; } const TotalsWrapper = ( { children, slotWrapper = false, }: TotalsWrapperProps ): JSX.Element | null => { return Children.count( children ) ? ( <div className={ `wc-block-components-totals-wrapper${ slotWrapper ? ' slot-wrapper' : '' }` } > { children } </div> ) : null; }; export default TotalsWrapper; woocommerce-blocks/packages/checkout/button/index.ts 0000644 00000000163 15133551764 0016725 0 ustar 00 /** * External dependencies */ import Button from '@woocommerce/base-components/button'; export default Button; woocommerce-blocks/packages/checkout/error-boundary/index.js 0000644 00000001540 15133551764 0020352 0 ustar 00 /** * External dependencies */ import { Component } from 'react'; class CheckoutSlotErrorBoundary extends Component { state = { errorMessage: '', hasError: false }; static getDerivedStateFromError( error ) { if ( typeof error.statusText !== 'undefined' && typeof error.status !== 'undefined' ) { return { errorMessage: ( <> <strong>{ error.status }</strong> { ': ' + error.statusText } </> ), hasError: true, }; } return { errorMessage: error.message, hasError: true }; } render() { const { renderError } = this.props; const { errorMessage, hasError } = this.state; if ( hasError ) { if ( typeof renderError === 'function' ) { return renderError( errorMessage ); } return <p>{ errorMessage }</p>; } return this.props.children; } } export default CheckoutSlotErrorBoundary; woocommerce-blocks/packages/checkout/blocks-registry/utils.ts 0000644 00000006543 15133551764 0020576 0 ustar 00 /** * External dependencies */ import { isObject } from '@woocommerce/types'; /** * Internal dependencies */ import { hasInnerBlocks } from './get-registered-blocks'; /** * Asserts that an option is of the given type. Otherwise, throws an error. * * @throws Will throw an error if the type of the option doesn't match the expected type. */ export const assertType = ( optionName: string, option: unknown, expectedType: unknown ): void => { const actualType = typeof option; if ( actualType !== expectedType ) { throw new Error( `Incorrect value for the ${ optionName } argument when registering a checkout block. It was a ${ actualType }, but must be a ${ expectedType }.` ); } }; /** * Validate the block name. * * @throws Will throw an error if the block name is invalid. */ export const assertBlockName = ( blockName: string ): void => { assertType( 'blockName', blockName, 'string' ); if ( ! blockName ) { throw new Error( `Value for the blockName argument must not be empty.` ); } }; /** * Validate the block parent. * * @throws Will throw an error if the block name is invalid. */ export const assertBlockParent = ( blockParent: string | string[] ): void => { if ( typeof blockParent !== 'string' && ! Array.isArray( blockParent ) ) { throw new Error( `Incorrect value for the parent argument when registering a checkout block. It was a ${ typeof blockParent }, but must be a string or array of strings.` ); } if ( typeof blockParent === 'string' && ! hasInnerBlocks( blockParent ) ) { throw new Error( `When registering a checkout block, the parent must be a valid inner block area.` ); } if ( Array.isArray( blockParent ) && ! blockParent.some( ( parent ) => hasInnerBlocks( parent ) ) ) { throw new Error( `When registering a checkout block, the parent must be a valid inner block area.` ); } }; /** * Asserts that an option is of the given type. Otherwise, throws an error. * * @throws Will throw an error if the type of the option doesn't match the expected type. * @param {Object} options Object containing the option to validate. * @param {string} optionName Name of the option to validate. * @param {string} expectedType Type expected for the option. */ export const assertOption = ( options: unknown, optionName: string, expectedType: string ): void => { if ( ! isObject( options ) ) { return; } const actualType = typeof options[ optionName ]; if ( actualType !== expectedType ) { throw new Error( `Incorrect value for the ${ optionName } argument when registering a block component. It was a ${ actualType }, but must be a ${ expectedType }.` ); } }; /** * Asserts that an option is a valid react element or lazy callback. Otherwise, throws an error. * * @throws Will throw an error if the type of the option doesn't match the expected type. */ export const assertBlockComponent = ( options: Record< string, unknown >, optionName: string ): void => { const optionValue = options[ optionName ]; if ( optionValue ) { if ( typeof optionValue === 'function' ) { return; } if ( isObject( optionValue ) && optionValue.$$typeof && optionValue.$$typeof === Symbol.for( 'react.lazy' ) ) { return; } } throw new Error( `Incorrect value for the ${ optionName } argument when registering a block component. Component must be a valid React Element or Lazy callback.` ); }; woocommerce-blocks/packages/checkout/blocks-registry/register-checkout-block.ts 0000644 00000002210 15133551764 0024140 0 ustar 00 /** * External dependencies */ import { registerBlockComponent } from '@woocommerce/blocks-registry'; /** * Internal dependencies */ import type { CheckoutBlockOptions } from './types'; import { assertBlockName, assertBlockParent, assertOption, assertBlockComponent, } from './utils'; import { registeredBlocks } from './registered-blocks'; /** * Main API for registering a new checkout block within areas. */ export const registerCheckoutBlock = ( options: CheckoutBlockOptions ): void => { assertOption( options, 'metadata', 'object' ); assertBlockName( options.metadata.name ); assertBlockParent( options.metadata.parent ); assertBlockComponent( options, 'component' ); /** * This ensures the frontend component for the checkout block is available. */ registerBlockComponent( { blockName: options.metadata.name as string, component: options.component, } ); /** * Store block metadata for later lookup. */ registeredBlocks[ options.metadata.name ] = { blockName: options.metadata.name, metadata: options.metadata, component: options.component, force: !! options.metadata?.attributes?.lock?.default?.remove, }; }; woocommerce-blocks/packages/checkout/blocks-registry/test/index.js 0000644 00000004072 15133551764 0021505 0 ustar 00 /** * Internal dependencies */ import { getRegisteredBlocks, registerCheckoutBlock, innerBlockAreas, } from '../index'; describe( 'checkout blocks registry', () => { const component = () => { return null; }; describe( 'registerCheckoutBlock', () => { const invokeTest = ( blockName, options ) => () => { return registerCheckoutBlock( blockName, options ); }; it( 'throws an error when registered block is missing `blockName`', () => { expect( invokeTest( { metadata: { name: null, parent: innerBlockAreas.CHECKOUT_FIELDS, }, component, } ) ).toThrowError( /blockName/ ); expect( invokeTest( { metadata: { name: '', parent: innerBlockAreas.CHECKOUT_FIELDS, }, component, } ) ).toThrowError( /blockName/ ); } ); it( 'throws an error when registered block is missing a valid parent', () => { expect( invokeTest( { metadata: { name: 'test/block-name', parent: [], }, component, } ) ).toThrowError( /parent/ ); expect( invokeTest( { metadata: { name: 'test/block-name', parent: 'invalid-parent', }, component, } ) ).toThrowError( /parent/ ); expect( invokeTest( { metadata: { name: 'test/block-name', parent: [ 'invalid-parent', innerBlockAreas.CHECKOUT_FIELDS, ], }, component, } ) ).not.toThrowError( /parent/ ); } ); it( 'throws an error when registered block is missing `component`', () => { expect( invokeTest( { metadata: { name: 'test/block-name', parent: innerBlockAreas.CHECKOUT_FIELDS, }, } ) ).toThrowError( /component/ ); } ); } ); describe( 'getRegisteredBlocks', () => { it( 'gets an empty array when checkout area has no registered blocks', () => { expect( getRegisteredBlocks( innerBlockAreas.CONTACT_INFORMATION ) ).toEqual( [] ); } ); it( 'gets an empty array when the area is not defined', () => { expect( getRegisteredBlocks( 'not-defined' ) ).toEqual( [] ); } ); } ); } ); woocommerce-blocks/packages/checkout/blocks-registry/types.ts 0000644 00000002716 15133551764 0020600 0 ustar 00 /** * External dependencies */ import type { LazyExoticComponent } from 'react'; import type { BlockConfiguration } from '@wordpress/blocks'; export enum innerBlockAreas { CHECKOUT = 'woocommerce/checkout', CHECKOUT_FIELDS = 'woocommerce/checkout-fields-block', CHECKOUT_TOTALS = 'woocommerce/checkout-totals-block', CONTACT_INFORMATION = 'woocommerce/checkout-contact-information-block', SHIPPING_ADDRESS = 'woocommerce/checkout-shipping-address-block', BILLING_ADDRESS = 'woocommerce/checkout-billing-address-block', SHIPPING_METHODS = 'woocommerce/checkout-shipping-methods-block', PAYMENT_METHODS = 'woocommerce/checkout-payment-methods-block', CART = 'woocommerce/cart-i2', EMPTY_CART = 'woocommerce/empty-cart-block', FILLED_CART = 'woocommerce/filled-cart-block', CART_ITEMS = 'woocommerce/cart-items-block', CART_TOTALS = 'woocommerce/cart-totals-block', } interface CheckoutBlockOptionsMetadata extends Partial< BlockConfiguration > { name: string; parent: string[]; } export type RegisteredBlock = { blockName: string; metadata: CheckoutBlockOptionsMetadata; component: | LazyExoticComponent< React.ComponentType< unknown > > | ( () => JSX.Element | null ) | null; force: boolean; }; export type RegisteredBlocks = Record< string, RegisteredBlock >; export type CheckoutBlockOptions = { metadata: CheckoutBlockOptionsMetadata; component: | LazyExoticComponent< React.ComponentType< unknown > > | ( () => JSX.Element | null ) | null; }; woocommerce-blocks/packages/checkout/blocks-registry/registered-blocks.ts 0000644 00000000240 15133551764 0023032 0 ustar 00 /** * Internal dependencies */ import type { RegisteredBlocks } from './types'; const registeredBlocks: RegisteredBlocks = {}; export { registeredBlocks }; woocommerce-blocks/packages/checkout/blocks-registry/get-registered-blocks.ts 0000644 00000001226 15133551764 0023614 0 ustar 00 /** * Internal dependencies */ import { innerBlockAreas, RegisteredBlock } from './types'; import { registeredBlocks } from './registered-blocks'; /** * Check area is valid. */ export const hasInnerBlocks = ( block: string ): block is innerBlockAreas => { return Object.values( innerBlockAreas ).includes( block as innerBlockAreas ); }; /** * Get a list of blocks available within a specific area. */ export const getRegisteredBlocks = ( block: string ): Array< RegisteredBlock > => { return hasInnerBlocks( block ) ? Object.values( registeredBlocks ).filter( ( { metadata } ) => ( metadata?.parent || [] ).includes( block ) ) : []; }; woocommerce-blocks/packages/checkout/blocks-registry/index.ts 0000644 00000000155 15133551764 0020536 0 ustar 00 export * from './get-registered-blocks'; export * from './register-checkout-block'; export * from './types'; woocommerce-blocks/packages/checkout/order-meta/index.js 0000644 00000001423 15133551764 0017437 0 ustar 00 /** * External dependencies */ import classnames from 'classnames'; /** * Internal dependencies */ import { createSlotFill, hasValidFills, useSlot } from '../slot'; import TotalsWrapper from '../wrapper'; const slotName = '__experimentalOrderMeta'; const { Fill: ExperimentalOrderMeta, Slot: OrderMetaSlot } = createSlotFill( slotName ); const Slot = ( { className, extensions, cart } ) => { const { fills } = useSlot( slotName ); return ( hasValidFills( fills ) && ( <TotalsWrapper slotWrapper={ true }> <OrderMetaSlot className={ classnames( className, 'wc-block-components-order-meta' ) } fillProps={ { extensions, cart } } /> </TotalsWrapper> ) ); }; ExperimentalOrderMeta.Slot = Slot; export default ExperimentalOrderMeta; woocommerce-blocks/packages/checkout/slot/index.js 0000644 00000007043 15133551764 0016365 0 ustar 00 /** * External dependencies */ import deprecated from '@wordpress/deprecated'; import { createSlotFill as baseCreateSlotFill, __experimentalUseSlot, useSlot as __useSlot, } from 'wordpress-components'; import { CURRENT_USER_IS_ADMIN } from '@woocommerce/settings'; import { Children, cloneElement } from '@wordpress/element'; /** * Internal dependencies */ import BlockErrorBoundary from '../error-boundary'; /** * This function is used in case __experimentalUseSlot is removed and useSlot is not released, it tries to mock * the return value of that slot. * * @return {Object} The hook mocked return, currently: * fills, a null array of length 2. */ const mockedUseSlot = () => { /** * If we're here, it means useSlot was never graduated and __experimentalUseSlot is removed, so we should change our code. * */ deprecated( '__experimentalUseSlot', { plugin: 'woocommerce-gutenberg-products-block', } ); // We're going to mock its value return { fills: new Array( 2 ), }; }; /** * Checks if this slot has any valid fills. A valid fill is one that isn't falsy. * * @param {Array} fills The list of fills to check for a valid one in. * @return {boolean} True if this slot contains any valid fills. */ export const hasValidFills = ( fills ) => Array.isArray( fills ) && fills.filter( Boolean ).length > 0; /** * A hook that is used inside a slotFillProvider to return information on the a slot. * * @param {string} slotName The slot name to be hooked into. * @return {Object} slot data. */ let useSlot; if ( typeof __useSlot === 'function' ) { useSlot = __useSlot; } else if ( typeof __experimentalUseSlot === 'function' ) { useSlot = __experimentalUseSlot; } else { useSlot = mockedUseSlot; } export { useSlot }; /** * Abstracts @wordpress/components createSlotFill, wraps Fill in an error boundary and passes down fillProps. * * @param {string} slotName The generated slotName, based down to createSlotFill. * @param {null|function(Element):Element} [onError] Returns an element to display the error if the current use is an admin. * * @return {Object} Returns a newly wrapped Fill and Slot. */ export const createSlotFill = ( slotName, onError = null ) => { const { Fill: BaseFill, Slot: BaseSlot } = baseCreateSlotFill( slotName ); /** * A Fill that will get rendered inside associate slot. * If the code inside has a error, it would be caught ad removed. * The error is only visible to admins. * * @param {Object} props Items props. * @param {Array} props.children Children to be rendered. */ const Fill = ( { children } ) => ( <BaseFill> { ( fillProps ) => Children.map( children, ( fill ) => ( <BlockErrorBoundary /* Returning null would trigger the default error display. * Returning () => null would render nothing. */ renderError={ CURRENT_USER_IS_ADMIN ? onError : () => null } > { cloneElement( fill, fillProps ) } </BlockErrorBoundary> ) ) } </BaseFill> ); /** * A Slot that will get rendered inside our tree. * This forces Slot to use the Portal implementation that allows events to be bubbled to react tree instead of dom tree. * * @param {Object} [props] Slot props. * @param {string} props.className Class name to be used on slot. * @param {Object} props.fillProps Props to be passed to fills. * @param {Element|string} props.as Element used to render the slot, defaults to div. * */ const Slot = ( props ) => <BaseSlot { ...props } bubblesVirtually />; return { Fill, Slot, }; }; woocommerce-blocks/packages/checkout/index.js 0000644 00000001143 15133551764 0015377 0 ustar 00 export * from './totals'; export * from './utils'; export * from './slot'; export * from './registry'; export { default as TotalsWrapper } from './wrapper'; export * from './blocks-registry'; export { default as ExperimentalOrderMeta } from './order-meta'; export { default as ExperimentalDiscountsMeta } from './discounts-meta'; export { default as ExperimentalOrderShippingPackages } from './order-shipping-packages'; export { default as Panel } from './panel'; export { SlotFillProvider } from 'wordpress-components'; export { default as Button } from './button'; export { default as Label } from './label'; woocommerce-blocks/packages/checkout/label/index.ts 0000644 00000000160 15133551764 0016466 0 ustar 00 /** * External dependencies */ import Label from '@woocommerce/base-components/label'; export default Label; woocommerce-blocks/packages/checkout/registry/index.ts 0000644 00000007667 15133551764 0017302 0 ustar 00 /** * External dependencies */ import { useMemo } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { CURRENT_USER_IS_ADMIN } from '@woocommerce/settings'; import deprecated from '@wordpress/deprecated'; /** * Internal dependencies */ import { returnTrue } from '../utils'; type CheckoutFilterFunction = < T >( value: T, extensions: Record< string, unknown >, args?: CheckoutFilterArguments ) => T; type CheckoutFilterArguments = | ( Record< string, unknown > & { context?: string; } ) | null; let checkoutFilters: Record< string, Record< string, CheckoutFilterFunction > > = {}; /** * Register filters for a specific extension. */ export const __experimentalRegisterCheckoutFilters = ( namespace: string, filters: Record< string, CheckoutFilterFunction > ): void => { /** * Let developers know snackbarNotices is no longer available as a filter. * * See: https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/4417 */ if ( Object.keys( filters ).includes( 'couponName' ) ) { deprecated( 'snackbarNotices', { alternative: 'snackbarNoticeVisibility', plugin: 'WooCommerce Blocks', link: 'https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/4417', } ); } /** * Let the user know couponName is no longer available as a filter. * * See https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/4312 */ if ( Object.keys( filters ).includes( 'couponName' ) ) { deprecated( 'couponName', { alternative: 'coupons', plugin: 'WooCommerce Blocks', link: 'https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/bb921d21f42e21f38df2b1c87b48e07aa4cb0538/docs/extensibility/available-filters.md#coupons', } ); } checkoutFilters = { ...checkoutFilters, [ namespace ]: filters, }; }; /** * Get all filters with a specific name. * * @param {string} filterName Name of the filter to search for. * @return {Function[]} Array of functions that are registered for that filter * name. */ const getCheckoutFilters = ( filterName: string ): CheckoutFilterFunction[] => { const namespaces = Object.keys( checkoutFilters ); const filters = namespaces .map( ( namespace ) => checkoutFilters[ namespace ][ filterName ] ) .filter( Boolean ); return filters; }; /** * Apply a filter. */ export const __experimentalApplyCheckoutFilter = < T >( { filterName, defaultValue, extensions = null, arg = null, validation = returnTrue, }: { /** Name of the filter to apply. */ filterName: string; /** Default value to filter. */ defaultValue: T; /** Values extend to REST API response. */ extensions?: Record< string, unknown > | null; /** Object containing arguments for the filter function. */ arg?: CheckoutFilterArguments; /** Function that needs to return true when the filtered value is passed in order for the filter to be applied. */ validation?: ( value: T ) => true | Error; } ): T => { return useMemo( () => { const filters = getCheckoutFilters( filterName ); let value = defaultValue; filters.forEach( ( filter ) => { try { const newValue = filter( value, extensions || {}, arg ); if ( typeof newValue !== typeof value ) { throw new Error( sprintf( /* translators: %1$s is the type of the variable passed to the filter function, %2$s is the type of the value returned by the filter function. */ __( 'The type returned by checkout filters must be the same as the type they receive. The function received %1$s but returned %2$s.', 'woo-gutenberg-products-block' ), typeof value, typeof newValue ) ); } value = validation( newValue ) ? newValue : value; } catch ( e ) { if ( CURRENT_USER_IS_ADMIN ) { throw e; } else { // eslint-disable-next-line no-console console.error( e ); } } } ); return value; }, [ filterName, defaultValue, extensions, arg, validation ] ); }; woocommerce-blocks/packages/checkout/registry/test/index.js 0000644 00000004240 15133551764 0020227 0 ustar 00 /** * External dependencies */ import { renderHook } from '@testing-library/react-hooks'; /** * Internal dependencies */ import { __experimentalRegisterCheckoutFilters, __experimentalApplyCheckoutFilter, } from '../'; describe( 'Checkout registry', () => { const filterName = 'loremIpsum'; test( 'should return default value if there are no filters', () => { const value = 'Hello World'; const { result: newValue } = renderHook( () => __experimentalApplyCheckoutFilter( { filterName, defaultValue: value, } ) ); expect( newValue.current ).toBe( value ); } ); test( 'should return filtered value when a filter is registered', () => { const value = 'Hello World'; __experimentalRegisterCheckoutFilters( filterName, { [ filterName ]: ( val, extensions, args ) => val.toUpperCase() + args.punctuationSign, } ); const { result: newValue } = renderHook( () => __experimentalApplyCheckoutFilter( { filterName, defaultValue: value, arg: { punctuationSign: '!', }, } ) ); expect( newValue.current ).toBe( 'HELLO WORLD!' ); } ); test( 'should not return filtered value if validation failed', () => { const value = 'Hello World'; __experimentalRegisterCheckoutFilters( filterName, { [ filterName ]: ( val ) => val.toUpperCase(), } ); const { result: newValue } = renderHook( () => __experimentalApplyCheckoutFilter( { filterName, defaultValue: value, validation: ( val ) => ! val.includes( 'HELLO' ), } ) ); expect( newValue.current ).toBe( value ); } ); test( 'should catch filter errors if user is not an admin', () => { const spy = {}; spy.console = jest .spyOn( console, 'error' ) .mockImplementation( () => {} ); const error = new Error( 'test error' ); const value = 'Hello World'; __experimentalRegisterCheckoutFilters( filterName, { [ filterName ]: () => { throw error; }, } ); const { result: newValue } = renderHook( () => __experimentalApplyCheckoutFilter( { filterName, defaultValue: value, } ) ); expect( spy.console ).toHaveBeenCalledWith( error ); expect( newValue.current ).toBe( value ); spy.console.mockRestore(); } ); } ); woocommerce-blocks/packages/checkout/registry/test/admin.js 0000644 00000002767 15133551764 0020224 0 ustar 00 /** * External dependencies */ import { renderHook } from '@testing-library/react-hooks'; /** * Internal dependencies */ import { __experimentalRegisterCheckoutFilters, __experimentalApplyCheckoutFilter, } from '../'; jest.mock( '@woocommerce/settings', () => { const originalModule = jest.requireActual( '@woocommerce/settings' ); return { // @ts-ignore We know @woocommerce/settings is an object. ...originalModule, CURRENT_USER_IS_ADMIN: true, }; } ); describe( 'Checkout registry (as admin user)', () => { test( 'should throw if the filter throws and user is an admin', () => { const filterName = 'ErrorTestFilter'; const value = 'Hello World'; __experimentalRegisterCheckoutFilters( filterName, { [ filterName ]: () => { throw new Error( 'test error' ); }, } ); const { result } = renderHook( () => __experimentalApplyCheckoutFilter( { filterName, defaultValue: value, } ) ); expect( result.error ).toEqual( Error( 'test error' ) ); } ); test( 'should throw if validation throws and user is an admin', () => { const filterName = 'ValidationTestFilter'; const value = 'Hello World'; __experimentalRegisterCheckoutFilters( filterName, { [ filterName ]: ( val ) => val, } ); const { result } = renderHook( () => __experimentalApplyCheckoutFilter( { filterName, defaultValue: value, validation: () => { throw Error( 'validation error' ); }, } ) ); expect( result.error ).toEqual( Error( 'validation error' ) ); } ); } ); woocommerce-blocks/packages/checkout/utils/extension-cart-update.ts 0000644 00000001451 15133551764 0021667 0 ustar 00 /** * External dependencies */ import { dispatch } from '@wordpress/data'; import { CartResponse } from '@woocommerce/type-defs/cart-response'; import { ExtensionCartUpdateArgs } from '@woocommerce/types'; /** * Internal dependencies */ import { STORE_KEY } from '../../../assets/js/data/cart/constants'; /** * When executed, this will call the cart/extensions endpoint. * The args contains a namespace, so if that extension has registered an update * callback, it will be executed server-side and the new cart will be returned. * The new cart is then received into the client-side store. */ export const extensionCartUpdate = ( args: ExtensionCartUpdateArgs ): Promise< CartResponse > => { const { applyExtensionCartUpdate } = dispatch( STORE_KEY ); return applyExtensionCartUpdate( args ); }; woocommerce-blocks/packages/checkout/utils/index.js 0000644 00000000135 15133551764 0016537 0 ustar 00 export * from './validation'; export { extensionCartUpdate } from './extension-cart-update'; woocommerce-blocks/packages/checkout/utils/validation/index.ts 0000644 00000001324 15133551764 0020704 0 ustar 00 /** * External dependencies */ import { __, sprintf } from '@wordpress/i18n'; /** * Checks if value passed contain passed label. */ export const mustContain = ( value: string, label: string ): true | Error => { if ( ! value.includes( label ) ) { throw Error( sprintf( /* translators: %1$s value passed to filter, %2$s : value that must be included. */ __( 'Returned value must include %1$s, you passed "%2$s"', 'woo-gutenberg-products-block' ), value, label ) ); } return true; }; /** * A function that always return true. * We need to have a single instance of this function so it doesn't * invalidate our memo comparison. */ export const returnTrue = (): true => true; woocommerce-blocks/packages/prices/utils/index.js 0000644 00000000031 15133551764 0016212 0 ustar 00 export * from './price'; woocommerce-blocks/packages/prices/utils/price.ts 0000644 00000006747 15133551764 0016243 0 ustar 00 /** * External dependencies */ import { CURRENCY } from '@woocommerce/settings'; import type { Currency, CurrencyResponse, CartShippingPackageShippingRate, SymbolPosition, } from '@woocommerce/types'; /** * Get currency prefix. */ const getPrefix = ( // Currency symbol. symbol: string, // Position of currency symbol from settings. symbolPosition: SymbolPosition ): string => { const prefixes = { left: symbol, left_space: ' ' + symbol, right: '', right_space: '', }; return prefixes[ symbolPosition ] || ''; }; /** * Get currency suffix. */ const getSuffix = ( // Currency symbol. symbol: string, // Position of currency symbol from settings. symbolPosition: SymbolPosition ): string => { const suffixes = { left: '', left_space: '', right: symbol, right_space: ' ' + symbol, }; return suffixes[ symbolPosition ] || ''; }; /** * Currency information in normalized format from server settings. */ const siteCurrencySettings: Currency = { code: CURRENCY.code, symbol: CURRENCY.symbol, thousandSeparator: CURRENCY.thousandSeparator, decimalSeparator: CURRENCY.decimalSeparator, minorUnit: CURRENCY.precision, prefix: getPrefix( CURRENCY.symbol, CURRENCY.symbolPosition as SymbolPosition ), suffix: getSuffix( CURRENCY.symbol, CURRENCY.symbolPosition as SymbolPosition ), }; /** * Gets currency information in normalized format from an API response or the server. */ export const getCurrencyFromPriceResponse = ( // Currency data object, for example an API response containing currency formatting data. currencyData: | CurrencyResponse | Record< string, never > | CartShippingPackageShippingRate ): Currency => { if ( ! currencyData || typeof currencyData !== 'object' ) { return siteCurrencySettings; } const { currency_code: code, currency_symbol: symbol, currency_thousand_separator: thousandSeparator, currency_decimal_separator: decimalSeparator, currency_minor_unit: minorUnit, currency_prefix: prefix, currency_suffix: suffix, } = currencyData; return { code: code || 'USD', symbol: symbol || '$', thousandSeparator: typeof thousandSeparator === 'string' ? thousandSeparator : ',', decimalSeparator: typeof decimalSeparator === 'string' ? decimalSeparator : '.', minorUnit: Number.isFinite( minorUnit ) ? minorUnit : 2, prefix: typeof prefix === 'string' ? prefix : '$', suffix: typeof suffix === 'string' ? suffix : '', }; }; /** * Gets currency information in normalized format, allowing overrides. */ export const getCurrency = ( currencyData: Partial< Currency > = {} ): Currency => { return { ...siteCurrencySettings, ...currencyData, }; }; /** * Format a price, provided using the smallest unit of the currency, as a * decimal complete with currency symbols using current store settings. */ export const formatPrice = ( // Price in minor unit, e.g. cents. price: number | string, currencyData?: Currency ): string => { if ( price === '' || price === undefined ) { return ''; } const priceInt: number = typeof price === 'number' ? price : parseInt( price, 10 ); if ( ! Number.isFinite( priceInt ) ) { return ''; } const currency: Currency = getCurrency( currencyData ); const formattedPrice: number = priceInt / 10 ** currency.minorUnit; const formattedValue: string = currency.prefix + formattedPrice + currency.suffix; // This uses a textarea to magically decode HTML currency symbols. const txt = document.createElement( 'textarea' ); txt.innerHTML = formattedValue; return txt.value; }; woocommerce-blocks/packages/prices/utils/test/price.js 0000644 00000002371 15133551764 0017175 0 ustar 00 /** * Internal dependencies */ import { formatPrice, getCurrency } from '../price'; describe( 'formatPrice', () => { test.each` value | prefix | suffix | expected ${ 1000 } | ${ '€' } | ${ '' } | ${ '€10' } ${ 1000 } | ${ '' } | ${ '€' } | ${ '10€' } ${ 1000 } | ${ '' } | ${ '$' } | ${ '10$' } ${ '1000' } | ${ '€' } | ${ '' } | ${ '€10' } ${ 0 } | ${ '€' } | ${ '' } | ${ '€0' } ${ '' } | ${ '€' } | ${ '' } | ${ '' } ${ null } | ${ '€' } | ${ '' } | ${ '' } ${ undefined } | ${ '€' } | ${ '' } | ${ '' } `( 'correctly formats price given "$value", "$prefix" prefix, and "$suffix" suffix', ( { value, prefix, suffix, expected } ) => { const formattedPrice = formatPrice( value, getCurrency( { prefix, suffix } ) ); expect( formattedPrice ).toEqual( expected ); } ); test.each` value | expected ${ 1000 } | ${ '$10' } ${ 0 } | ${ '$0' } ${ '' } | ${ '' } ${ null } | ${ '' } ${ undefined } | ${ '' } `( 'correctly formats price given "$value" only', ( { value, expected } ) => { const formattedPrice = formatPrice( value ); expect( formattedPrice ).toEqual( expected ); } ); } ); woocommerce-blocks/packages/prices/index.js 0000644 00000000031 15133551764 0015052 0 ustar 00 export * from './utils'; woocommerce-blocks/woocommerce-gutenberg-products-block.php 0000644 00000021520 15133551764 0020311 0 ustar 00 <?php /** * Plugin Name: WooCommerce Blocks * Plugin URI: https://github.com/woocommerce/woocommerce-gutenberg-products-block * Description: WooCommerce blocks for the Gutenberg editor. * Version: 6.1.0 * Author: Automattic * Author URI: https://woocommerce.com * Text Domain: woo-gutenberg-products-block * Requires at least: 5.8 * Requires PHP: 7.0 * WC requires at least: 5.7 * WC tested up to: 5.7 * * @package WooCommerce\Blocks * @internal This file is only used when running as a feature plugin. */ defined( 'ABSPATH' ) || exit; $minimum_wp_version = '5.8'; if ( ! defined( 'WC_BLOCKS_IS_FEATURE_PLUGIN' ) ) { define( 'WC_BLOCKS_IS_FEATURE_PLUGIN', true ); } /** * Whether notices must be displayed in the current page (plugins and WooCommerce pages). * * @since 2.5.0 */ function should_display_compatibility_notices() { $current_screen = get_current_screen(); if ( ! isset( $current_screen ) ) { return false; } $is_plugins_page = property_exists( $current_screen, 'id' ) && 'plugins' === $current_screen->id; $is_woocommerce_page = property_exists( $current_screen, 'parent_base' ) && 'woocommerce' === $current_screen->parent_base; return $is_plugins_page || $is_woocommerce_page; } if ( version_compare( $GLOBALS['wp_version'], $minimum_wp_version, '<' ) ) { /** * Outputs for an admin notice about running WooCommerce Blocks on outdated WordPress. * * @since 2.5.0 */ function woocommerce_blocks_admin_unsupported_wp_notice() { if ( should_display_compatibility_notices() ) { ?> <div class="notice notice-error"> <p><?php esc_html_e( 'The WooCommerce Blocks feature plugin requires a more recent version of WordPress and has been paused. Please update WordPress to continue enjoying WooCommerce Blocks.', 'woocommerce' ); ?></p> </div> <?php } } add_action( 'admin_notices', 'woocommerce_blocks_admin_unsupported_wp_notice' ); return; } /** * Returns whether the current version is a development version * Note this relies on composer.json version, not plugin version. * Development installs of the plugin don't have a version defined in * composer json. * * @return bool True means the current version is a development version. */ function woocommerce_blocks_is_development_version() { $composer_file = __DIR__ . '/composer.json'; if ( ! is_readable( $composer_file ) ) { return false; } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- including local file $composer_config = json_decode( file_get_contents( $composer_file ), true ); return ! isset( $composer_config['version'] ); } /** * If development version is detected and the Jetpack constant is not defined, show a notice. */ if ( woocommerce_blocks_is_development_version() && ! defined( 'JETPACK_AUTOLOAD_DEV' ) ) { add_action( 'admin_notices', function() { echo '<div class="error"><p>'; printf( /* translators: %1$s is referring to a php constant name, %2$s is referring to the wp-config.php file. */ esc_html__( 'WooCommerce Blocks development mode requires the %1$s constant to be defined and true in your %2$s file. Otherwise you are loading the blocks package from WooCommerce core.', 'woocommerce' ), 'JETPACK_AUTOLOAD_DEV', 'wp-config.php' ); echo '</p></div>'; } ); } /** * Autoload packages. * * The package autoloader includes version information which prevents classes in this feature plugin * conflicting with WooCommerce core. * * We want to fail gracefully if `composer install` has not been executed yet, so we are checking for the autoloader. * If the autoloader is not present, let's log the failure and display a nice admin notice. */ $autoloader = __DIR__ . '/vendor/autoload_packages.php'; if ( is_readable( $autoloader ) ) { require $autoloader; } else { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( // phpcs:ignore sprintf( /* translators: 1: composer command. 2: plugin directory */ esc_html__( 'Your installation of the WooCommerce Blocks feature plugin is incomplete. Please run %1$s within the %2$s directory.', 'woocommerce' ), '`composer install`', '`' . esc_html( str_replace( ABSPATH, '', __DIR__ ) ) . '`' ) ); } /** * Outputs an admin notice if composer install has not been ran. */ add_action( 'admin_notices', function() { ?> <div class="notice notice-error"> <p> <?php printf( /* translators: 1: composer command. 2: plugin directory */ esc_html__( 'Your installation of the WooCommerce Blocks feature plugin is incomplete. Please run %1$s within the %2$s directory.', 'woocommerce' ), '<code>composer install</code>', '<code>' . esc_html( str_replace( ABSPATH, '', __DIR__ ) ) . '</code>' ); ?> </p> </div> <?php } ); return; } add_action( 'plugins_loaded', array( '\Automattic\WooCommerce\Blocks\Package', 'init' ) ); /** * Pre-filters script translations for the given file, script handle and text domain. * * @param string|false|null $translations JSON-encoded translation data. Default null. * @param string|false $file Path to the translation file to load. False if there isn't one. * @param string $handle Name of the script to register a translation domain to. * @param string $domain The text domain. * @return string JSON translations. */ function woocommerce_blocks_get_i18n_data_json( $translations, $file, $handle, $domain ) { if ( 'woo-gutenberg-products-block' !== $domain ) { return $translations; } global $wp_scripts; if ( ! isset( $wp_scripts->registered[ $handle ], $wp_scripts->registered[ $handle ]->src ) ) { return $translations; } $handle_filename = basename( $wp_scripts->registered[ $handle ]->src ); $locale = determine_locale(); $lang_dir = WP_LANG_DIR . '/plugins'; // Translations are always based on the unminified filename. if ( substr( $handle_filename, -7 ) === '.min.js' ) { $handle_filename = substr( $handle_filename, 0, -7 ) . '.js'; } // WordPress 5.0 uses md5 hashes of file paths to associate translation // JSON files with the file they should be included for. This is an md5 // of 'packages/woocommerce-blocks/build/FILENAME.js'. $core_path_md5 = md5( 'packages/woocommerce-blocks/build/' . $handle_filename ); $core_json_file = $lang_dir . '/woocommerce-' . $locale . '-' . $core_path_md5 . '.json'; $json_translations = is_file( $core_json_file ) && is_readable( $core_json_file ) ? file_get_contents( $core_json_file ) : false; // phpcs:ignore if ( ! $json_translations ) { return $translations; } // Rather than short circuit pre_load_script_translations, we will output // core translations using an inline script. This will allow us to continue // to load feature-plugin translations which may exist as well. $output = <<<JS ( function( domain, translations ) { var localeData = translations.locale_data[ domain ] || translations.locale_data.messages; localeData[""].domain = domain; wp.i18n.setLocaleData( localeData, domain ); } )( "{$domain}", {$json_translations} ); JS; printf( "<script type='text/javascript'>\n%s\n</script>\n", $output ); // phpcs:ignore // Finally, short circuit the pre_load_script_translations hook by returning // the translation JSON from the feature plugin, if it exists so this hook // does not run again for the current handle. $path_md5 = md5( 'build/' . $handle_filename ); $json_file = $lang_dir . '/' . $domain . '-' . $locale . '-' . $path_md5 . '.json'; $translations = is_file( $json_file ) && is_readable( $json_file ) ? file_get_contents( $json_file ) : false; // phpcs:ignore if ( $translations ) { return $translations; } // Return valid empty Jed locale. return '{ "locale_data": { "messages": { "": {} } } }'; } add_filter( 'pre_load_script_translations', 'woocommerce_blocks_get_i18n_data_json', 10, 4 ); /** * Filter translations so we can retrieve translations from Core when the original and the translated * texts are the same (which happens when translations are missing). * * @param string $translation Translated text based on WC Blocks translations. * @param string $text Text to translate. * @param string $domain The text domain. * @return string WC Blocks translation. In case it's the same as $text, Core translation. */ function woocommerce_blocks_get_php_translation_from_core( $translation, $text, $domain ) { if ( 'woo-gutenberg-products-block' !== $domain ) { return $translation; } // When translation is the same, that could mean the string is not translated. // In that case, load it from core. if ( $translation === $text ) { return translate( $text, 'woocommerce' ); // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction, WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.TextDomainMismatch } return $translation; } add_filter( 'gettext', 'woocommerce_blocks_get_php_translation_from_core', 10, 3 ); woocommerce-blocks/src/InboxNotifications.php 0000644 00000011665 15133551764 0015472 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks; use Automattic\WooCommerce\Admin\Notes\Note; use Automattic\WooCommerce\Admin\Notes\Notes; /** * A class used to display inbox messages to merchants in the WooCommerce Admin dashboard. * * @package Automattic\WooCommerce\Blocks * @since x.x.x */ class InboxNotifications { const SURFACE_CART_CHECKOUT_NOTE_NAME = 'surface_cart_checkout'; const SURFACE_CART_CHECKOUT_PROBABILITY_OPTION = 'wc_blocks_surface_cart_checkout_probability'; const PERCENT_USERS_TO_TARGET = 50; const INELIGIBLE_EXTENSIONS = [ 'automatewoo', 'mailchimp-for-woocommerce', 'mailpoet', 'klarna-payments-for-woocommerce', 'klarna-checkout-for-woocommerce', 'woocommerce-gutenberg-products-block', // Disallow the notification if the store is using the feature plugin already. 'woocommerce-all-products-for-subscriptions', 'woocommerce-bookings', 'woocommerce-box-office', 'woocommerce-cart-add-ons', 'woocommerce-checkout-add-ons', 'woocommerce-checkout-field-editor', 'woocommerce-conditional-shipping-and-payments', 'woocommerce-dynamic-pricing', 'woocommerce-eu-vat-number', 'woocommerce-follow-up-emails', 'woocommerce-gateway-amazon-payments-advanced', 'woocommerce-gateway-authorize-net-cim', 'woocommerce-google-analytics-pro', 'woocommerce-memberships', 'woocommerce-paypal-payments', 'woocommerce-pre-orders', 'woocommerce-product-bundles', 'woocommerce-shipping-fedex', 'woocommerce-smart-coupons', ]; const ELIGIBLE_COUNTRIES = [ 'GB', 'US', ]; /** * Deletes the note. */ public static function delete_surface_cart_checkout_blocks_notification() { Notes::delete_notes_with_name( self::SURFACE_CART_CHECKOUT_NOTE_NAME ); } /** * Creates a notification letting merchants know about the Cart and Checkout Blocks. */ public static function create_surface_cart_checkout_blocks_notification() { // If this is the feature plugin, then we don't need to do this. This should only show when Blocks is bundled // with WooCommerce Core. if ( Package::feature()->is_feature_plugin_build() ) { return; } if ( ! class_exists( 'Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes' ) ) { return; } if ( ! class_exists( 'WC_Data_Store' ) ) { return; } $data_store = \WC_Data_Store::load( 'admin-note' ); $note_ids = $data_store->get_notes_with_name( self::SURFACE_CART_CHECKOUT_NOTE_NAME ); // Calculate store's eligibility to be shown the notice, starting with whether they have any plugins we know to // be incompatible with Blocks. This check is done before checking if the note exists already because we want to // delete the note if the merchant activates an ineligible plugin. foreach ( self::INELIGIBLE_EXTENSIONS as $extension ) { if ( is_plugin_active( $extension . '/' . $extension . '.php' ) ) { // Delete the notification here, we shouldn't show it if it's not going to work with the merchant's site. self::delete_surface_cart_checkout_blocks_notification(); return; } } foreach ( (array) $note_ids as $note_id ) { $note = Notes::get_note( $note_id ); // Return now because the note already exists. if ( $note->get_name() === self::SURFACE_CART_CHECKOUT_NOTE_NAME ) { return; } } // Next check the store is located in one of the eligible countries. $raw_country = get_option( 'woocommerce_default_country' ); $country = explode( ':', $raw_country )[0]; if ( ! in_array( $country, self::ELIGIBLE_COUNTRIES, true ) ) { return; } // Pick a random number between 1 and 100 and add this to the wp_options table. This can then be used to target // a percentage of users. We do this here so we target a truer percentage of eligible users than if we did it // before checking plugins/country. $existing_probability = get_option( self::SURFACE_CART_CHECKOUT_PROBABILITY_OPTION ); if ( false === $existing_probability ) { $existing_probability = wp_rand( 0, 100 ); add_option( self::SURFACE_CART_CHECKOUT_PROBABILITY_OPTION, $existing_probability ); } // Finally, check if the store's generated % chance is below the % of users we want to surface this to. if ( $existing_probability > self::PERCENT_USERS_TO_TARGET ) { return; } // At this point, the store meets all the criteria to be shown the notice! Woo! $note = new Note(); $note->set_title( __( 'Introducing the Cart and Checkout blocks!', 'woocommerce' ) ); $note->set_content( __( "Increase your store's revenue with the conversion optimized Cart & Checkout WooCommerce blocks available in the WooCommerce Blocks extension.", 'woocommerce' ) ); $note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL ); $note->set_source( 'woo-gutenberg-products-block' ); $note->set_name( self::SURFACE_CART_CHECKOUT_NOTE_NAME ); $note->add_action( 'learn_more', 'Learn More', 'https://woocommerce.com/checkout-blocks/' ); $note->save(); } } woocommerce-blocks/src/Payments/Integrations/PayPal.php 0000644 00000004071 15133551764 0017306 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Payments\Integrations; use WC_Gateway_Paypal; use Automattic\WooCommerce\Blocks\Assets\Api; /** * PayPal Standard payment method integration * * @since 2.6.0 */ final class PayPal extends AbstractPaymentMethodType { /** * Payment method name defined by payment methods extending this class. * * @var string */ protected $name = 'paypal'; /** * An instance of the Asset Api * * @var Api */ private $asset_api; /** * Constructor * * @param Api $asset_api An instance of Api. */ public function __construct( Api $asset_api ) { $this->asset_api = $asset_api; } /** * Initializes the payment method type. */ public function initialize() { $this->settings = get_option( 'woocommerce_paypal_settings', [] ); } /** * Returns if this payment method should be active. If false, the scripts will not be enqueued. * * @return boolean */ public function is_active() { return filter_var( $this->get_setting( 'enabled', false ), FILTER_VALIDATE_BOOLEAN ); } /** * Returns an array of scripts/handles to be registered for this payment method. * * @return array */ public function get_payment_method_script_handles() { $this->asset_api->register_script( 'wc-payment-method-paypal', 'build/wc-payment-method-paypal.js' ); return [ 'wc-payment-method-paypal' ]; } /** * Returns an array of key=>value pairs of data made available to the payment methods script. * * @return array */ public function get_payment_method_data() { return [ 'title' => $this->get_setting( 'title' ), 'description' => $this->get_setting( 'description' ), 'supports' => $this->get_supported_features(), ]; } /** * Returns an array of supported features. * * @return string[] */ public function get_supported_features() { $gateway = new WC_Gateway_Paypal(); $features = array_filter( $gateway->supports, array( $gateway, 'supports' ) ); return apply_filters( '__experimental_woocommerce_blocks_payment_gateway_features_list', $features, $this->get_name() ); } } woocommerce-blocks/src/Payments/Integrations/BankTransfer.php 0000644 00000003261 15133551764 0020500 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Payments\Integrations; use Automattic\WooCommerce\Blocks\Assets\Api; /** * Bank Transfer (BACS) payment method integration * * @since 3.0.0 */ final class BankTransfer extends AbstractPaymentMethodType { /** * Payment method name/id/slug (matches id in WC_Gateway_BACS in core). * * @var string */ protected $name = 'bacs'; /** * An instance of the Asset Api * * @var Api */ private $asset_api; /** * Constructor * * @param Api $asset_api An instance of Api. */ public function __construct( Api $asset_api ) { $this->asset_api = $asset_api; } /** * Initializes the payment method type. */ public function initialize() { $this->settings = get_option( 'woocommerce_bacs_settings', [] ); } /** * Returns if this payment method should be active. If false, the scripts will not be enqueued. * * @return boolean */ public function is_active() { return filter_var( $this->get_setting( 'enabled', false ), FILTER_VALIDATE_BOOLEAN ); } /** * Returns an array of scripts/handles to be registered for this payment method. * * @return array */ public function get_payment_method_script_handles() { $this->asset_api->register_script( 'wc-payment-method-bacs', 'build/wc-payment-method-bacs.js' ); return [ 'wc-payment-method-bacs' ]; } /** * Returns an array of key=>value pairs of data made available to the payment methods script. * * @return array */ public function get_payment_method_data() { return [ 'title' => $this->get_setting( 'title' ), 'description' => $this->get_setting( 'description' ), 'supports' => $this->get_supported_features(), ]; } } woocommerce-blocks/src/Payments/Integrations/AbstractPaymentMethodType.php 0000644 00000005425 15133551764 0023230 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Payments\Integrations; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodTypeInterface; /** * AbstractPaymentMethodType class. * * @since 2.6.0 */ abstract class AbstractPaymentMethodType implements PaymentMethodTypeInterface { /** * Payment method name defined by payment methods extending this class. * * @var string */ protected $name = ''; /** * Settings from the WP options table * * @var array */ protected $settings = []; /** * Get a setting from the settings array if set. * * @param string $name Setting name. * @param mixed $default Value that is returned if the setting does not exist. * @return mixed */ protected function get_setting( $name, $default = '' ) { return isset( $this->settings[ $name ] ) ? $this->settings[ $name ] : $default; } /** * Returns the name of the payment method. */ public function get_name() { return $this->name; } /** * Returns if this payment method should be active. If false, the scripts will not be enqueued. * * @return boolean */ public function is_active() { return true; } /** * Returns an array of script handles to enqueue for this payment method in * the frontend context * * @return string[] */ public function get_payment_method_script_handles() { return []; } /** * Returns an array of script handles to enqueue for this payment method in * the admin context * * @return string[] */ public function get_payment_method_script_handles_for_admin() { return $this->get_payment_method_script_handles(); } /** * Returns an array of supported features. * * @return string[] */ public function get_supported_features() { return [ 'products' ]; } /** * An array of key, value pairs of data made available to payment methods * client side. * * @return array */ public function get_payment_method_data() { return []; } /** * Returns an array of script handles to enqueue in the frontend context. * * Alias of get_payment_method_script_handles. Defined by IntegrationInterface. * * @return string[] */ public function get_script_handles() { return $this->get_payment_method_script_handles(); } /** * Returns an array of script handles to enqueue in the admin context. * * Alias of get_payment_method_script_handles_for_admin. Defined by IntegrationInterface. * * @return string[] */ public function get_editor_script_handles() { return $this->get_payment_method_script_handles_for_admin(); } /** * An array of key, value pairs of data made available to the block on the client side. * * Alias of get_payment_method_data. Defined by IntegrationInterface. * * @return array */ public function get_script_data() { return $this->get_payment_method_data(); } } woocommerce-blocks/src/Payments/Integrations/CashOnDelivery.php 0000644 00000004756 15133551764 0021011 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Payments\Integrations; use Automattic\WooCommerce\Blocks\Assets\Api; /** * Cash on Delivery (COD) payment method integration * * @since 3.0.0 */ final class CashOnDelivery extends AbstractPaymentMethodType { /** * Payment method name/id/slug (matches id in WC_Gateway_COD in core). * * @var string */ protected $name = 'cod'; /** * An instance of the Asset Api * * @var Api */ private $asset_api; /** * Constructor * * @param Api $asset_api An instance of Api. */ public function __construct( Api $asset_api ) { $this->asset_api = $asset_api; } /** * Initializes the payment method type. */ public function initialize() { $this->settings = get_option( 'woocommerce_cod_settings', [] ); } /** * Returns if this payment method should be active. If false, the scripts will not be enqueued. * * @return boolean */ public function is_active() { return filter_var( $this->get_setting( 'enabled', false ), FILTER_VALIDATE_BOOLEAN ); } /** * Return enable_for_virtual option. * * @return boolean True if store allows COD payment for orders containing only virtual products. */ private function get_enable_for_virtual() { return filter_var( $this->get_setting( 'enable_for_virtual', false ), FILTER_VALIDATE_BOOLEAN ); } /** * Return enable_for_methods option. * * @return array Array of shipping methods (string ids) that allow COD. (If empty, all support COD.) */ private function get_enable_for_methods() { $enable_for_methods = $this->get_setting( 'enable_for_methods', [] ); if ( '' === $enable_for_methods ) { return []; } return $enable_for_methods; } /** * Returns an array of scripts/handles to be registered for this payment method. * * @return array */ public function get_payment_method_script_handles() { $this->asset_api->register_script( 'wc-payment-method-cod', 'build/wc-payment-method-cod.js' ); return [ 'wc-payment-method-cod' ]; } /** * Returns an array of key=>value pairs of data made available to the payment methods script. * * @return array */ public function get_payment_method_data() { return [ 'title' => $this->get_setting( 'title' ), 'description' => $this->get_setting( 'description' ), 'enableForVirtual' => $this->get_enable_for_virtual(), 'enableForShippingMethods' => $this->get_enable_for_methods(), 'supports' => $this->get_supported_features(), ]; } } woocommerce-blocks/src/Payments/Integrations/Stripe.php 0000644 00000026470 15133551764 0017375 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Payments\Integrations; use Exception; use WC_Stripe_Payment_Request; use WC_Stripe_Helper; use WC_Gateway_Stripe; use Automattic\WooCommerce\Blocks\Assets\Api; use Automattic\WooCommerce\Blocks\Payments\PaymentContext; use Automattic\WooCommerce\Blocks\Payments\PaymentResult; /** * Stripe payment method integration * * Temporary integration of the stripe payment method for the new cart and * checkout blocks. Once the api is demonstrated to be stable, this integration * will be moved to the Stripe extension * * @since 2.6.0 */ final class Stripe extends AbstractPaymentMethodType { /** * Payment method name defined by payment methods extending this class. * * @var string */ protected $name = 'stripe'; /** * An instance of the Asset Api * * @var Api */ private $asset_api; /** * Constructor * * @param Api $asset_api An instance of Api. */ public function __construct( Api $asset_api ) { $this->asset_api = $asset_api; add_action( 'woocommerce_rest_checkout_process_payment_with_context', [ $this, 'add_payment_request_order_meta' ], 8, 2 ); add_action( 'woocommerce_rest_checkout_process_payment_with_context', [ $this, 'add_stripe_intents' ], 9999, 2 ); } /** * Initializes the payment method type. */ public function initialize() { $this->settings = get_option( 'woocommerce_stripe_settings', [] ); } /** * Returns if this payment method should be active. If false, the scripts will not be enqueued. * * @return boolean */ public function is_active() { return ! empty( $this->settings['enabled'] ) && 'yes' === $this->settings['enabled']; } /** * Returns an array of scripts/handles to be registered for this payment method. * * @return array */ public function get_payment_method_script_handles() { $this->asset_api->register_script( 'wc-payment-method-stripe', 'build/wc-payment-method-stripe.js', [] ); return [ 'wc-payment-method-stripe' ]; } /** * Returns an array of key=>value pairs of data made available to the payment methods script. * * @return array */ public function get_payment_method_data() { return [ 'stripeTotalLabel' => $this->get_total_label(), 'publicKey' => $this->get_publishable_key(), 'allowPrepaidCard' => $this->get_allow_prepaid_card(), 'title' => $this->get_title(), 'button' => [ 'type' => $this->get_button_type(), 'theme' => $this->get_button_theme(), 'height' => $this->get_button_height(), 'locale' => $this->get_button_locale(), ], 'inline_cc_form' => $this->get_inline_cc_form(), 'icons' => $this->get_icons(), 'showSavedCards' => $this->get_show_saved_cards(), 'allowPaymentRequest' => $this->get_allow_payment_request(), 'showSaveOption' => $this->get_show_save_option(), 'supports' => $this->get_supported_features(), ]; } /** * Determine if store allows cards to be saved during checkout. * * @return bool True if merchant allows shopper to save card (payment method) during checkout). */ private function get_show_saved_cards() { return isset( $this->settings['saved_cards'] ) ? 'yes' === $this->settings['saved_cards'] : false; } /** * Determine if the checkbox to enable the user to save their payment method should be shown. * * @return bool True if the save payment checkbox should be displayed to the user. */ private function get_show_save_option() { $saved_cards = $this->get_show_saved_cards(); // This assumes that Stripe supports `tokenization` - currently this is true, based on // https://github.com/woocommerce/woocommerce-gateway-stripe/blob/master/includes/class-wc-gateway-stripe.php#L95 . // See https://github.com/woocommerce/woocommerce-gateway-stripe/blob/ad19168b63df86176cbe35c3e95203a245687640/includes/class-wc-gateway-stripe.php#L271 and // https://github.com/woocommerce/woocommerce/wiki/Payment-Token-API . return apply_filters( 'wc_stripe_display_save_payment_method_checkbox', filter_var( $saved_cards, FILTER_VALIDATE_BOOLEAN ) ); } /** * Returns the label to use accompanying the total in the stripe statement. * * @return string Statement descriptor. */ private function get_total_label() { return ! empty( $this->settings['statement_descriptor'] ) ? WC_Stripe_Helper::clean_statement_descriptor( $this->settings['statement_descriptor'] ) : ''; } /** * Returns the publishable api key for the Stripe service. * * @return string Public api key. */ private function get_publishable_key() { $test_mode = ( ! empty( $this->settings['testmode'] ) && 'yes' === $this->settings['testmode'] ); $setting_key = $test_mode ? 'test_publishable_key' : 'publishable_key'; return ! empty( $this->settings[ $setting_key ] ) ? $this->settings[ $setting_key ] : ''; } /** * Returns whether to allow prepaid cards for payments. * * @return bool True means to allow prepaid card (default). */ private function get_allow_prepaid_card() { return apply_filters( 'wc_stripe_allow_prepaid_card', true ); } /** * Returns the title string to use in the UI (customisable via admin settings screen). * * @return string Title / label string */ private function get_title() { return isset( $this->settings['title'] ) ? $this->settings['title'] : __( 'Credit / Debit Card', 'woocommerce' ); } /** * Determine if store allows Payment Request buttons - e.g. Apple Pay / Chrome Pay. * * @return bool True if merchant has opted into payment request. */ private function get_allow_payment_request() { $option = isset( $this->settings['payment_request'] ) ? $this->settings['payment_request'] : false; return filter_var( $option, FILTER_VALIDATE_BOOLEAN ); } /** * Return the button type for the payment button. * * @return string Defaults to 'default'. */ private function get_button_type() { return isset( $this->settings['payment_request_button_type'] ) ? $this->settings['payment_request_button_type'] : 'default'; } /** * Return the theme to use for the payment button. * * @return string Defaults to 'dark'. */ private function get_button_theme() { return isset( $this->settings['payment_request_button_theme'] ) ? $this->settings['payment_request_button_theme'] : 'dark'; } /** * Return the height for the payment button. * * @return string A pixel value for the height (defaults to '64'). */ private function get_button_height() { return isset( $this->settings['payment_request_button_height'] ) ? str_replace( 'px', '', $this->settings['payment_request_button_height'] ) : '64'; } /** * Return the inline cc option. * * @return boolean True if the inline CC form option is enabled. */ private function get_inline_cc_form() { return isset( $this->settings['inline_cc_form'] ) && 'yes' === $this->settings['inline_cc_form']; } /** * Return the locale for the payment button. * * @return string Defaults to en_US. */ private function get_button_locale() { return apply_filters( 'wc_stripe_payment_request_button_locale', substr( get_locale(), 0, 2 ) ); } /** * Return the icons urls. * * @return array Arrays of icons metadata. */ private function get_icons() { $icons_src = [ 'visa' => [ 'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/visa.svg', 'alt' => __( 'Visa', 'woocommerce' ), ], 'amex' => [ 'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/amex.svg', 'alt' => __( 'American Express', 'woocommerce' ), ], 'mastercard' => [ 'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/mastercard.svg', 'alt' => __( 'Mastercard', 'woocommerce' ), ], ]; if ( 'USD' === get_woocommerce_currency() ) { $icons_src['discover'] = [ 'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/discover.svg', 'alt' => __( 'Discover', 'woocommerce' ), ]; $icons_src['jcb'] = [ 'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/jcb.svg', 'alt' => __( 'JCB', 'woocommerce' ), ]; $icons_src['diners'] = [ 'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/diners.svg', 'alt' => __( 'Diners', 'woocommerce' ), ]; } return $icons_src; } /** * Add payment request data to the order meta as hooked on the * woocommerce_rest_checkout_process_payment_with_context action. * * @param PaymentContext $context Holds context for the payment. * @param PaymentResult $result Result object for the payment. */ public function add_payment_request_order_meta( PaymentContext $context, PaymentResult &$result ) { $data = $context->payment_data; if ( ! empty( $data['payment_request_type'] ) && 'stripe' === $context->payment_method ) { // phpcs:ignore WordPress.Security.NonceVerification $post_data = $_POST; $_POST = $context->payment_data; $this->add_order_meta( $context->order, $data['payment_request_type'] ); $_POST = $post_data; } // hook into stripe error processing so that we can capture the error to // payment details (which is added to notices and thus not helpful for // this context). if ( 'stripe' === $context->payment_method ) { add_action( 'wc_gateway_stripe_process_payment_error', function( $error ) use ( &$result ) { $payment_details = $result->payment_details; $payment_details['errorMessage'] = wp_strip_all_tags( $error->getLocalizedMessage() ); $result->set_payment_details( $payment_details ); } ); } } /** * Handles any potential stripe intents on the order that need handled. * * This is configured to execute after legacy payment processing has * happened on the woocommerce_rest_checkout_process_payment_with_context * action hook. * * @param PaymentContext $context Holds context for the payment. * @param PaymentResult $result Result object for the payment. */ public function add_stripe_intents( PaymentContext $context, PaymentResult &$result ) { if ( 'stripe' === $context->payment_method && ( ! empty( $result->payment_details['payment_intent_secret'] ) || ! empty( $result->payment_details['setup_intent_secret'] ) ) ) { $payment_details = $result->payment_details; $payment_details['verification_endpoint'] = add_query_arg( [ 'order' => $context->order->get_id(), 'nonce' => wp_create_nonce( 'wc_stripe_confirm_pi' ), 'redirect_to' => rawurlencode( $result->redirect_url ), ], home_url() . \WC_Ajax::get_endpoint( 'wc_stripe_verify_intent' ) ); $result->set_payment_details( $payment_details ); $result->set_status( 'success' ); } } /** * Handles adding information about the payment request type used to the order meta. * * @param \WC_Order $order The order being processed. * @param string $payment_request_type The payment request type used for payment. */ private function add_order_meta( \WC_Order $order, string $payment_request_type ) { if ( 'apple_pay' === $payment_request_type ) { $order->set_payment_method_title( 'Apple Pay (Stripe)' ); $order->save(); } if ( 'payment_request_api' === $payment_request_type ) { $order->set_payment_method_title( 'Chrome Payment Request (Stripe)' ); $order->save(); } } /** * Returns an array of supported features. * * @return string[] */ public function get_supported_features() { $gateway = new WC_Gateway_Stripe(); return array_filter( $gateway->supports, array( $gateway, 'supports' ) ); } } woocommerce-blocks/src/Payments/Integrations/Cheque.php 0000644 00000003266 15133551764 0017337 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Payments\Integrations; use Exception; use Automattic\WooCommerce\Blocks\Assets\Api; /** * Cheque payment method integration * * @since 2.6.0 */ final class Cheque extends AbstractPaymentMethodType { /** * Payment method name defined by payment methods extending this class. * * @var string */ protected $name = 'cheque'; /** * An instance of the Asset Api * * @var Api */ private $asset_api; /** * Constructor * * @param Api $asset_api An instance of Api. */ public function __construct( Api $asset_api ) { $this->asset_api = $asset_api; } /** * Initializes the payment method type. */ public function initialize() { $this->settings = get_option( 'woocommerce_cheque_settings', [] ); } /** * Returns if this payment method should be active. If false, the scripts will not be enqueued. * * @return boolean */ public function is_active() { return filter_var( $this->get_setting( 'enabled', false ), FILTER_VALIDATE_BOOLEAN ); } /** * Returns an array of scripts/handles to be registered for this payment method. * * @return array */ public function get_payment_method_script_handles() { $this->asset_api->register_script( 'wc-payment-method-cheque', 'build/wc-payment-method-cheque.js' ); return [ 'wc-payment-method-cheque' ]; } /** * Returns an array of key=>value pairs of data made available to the payment methods script. * * @return array */ public function get_payment_method_data() { return [ 'title' => $this->get_setting( 'title' ), 'description' => $this->get_setting( 'description' ), 'supports' => $this->get_supported_features(), ]; } } woocommerce-blocks/src/Payments/PaymentContext.php 0000644 00000003471 15133551764 0016437 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Payments; /** * PaymentContext class. */ class PaymentContext { /** * Payment method ID. * * @var string */ protected $payment_method = ''; /** * Order object for the order being paid. * * @var \WC_Order */ protected $order; /** * Holds data to send to the payment gateway to support payment. * * @var array Key value pairs. */ protected $payment_data = []; /** * Magic getter for protected properties. * * @param string $name Property name. */ public function __get( $name ) { if ( in_array( $name, [ 'payment_method', 'order', 'payment_data' ], true ) ) { return $this->$name; } return null; } /** * Set the chosen payment method ID context. * * @param string $payment_method Payment method ID. */ public function set_payment_method( $payment_method ) { $this->payment_method = (string) $payment_method; } /** * Retrieve the payment method instance for the current set payment method. * * @return {\WC_Payment_Gateway|null} An instance of the payment gateway if it exists. */ public function get_payment_method_instance() { $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); if ( ! isset( $available_gateways[ $this->payment_method ] ) ) { return; } return $available_gateways[ $this->payment_method ]; } /** * Set the order context. * * @param \WC_Order $order Order object. */ public function set_order( \WC_Order $order ) { $this->order = $order; } /** * Set payment data context. * * @param array $payment_data Array of key value pairs of data. */ public function set_payment_data( $payment_data = [] ) { $this->payment_data = []; foreach ( $payment_data as $key => $value ) { $this->payment_data[ (string) $key ] = (string) $value; } } } woocommerce-blocks/src/Payments/PaymentMethodRegistry.php 0000644 00000003520 15133551764 0017757 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Payments; use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry; /** * Class used for interacting with payment method types. * * @since 2.6.0 */ final class PaymentMethodRegistry extends IntegrationRegistry { /** * Integration identifier is used to construct hook names and is given when the integration registry is initialized. * * @var string */ protected $registry_identifier = 'payment_method_type'; /** * Retrieves all registered payment methods that are also active. * * @return PaymentMethodTypeInterface[] */ public function get_all_active_registered() { return array_filter( $this->get_all_registered(), function( $payment_method ) { return $payment_method->is_active(); } ); } /** * Gets an array of all registered payment method script handles, but only for active payment methods. * * @return string[] */ public function get_all_active_payment_method_script_dependencies() { $script_handles = []; $payment_methods = $this->get_all_active_registered(); foreach ( $payment_methods as $payment_method ) { $script_handles = array_merge( $script_handles, is_admin() ? $payment_method->get_payment_method_script_handles_for_admin() : $payment_method->get_payment_method_script_handles() ); } return array_unique( array_filter( $script_handles ) ); } /** * Gets an array of all registered payment method script data, but only for active payment methods. * * @return array */ public function get_all_registered_script_data() { $script_data = []; $payment_methods = $this->get_all_active_registered(); foreach ( $payment_methods as $payment_method ) { $script_data[ $payment_method->get_name() . '_data' ] = $payment_method->get_payment_method_data(); } return array_filter( $script_data ); } } woocommerce-blocks/src/Payments/PaymentMethodTypeInterface.php 0000644 00000002012 15133551764 0020704 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Payments; use Automattic\WooCommerce\Blocks\Integrations\IntegrationInterface; interface PaymentMethodTypeInterface extends IntegrationInterface { /** * Returns if this payment method should be active. If false, the scripts will not be enqueued. * * @return boolean */ public function is_active(); /** * Returns an array of script handles to enqueue for this payment method in * the frontend context * * @return string[] */ public function get_payment_method_script_handles(); /** * Returns an array of script handles to enqueue for this payment method in * the admin context * * @return string[] */ public function get_payment_method_script_handles_for_admin(); /** * An array of key, value pairs of data made available to payment methods * client side. * * @return array */ public function get_payment_method_data(); /** * Get array of supported features. * * @return string[] */ public function get_supported_features(); } woocommerce-blocks/src/Payments/Api.php 0000644 00000022062 15133551764 0014163 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Payments; use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\NoticeHandler; use Automattic\WooCommerce\Blocks\Payments\Integrations\Stripe; use Automattic\WooCommerce\Blocks\Payments\Integrations\Cheque; use Automattic\WooCommerce\Blocks\Payments\Integrations\PayPal; use Automattic\WooCommerce\Blocks\Payments\Integrations\BankTransfer; use Automattic\WooCommerce\Blocks\Payments\Integrations\CashOnDelivery; /** * The Api class provides an interface to payment method registration. * * @since 2.6.0 */ class Api { /** * Reference to the PaymentMethodRegistry instance. * * @var PaymentMethodRegistry */ private $payment_method_registry; /** * Reference to the AssetDataRegistry instance. * * @var AssetDataRegistry */ private $asset_registry; /** * Constructor * * @param PaymentMethodRegistry $payment_method_registry An instance of Payment Method Registry. * @param AssetDataRegistry $asset_registry Used for registering data to pass along to the request. */ public function __construct( PaymentMethodRegistry $payment_method_registry, AssetDataRegistry $asset_registry ) { $this->payment_method_registry = $payment_method_registry; $this->asset_registry = $asset_registry; $this->init(); } /** * Initialize class features. */ protected function init() { add_action( 'init', array( $this->payment_method_registry, 'initialize' ), 5 ); add_filter( 'woocommerce_blocks_register_script_dependencies', array( $this, 'add_payment_method_script_dependencies' ), 10, 2 ); add_action( 'woocommerce_blocks_checkout_enqueue_data', array( $this, 'add_payment_method_script_data' ) ); add_action( 'woocommerce_blocks_cart_enqueue_data', array( $this, 'add_payment_method_script_data' ) ); add_action( 'woocommerce_blocks_payment_method_type_registration', array( $this, 'register_payment_method_integrations' ) ); add_action( 'woocommerce_rest_checkout_process_payment_with_context', array( $this, 'process_legacy_payment' ), 999, 2 ); add_action( 'wp_print_scripts', array( $this, 'verify_payment_methods_dependencies' ), 1 ); } /** * Add payment method script handles as script dependencies. * * @param array $dependencies Array of script dependencies. * @param string $handle Script handle. * @return array */ public function add_payment_method_script_dependencies( $dependencies, $handle ) { if ( ! in_array( $handle, [ 'wc-checkout-block', 'wc-checkout-block-frontend', 'wc-cart-block', 'wc-cart-block-frontend', 'wc-cart-i2-block', 'wc-cart-i2-block-frontend' ], true ) ) { return $dependencies; } return array_merge( $dependencies, $this->payment_method_registry->get_all_active_payment_method_script_dependencies() ); } /** * Returns true if the payment gateway is enabled. * * @param object $gateway Payment gateway. * @return boolean */ private function is_payment_gateway_enabled( $gateway ) { return filter_var( $gateway->enabled, FILTER_VALIDATE_BOOLEAN ); } /** * Add payment method data to Asset Registry. */ public function add_payment_method_script_data() { // Enqueue the order of enabled gateways as `paymentGatewaySortOrder`. if ( ! $this->asset_registry->exists( 'paymentGatewaySortOrder' ) ) { $payment_gateways = WC()->payment_gateways->payment_gateways(); $enabled_gateways = array_filter( $payment_gateways, array( $this, 'is_payment_gateway_enabled' ) ); $this->asset_registry->add( 'paymentGatewaySortOrder', array_keys( $enabled_gateways ) ); } // Enqueue all registered gateway data (settings/config etc). $script_data = $this->payment_method_registry->get_all_registered_script_data(); foreach ( $script_data as $asset_data_key => $asset_data_value ) { if ( ! $this->asset_registry->exists( $asset_data_key ) ) { $this->asset_registry->add( $asset_data_key, $asset_data_value ); } } } /** * Register payment method integrations bundled with blocks. * * @param PaymentMethodRegistry $payment_method_registry Payment method registry instance. */ public function register_payment_method_integrations( PaymentMethodRegistry $payment_method_registry ) { // This is temporarily registering Stripe until it's moved to the extension. if ( class_exists( '\WC_Stripe', false ) && ! $payment_method_registry->is_registered( 'stripe' ) ) { $payment_method_registry->register( Package::container()->get( Stripe::class ) ); } $payment_method_registry->register( Package::container()->get( Cheque::class ) ); $payment_method_registry->register( Package::container()->get( PayPal::class ) ); $payment_method_registry->register( Package::container()->get( BankTransfer::class ) ); $payment_method_registry->register( Package::container()->get( CashOnDelivery::class ) ); } /** * Attempt to process a payment for the checkout API if no payment methods support the * woocommerce_rest_checkout_process_payment_with_context action. * * @param PaymentContext $context Holds context for the payment. * @param PaymentResult $result Result of the payment. */ public function process_legacy_payment( PaymentContext $context, PaymentResult &$result ) { if ( $result->status ) { return; } // phpcs:ignore WordPress.Security.NonceVerification $post_data = $_POST; // Set constants. wc_maybe_define_constant( 'WOOCOMMERCE_CHECKOUT', true ); // Add the payment data from the API to the POST global. $_POST = $context->payment_data; // Call the process payment method of the chosen gateway. $payment_method_object = $context->get_payment_method_instance(); if ( ! $payment_method_object instanceof \WC_Payment_Gateway ) { return; } $payment_method_object->validate_fields(); // If errors were thrown, we need to abort. NoticeHandler::convert_notices_to_exceptions( 'woocommerce_rest_payment_error' ); // Process Payment. $gateway_result = $payment_method_object->process_payment( $context->order->get_id() ); // Restore $_POST data. $_POST = $post_data; // If `process_payment` added notices, clear them. Notices are not displayed from the API -- payment should fail, // and a generic notice will be shown instead if payment failed. wc_clear_notices(); // Handle result. $result->set_status( isset( $gateway_result['result'] ) && 'success' === $gateway_result['result'] ? 'success' : 'failure' ); // set payment_details from result. $result->set_payment_details( array_merge( $result->payment_details, $gateway_result ) ); $result->set_redirect_url( $gateway_result['redirect'] ); } /** * Verify all dependencies of registered payment methods have been registered. * If not, remove that payment method script from the list of dependencies * of Cart and Checkout block scripts so it doesn't break the blocks and show * an error in the admin. */ public function verify_payment_methods_dependencies() { $wp_scripts = wp_scripts(); $payment_method_scripts = $this->payment_method_registry->get_all_active_payment_method_script_dependencies(); foreach ( $payment_method_scripts as $payment_method_script ) { if ( ! array_key_exists( $payment_method_script, $wp_scripts->registered ) || ! property_exists( $wp_scripts->registered[ $payment_method_script ], 'deps' ) ) { continue; } $deps = $wp_scripts->registered[ $payment_method_script ]->deps; foreach ( $deps as $dep ) { if ( ! wp_script_is( $dep, 'registered' ) ) { $error_handle = $dep . '-dependency-error'; $error_message = sprintf( 'Payment gateway with handle \'%1$s\' has been deactivated in Cart and Checkout blocks because its dependency \'%2$s\' is not registered. Read the docs about registering assets for payment methods: https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/trunk/docs/extensibility/payment-method-integration.md#registering-assets', esc_html( $payment_method_script ), esc_html( $dep ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log( $error_message ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter,WordPress.WP.EnqueuedResourceParameters.MissingVersion wp_register_script( $error_handle, '' ); wp_enqueue_script( $error_handle ); wp_add_inline_script( $error_handle, sprintf( 'console.error( "%s" );', $error_message ) ); $cart_checkout_scripts = [ 'wc-cart-block', 'wc-cart-block-frontend', 'wc-checkout-block', 'wc-checkout-block-frontend', 'wc-cart-i2-block', 'wc-cart-i2-block-frontend' ]; foreach ( $cart_checkout_scripts as $script_handle ) { if ( ! array_key_exists( $script_handle, $wp_scripts->registered ) || ! property_exists( $wp_scripts->registered[ $script_handle ], 'deps' ) ) { continue; } // Remove payment method script from dependencies. $wp_scripts->registered[ $script_handle ]->deps = array_diff( $wp_scripts->registered[ $script_handle ]->deps, [ $payment_method_script ] ); } } } } } } woocommerce-blocks/src/Payments/PaymentResult.php 0000644 00000003726 15133551764 0016274 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Payments; /** * PaymentResult class. */ class PaymentResult { /** * List of valid payment statuses. * * @var array */ protected $valid_statuses = [ 'success', 'failure', 'pending', 'error' ]; /** * Current payment status. * * @var string */ protected $status = ''; /** * Array of details about the payment. * * @var string */ protected $payment_details = []; /** * Redirect URL for checkout. * * @var string */ protected $redirect_url = ''; /** * Constructor. * * @param string $status Sets the payment status for the result. */ public function __construct( $status = '' ) { if ( $status ) { $this->set_status( $status ); } } /** * Magic getter for protected properties. * * @param string $name Property name. */ public function __get( $name ) { if ( in_array( $name, [ 'status', 'payment_details', 'redirect_url' ], true ) ) { return $this->$name; } return null; } /** * Set payment status. * * @throws \Exception When an invalid status is provided. * * @param string $payment_status Status to set. */ public function set_status( $payment_status ) { if ( ! in_array( $payment_status, $this->valid_statuses, true ) ) { throw new \Exception( sprintf( 'Invalid payment status %s. Use one of %s', $payment_status, implode( ', ', $this->valid_statuses ) ) ); } $this->status = $payment_status; } /** * Set payment details. * * @param array $payment_details Array of key value pairs of data. */ public function set_payment_details( $payment_details = [] ) { $this->payment_details = []; foreach ( $payment_details as $key => $value ) { $this->payment_details[ (string) $key ] = (string) $value; } } /** * Set redirect URL. * * @param array $redirect_url URL to redirect the customer to after checkout. */ public function set_redirect_url( $redirect_url = [] ) { $this->redirect_url = esc_url_raw( $redirect_url ); } } woocommerce-blocks/src/RestApi.php 0000644 00000005725 15133551764 0013230 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks; use Automattic\WooCommerce\Blocks\StoreApi\RoutesController; use Automattic\WooCommerce\Blocks\StoreApi\SchemaController; use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi; /** * RestApi class. * Registers controllers in the blocks REST API namespace. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class RestApi { /** * Stores Rest Routes instance * * @var RoutesController */ private $routes; /** * Constructor * * @param RoutesController $routes Rest Routes instance. */ public function __construct( RoutesController $routes ) { $this->routes = $routes; $this->init(); } /** * Initialize class features. */ protected function init() { add_action( 'rest_api_init', array( $this, 'register_rest_routes' ), 10 ); add_filter( 'rest_authentication_errors', array( $this, 'store_api_authentication' ) ); add_action( 'set_logged_in_cookie', array( $this, 'store_api_logged_in_cookie' ) ); } /** * Register REST API routes. */ public function register_rest_routes() { $this->routes->register_routes(); } /** * Get routes for a namespace. * * @param string $namespace Namespace to retrieve. * @return array|null */ public function get_routes_from_namespace( $namespace ) { $rest_server = rest_get_server(); $namespace_index = $rest_server->get_namespace_index( [ 'namespace' => $namespace, 'context' => 'view', ] ); $response_data = $namespace_index->get_data(); return isset( $response_data['routes'] ) ? $response_data['routes'] : null; } /** * The Store API does not require authentication. * * @param \WP_Error|mixed $result Error from another authentication handler, null if we should handle it, or another value if not. * @return \WP_Error|null|bool */ public function store_api_authentication( $result ) { // Pass through errors from other authentication methods used before this one. if ( ! empty( $result ) || ! self::is_request_to_store_api() ) { return $result; } return true; } /** * When the login cookies are set, they are not available until the next page reload. For the Store API, specifically * for returning updated nonces, we need this to be available immediately. * * @param string $logged_in_cookie The value for the logged in cookie. */ public function store_api_logged_in_cookie( $logged_in_cookie ) { if ( ! defined( 'LOGGED_IN_COOKIE' ) || ! self::is_request_to_store_api() ) { return; } $_COOKIE[ LOGGED_IN_COOKIE ] = $logged_in_cookie; } /** * Check if is request to the Store API. * * @return bool */ protected function is_request_to_store_api() { if ( empty( $_SERVER['REQUEST_URI'] ) ) { return false; } $rest_prefix = trailingslashit( rest_get_url_prefix() ); $request_uri = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ); return false !== strpos( $request_uri, $rest_prefix . 'wc/store' ); } } woocommerce-blocks/src/Assets/Api.php 0000644 00000016050 15133551764 0013625 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Assets; use Automattic\WooCommerce\Blocks\Domain\Package; use Exception; /** * The Api class provides an interface to various asset registration helpers. * * Contains asset api methods * * @since 2.5.0 */ class Api { /** * Stores inline scripts already enqueued. * * @var array */ private $inline_scripts = []; /** * Reference to the Package instance * * @var Package */ private $package; /** * Constructor for class * * @param Package $package An instance of Package. */ public function __construct( Package $package ) { $this->package = $package; } /** * Get the file modified time as a cache buster if we're in dev mode. * * @param string $file Local path to the file (relative to the plugin * directory). * @return string The cache buster value to use for the given file. */ protected function get_file_version( $file ) { if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG && file_exists( $this->package->get_path() . $file ) ) { return filemtime( $this->package->get_path( trim( $file, '/' ) ) ); } return $this->package->get_version(); } /** * Retrieve the url to an asset for this plugin. * * @param string $relative_path An optional relative path appended to the * returned url. * * @return string */ protected function get_asset_url( $relative_path = '' ) { return $this->package->get_url( $relative_path ); } /** * Get src, version and dependencies given a script relative src. * * @param string $relative_src Relative src to the script. * @param array $dependencies Optional. An array of registered script handles this script depends on. Default empty array. * * @return array src, version and dependencies of the script. */ public function get_script_data( $relative_src, $dependencies = [] ) { $src = ''; $version = '1'; if ( $relative_src ) { $src = $this->get_asset_url( $relative_src ); $asset_path = $this->package->get_path( str_replace( '.js', '.asset.php', $relative_src ) ); if ( file_exists( $asset_path ) ) { $asset = require $asset_path; $dependencies = isset( $asset['dependencies'] ) ? array_merge( $asset['dependencies'], $dependencies ) : $dependencies; $version = ! empty( $asset['version'] ) ? $asset['version'] : $this->get_file_version( $relative_src ); } else { $version = $this->get_file_version( $relative_src ); } } return array( 'src' => $src, 'version' => $version, 'dependencies' => $dependencies, ); } /** * Registers a script according to `wp_register_script`, adding the correct prefix, and additionally loading translations. * * When creating script assets, the following rules should be followed: * 1. All asset handles should have a `wc-` prefix. * 2. If the asset handle is for a Block (in editor context) use the `-block` suffix. * 3. If the asset handle is for a Block (in frontend context) use the `-block-frontend` suffix. * 4. If the asset is for any other script being consumed or enqueued by the blocks plugin, use the `wc-blocks-` prefix. * * @since 2.5.0 * @throws Exception If the registered script has a dependency on itself. * * @param string $handle Unique name of the script. * @param string $relative_src Relative url for the script to the path from plugin root. * @param array $dependencies Optional. An array of registered script handles this script depends on. Default empty array. * @param bool $has_i18n Optional. Whether to add a script translation call to this file. Default: true. */ public function register_script( $handle, $relative_src, $dependencies = [], $has_i18n = true ) { $script_data = $this->get_script_data( $relative_src, $dependencies ); if ( in_array( $handle, $script_data['dependencies'], true ) ) { if ( $this->package->feature()->is_development_environment() ) { $dependencies = array_diff( $script_data['dependencies'], [ $handle ] ); add_action( 'admin_notices', function() use ( $handle ) { echo '<div class="error"><p>'; /* translators: %s file handle name. */ printf( esc_html__( 'Script with handle %s had a dependency on itself which has been removed. This is an indicator that your JS code has a circular dependency that can cause bugs.', 'woocommerce' ), esc_html( $handle ) ); echo '</p></div>'; } ); } else { throw new Exception( sprintf( 'Script with handle %s had a dependency on itself. This is an indicator that your JS code has a circular dependency that can cause bugs.', $handle ) ); } } wp_register_script( $handle, $script_data['src'], apply_filters( 'woocommerce_blocks_register_script_dependencies', $script_data['dependencies'], $handle ), $script_data['version'], true ); if ( $has_i18n && function_exists( 'wp_set_script_translations' ) ) { wp_set_script_translations( $handle, 'woocommerce', $this->package->get_path( 'languages' ) ); } } /** * Registers a style according to `wp_register_style`. * * @since 2.5.0 * @since 2.6.0 Change src to be relative source. * * @param string $handle Name of the stylesheet. Should be unique. * @param string $relative_src Relative source of the stylesheet to the plugin path. * @param array $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array. * @param string $media Optional. The media for which this stylesheet has been defined. Default 'all'. Accepts media types like * 'all', 'print' and 'screen', or media queries like '(orientation: portrait)' and '(max-width: 640px)'. */ public function register_style( $handle, $relative_src, $deps = [], $media = 'all' ) { $filename = str_replace( plugins_url( '/', __DIR__ ), '', $relative_src ); $src = $this->get_asset_url( $relative_src ); $ver = $this->get_file_version( $filename ); wp_register_style( $handle, $src, $deps, $ver, $media ); } /** * Returns the appropriate asset path for loading either legacy builds or * current builds. * * @param string $filename Filename for asset path (without extension). * @param string $type File type (.css or .js). * * @return string The generated path. */ public function get_block_asset_build_path( $filename, $type = 'js' ) { global $wp_version; $suffix = version_compare( $wp_version, '5.3', '>=' ) ? '' : '-legacy'; return "build/$filename$suffix.$type"; } /** * Adds an inline script, once. * * @param string $handle Script handle. * @param string $script Script contents. */ public function add_inline_script( $handle, $script ) { if ( ! empty( $this->inline_scripts[ $handle ] ) && in_array( $script, $this->inline_scripts[ $handle ], true ) ) { return; } wp_add_inline_script( $handle, $script ); if ( isset( $this->inline_scripts[ $handle ] ) ) { $this->inline_scripts[ $handle ][] = $script; } else { $this->inline_scripts[ $handle ] = array( $script ); } } } woocommerce-blocks/src/Assets/AssetDataRegistry.php 0000644 00000026120 15133551764 0016515 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Assets; use Automattic\WooCommerce\Blocks\Package; use Exception; use InvalidArgumentException; /** * Class instance for registering data used on the current view session by * assets. * * Holds data registered for output on the current view session when * `wc-settings` is enqueued( directly or via dependency ) * * @since 2.5.0 */ class AssetDataRegistry { /** * Contains registered data. * * @var array */ private $data = []; /** * Lazy data is an array of closures that will be invoked just before * asset data is generated for the enqueued script. * * @var array */ private $lazy_data = []; /** * Asset handle for registered data. * * @var string */ private $handle = 'wc-settings'; /** * Asset API interface for various asset registration. * * @var API */ private $api; /** * Constructor * * @param Api $asset_api Asset API interface for various asset registration. */ public function __construct( Api $asset_api ) { $this->api = $asset_api; $this->init(); } /** * Hook into WP asset registration for enqueueing asset data. */ protected function init() { add_action( 'init', array( $this, 'register_data_script' ) ); add_action( 'wp_print_footer_scripts', array( $this, 'enqueue_asset_data' ), 1 ); add_action( 'admin_print_footer_scripts', array( $this, 'enqueue_asset_data' ), 1 ); } /** * Exposes core data via the wcSettings global. This data is shared throughout the client. * * Settings that are used by various components or multiple blocks should be added here. Note, that settings here are * global so be sure not to add anything heavy if possible. * * @return array An array containing core data. */ protected function get_core_data() { return [ 'adminUrl' => admin_url(), 'countries' => WC()->countries->get_countries(), 'currency' => $this->get_currency_data(), 'currentUserIsAdmin' => current_user_can( 'manage_woocommerce' ), 'homeUrl' => esc_url( home_url( '/' ) ), 'locale' => $this->get_locale_data(), 'orderStatuses' => $this->get_order_statuses(), 'placeholderImgSrc' => wc_placeholder_img_src(), 'siteTitle' => get_bloginfo( 'name' ), 'storePages' => $this->get_store_pages(), 'wcAssetUrl' => plugins_url( 'assets/', WC_PLUGIN_FILE ), 'wcVersion' => defined( 'WC_VERSION' ) ? WC_VERSION : '', 'wpLoginUrl' => wp_login_url(), 'wpVersion' => get_bloginfo( 'version' ), ]; } /** * Get currency data to include in settings. * * @return array */ protected function get_currency_data() { $currency = get_woocommerce_currency(); return [ 'code' => $currency, 'precision' => wc_get_price_decimals(), 'symbol' => html_entity_decode( get_woocommerce_currency_symbol( $currency ) ), 'symbolPosition' => get_option( 'woocommerce_currency_pos' ), 'decimalSeparator' => wc_get_price_decimal_separator(), 'thousandSeparator' => wc_get_price_thousand_separator(), 'priceFormat' => html_entity_decode( get_woocommerce_price_format() ), ]; } /** * Get locale data to include in settings. * * @return array */ protected function get_locale_data() { global $wp_locale; return [ 'siteLocale' => get_locale(), 'userLocale' => get_user_locale(), 'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ), ]; } /** * Get store pages to include in settings. * * @return array */ protected function get_store_pages() { return array_map( [ $this, 'format_page_resource' ], [ 'myaccount' => wc_get_page_id( 'myaccount' ), 'shop' => wc_get_page_id( 'shop' ), 'cart' => wc_get_page_id( 'cart' ), 'checkout' => wc_get_page_id( 'checkout' ), 'privacy' => wc_privacy_policy_page_id(), 'terms' => wc_terms_and_conditions_page_id(), ] ); } /** * Format a page object into a standard array of data. * * @param WP_Post|int $page Page object or ID. * @return array */ protected function format_page_resource( $page ) { if ( is_numeric( $page ) && $page > 0 ) { $page = get_post( $page ); } if ( ! is_a( $page, '\WP_Post' ) || 'publish' !== $page->post_status ) { return [ 'id' => 0, 'title' => '', 'permalink' => false, ]; } return [ 'id' => $page->ID, 'title' => $page->post_title, 'permalink' => get_permalink( $page->ID ), ]; } /** * Returns block-related data for enqueued wc-settings script. * Format order statuses by removing a leading 'wc-' if present. * * @return array formatted statuses. */ protected function get_order_statuses() { $formatted_statuses = array(); foreach ( wc_get_order_statuses() as $key => $value ) { $formatted_key = preg_replace( '/^wc-/', '', $key ); $formatted_statuses[ $formatted_key ] = $value; } return $formatted_statuses; } /** * Used for on demand initialization of asset data and registering it with * the internal data registry. * * Note: core data will overwrite any externally registered data via the api. */ protected function initialize_core_data() { /** * Low level hook for registration of new data late in the cycle. This is deprecated. * Instead, use the data api: * Automattic\WooCommerce\Blocks\Package::container() * ->get( Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry::class ) * ->add( $key, $value ) */ $settings = apply_filters( 'woocommerce_shared_settings', $this->data ); // Surface a deprecation warning in the error console. if ( has_filter( 'woocommerce_shared_settings' ) ) { $error_handle = 'deprecated-shared-settings-error'; $error_message = '`woocommerce_shared_settings` filter in Blocks is deprecated. See https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/trunk/docs/contributors/block-assets.md'; // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter,WordPress.WP.EnqueuedResourceParameters.MissingVersion wp_register_script( $error_handle, '' ); wp_enqueue_script( $error_handle ); wp_add_inline_script( $error_handle, sprintf( 'console.warn( "%s" );', $error_message ) ); } // note this WILL wipe any data already registered to these keys because they are protected. $this->data = array_replace_recursive( $settings, $this->get_core_data() ); } /** * Loops through each registered lazy data callback and adds the returned * value to the data array. * * This method is executed right before preparing the data for printing to * the rendered screen. * * @return void */ protected function execute_lazy_data() { foreach ( $this->lazy_data as $key => $callback ) { $this->data[ $key ] = $callback(); } } /** * Exposes private registered data to child classes. * * @return array The registered data on the private data property */ protected function get() { return $this->data; } /** * Allows checking whether a key exists. * * @param string $key The key to check if exists. * @return bool Whether the key exists in the current data registry. */ public function exists( $key ) { return array_key_exists( $key, $this->data ); } /** * Interface for adding data to the registry. * * You can only register data that is not already in the registry identified by the given key. If there is a * duplicate found, unless $ignore_duplicates is true, an exception will be thrown. * * @param string $key The key used to reference the data being registered. * @param mixed $data If not a function, registered to the registry as is. If a function, then the * callback is invoked right before output to the screen. * @param boolean $check_key_exists If set to true, duplicate data will be ignored if the key exists. * If false, duplicate data will cause an exception. * * @throws InvalidArgumentException Only throws when site is in debug mode. Always logs the error. */ public function add( $key, $data, $check_key_exists = false ) { if ( $check_key_exists && $this->exists( $key ) ) { return; } try { $this->add_data( $key, $data ); } catch ( Exception $e ) { if ( $this->debug() ) { // bubble up. throw $e; } wc_caught_exception( $e, __METHOD__, [ $key, $data ] ); } } /** * Hydrate from API. * * @param string $path REST API path to preload. */ public function hydrate_api_request( $path ) { if ( ! isset( $this->data['preloadedApiRequests'] ) ) { $this->data['preloadedApiRequests'] = []; } if ( ! isset( $this->data['preloadedApiRequests'][ $path ] ) ) { $this->data['preloadedApiRequests'] = rest_preload_api_request( $this->data['preloadedApiRequests'], $path ); } } /** * Adds a page permalink to the data registry. * * @param integer $page_id Page ID to add to the registry. */ public function register_page_id( $page_id ) { $permalink = $page_id ? get_permalink( $page_id ) : false; if ( $permalink ) { $this->data[ 'page-' . $page_id ] = $permalink; } } /** * Callback for registering the data script via WordPress API. * * @return void */ public function register_data_script() { $this->api->register_script( $this->handle, 'build/wc-settings.js', [], true ); } /** * Callback for enqueuing asset data via the WP api. * * Note: while this is hooked into print/admin_print_scripts, it still only * happens if the script attached to `wc-settings` handle is enqueued. This * is done to allow for any potentially expensive data generation to only * happen for routes that need it. */ public function enqueue_asset_data() { if ( wp_script_is( $this->handle, 'enqueued' ) ) { $this->initialize_core_data(); $this->execute_lazy_data(); $data = rawurlencode( wp_json_encode( $this->data ) ); wp_add_inline_script( $this->handle, "var wcSettings = wcSettings || JSON.parse( decodeURIComponent( '" . esc_js( $data ) . "' ) );", 'before' ); } } /** * See self::add() for docs. * * @param string $key Key for the data. * @param mixed $data Value for the data. * * @throws InvalidArgumentException If key is not a string or already * exists in internal data cache. */ protected function add_data( $key, $data ) { if ( ! is_string( $key ) ) { if ( $this->debug() ) { throw new InvalidArgumentException( 'Key for the data being registered must be a string' ); } } if ( isset( $this->data[ $key ] ) ) { if ( $this->debug() ) { throw new InvalidArgumentException( 'Overriding existing data with an already registered key is not allowed' ); } return; } if ( \is_callable( $data ) ) { $this->lazy_data[ $key ] = $data; return; } $this->data[ $key ] = $data; } /** * Exposes whether the current site is in debug mode or not. * * @return boolean True means the site is in debug mode. */ protected function debug() { return defined( 'WP_DEBUG' ) && WP_DEBUG; } } woocommerce-blocks/src/Registry/SharedType.php 0000644 00000001344 15133551764 0015532 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Registry; /** * A definition for the SharedType dependency type. * * @since 2.5.0 */ class SharedType extends AbstractDependencyType { /** * Holds a cached instance of the value stored (or returned) internally. * * @var mixed */ private $shared_instance; /** * Returns the internal stored and shared value after initial generation. * * @param Container $container An instance of the dependency injection * container. * * @return mixed */ public function get( Container $container ) { if ( empty( $this->shared_instance ) ) { $this->shared_instance = $this->resolve_value( $container ); } return $this->shared_instance; } } woocommerce-blocks/src/Registry/Container.php 0000644 00000006025 15133551764 0015405 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Registry; use Closure; use Exception; /** * A simple Dependency Injection Container * * This is used to manage dependencies used throughout the plugin. * * @since 2.5.0 */ class Container { /** * A map of Dependency Type objects used to resolve dependencies. * * @var AbstractDependencyType[] */ private $registry = []; /** * Public api for adding a factory to the container. * * Factory dependencies will have the instantiation callback invoked * every time the dependency is requested. * * Typical Usage: * * ``` * $container->register( MyClass::class, $container->factory( $mycallback ) ); * ``` * * @param Closure $instantiation_callback This will be invoked when the * dependency is required. It will * receive an instance of this * container so the callback can * retrieve dependencies from the * container. * * @return FactoryType An instance of the FactoryType dependency. */ public function factory( Closure $instantiation_callback ) { return new FactoryType( $instantiation_callback ); } /** * Interface for registering a new dependency with the container. * * By default, the $value will be added as a shared dependency. This means * that it will be a single instance shared among any other classes having * that dependency. * * If you want a new instance every time it's required, then wrap the value * in a call to the factory method (@see Container::factory for example) * * Note: Currently if the provided id already is registered in the container, * the provided value is ignored. * * @param string $id A unique string identifier for the provided value. * Typically it's the fully qualified name for the * dependency. * @param mixed $value The value for the dependency. Typically, this is a * closure that will create the class instance needed. */ public function register( $id, $value ) { if ( empty( $this->registry[ $id ] ) ) { if ( ! $value instanceof FactoryType ) { $value = new SharedType( $value ); } $this->registry[ $id ] = $value; } } /** * Interface for retrieving the dependency stored in the container for the * given identifier. * * @param string $id The identifier for the dependency being retrieved. * @throws Exception If there is no dependency for the given identifier in * the container. * * @return mixed Typically a class instance. */ public function get( $id ) { if ( ! isset( $this->registry[ $id ] ) ) { // this is a developer facing exception, hence it is not localized. throw new Exception( sprintf( 'Cannot construct an instance of %s because it has not been registered.', $id ) ); } return $this->registry[ $id ]->get( $this ); } } woocommerce-blocks/src/Registry/AbstractDependencyType.php 0000644 00000002362 15133551764 0020067 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Registry; /** * An abstract class for dependency types. * * Dependency types are instances of a dependency used by the * Dependency Injection Container for storing dependencies to invoke as they * are needed. * * @since 2.5.0 */ abstract class AbstractDependencyType { /** * Holds a callable or value provided for this type. * * @var mixed */ private $callable_or_value; /** * Constructor * * @param mixed $callable_or_value A callable or value for the dependency * type instance. */ public function __construct( $callable_or_value ) { $this->callable_or_value = $callable_or_value; } /** * Resolver for the internal dependency value. * * @param Container $container The Dependency Injection Container. * * @return mixed */ protected function resolve_value( Container $container ) { $callback = $this->callable_or_value; return \is_callable( $callback ) ? $callback( $container ) : $callback; } /** * Retrieves the value stored internally for this DependencyType * * @param Container $container The Dependency Injection Container. * * @return void */ abstract public function get( Container $container ); } woocommerce-blocks/src/Registry/FactoryType.php 0000644 00000000771 15133551764 0015736 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Registry; /** * Definition for the FactoryType dependency type. * * @since 2.5.0 */ class FactoryType extends AbstractDependencyType { /** * Invokes and returns the value from the stored internal callback. * * @param Container $container An instance of the dependency injection * container. * * @return mixed */ public function get( Container $container ) { return $this->resolve_value( $container ); } } woocommerce-blocks/src/AssetsController.php 0000644 00000013161 15133551764 0015160 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks; use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi; use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry as AssetDataRegistry; /** * AssetsController class. * * @since 5.0.0 * @internal */ final class AssetsController { /** * Asset API interface for various asset registration. * * @var AssetApi */ private $api; /** * Constructor. * * @param AssetApi $asset_api Asset API interface for various asset registration. */ public function __construct( AssetApi $asset_api ) { $this->api = $asset_api; $this->init(); } /** * Initialize class features. */ protected function init() { add_action( 'init', array( $this, 'register_assets' ) ); add_action( 'body_class', array( $this, 'add_theme_body_class' ), 1 ); add_action( 'admin_body_class', array( $this, 'add_theme_body_class' ), 1 ); add_action( 'admin_enqueue_scripts', array( $this, 'update_block_style_dependencies' ), 20 ); } /** * Register block scripts & styles. */ public function register_assets() { $this->register_style( 'wc-blocks-vendors-style', plugins_url( $this->api->get_block_asset_build_path( 'wc-blocks-vendors-style', 'css' ), __DIR__ ) ); $this->register_style( 'wc-blocks-editor-style', plugins_url( $this->api->get_block_asset_build_path( 'wc-blocks-editor-style', 'css' ), __DIR__ ), [ 'wp-edit-blocks' ], 'all', true ); $this->register_style( 'wc-blocks-style', plugins_url( $this->api->get_block_asset_build_path( 'wc-blocks-style', 'css' ), __DIR__ ), [ 'wc-blocks-vendors-style' ], 'all', true ); $this->api->register_script( 'wc-blocks-middleware', 'build/wc-blocks-middleware.js', [], false ); $this->api->register_script( 'wc-blocks-data-store', 'build/wc-blocks-data.js', [ 'wc-blocks-middleware' ] ); $this->api->register_script( 'wc-blocks-vendors', $this->api->get_block_asset_build_path( 'wc-blocks-vendors' ), [], false ); $this->api->register_script( 'wc-blocks-registry', 'build/wc-blocks-registry.js', [], false ); $this->api->register_script( 'wc-blocks', $this->api->get_block_asset_build_path( 'wc-blocks' ), [ 'wc-blocks-vendors' ], false ); $this->api->register_script( 'wc-blocks-shared-context', 'build/wc-blocks-shared-context.js', [] ); $this->api->register_script( 'wc-blocks-shared-hocs', 'build/wc-blocks-shared-hocs.js', [], false ); // The price package is shared externally so has no blocks prefix. $this->api->register_script( 'wc-price-format', 'build/price-format.js', [], false ); if ( Package::feature()->is_feature_plugin_build() ) { $this->api->register_script( 'wc-blocks-checkout', 'build/blocks-checkout.js', [] ); } wp_add_inline_script( 'wc-blocks-middleware', " var wcBlocksMiddlewareConfig = { storeApiNonce: '" . esc_js( wp_create_nonce( 'wc_store_api' ) ) . "', wcStoreApiNonceTimestamp: '" . esc_js( time() ) . "' }; ", 'before' ); } /** * Add body classes to the frontend and within admin. * * @param string|array $classes Array or string of CSS classnames. * @return string|array Modified classnames. */ public function add_theme_body_class( $classes ) { $class = 'theme-' . get_template(); if ( is_array( $classes ) ) { $classes[] = $class; } else { $classes .= ' ' . $class . ' '; } return $classes; } /** * Get the file modified time as a cache buster if we're in dev mode. * * @param string $file Local path to the file. * @return string The cache buster value to use for the given file. */ protected function get_file_version( $file ) { if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG && file_exists( \Automattic\WooCommerce\Blocks\Package::get_path() . $file ) ) { return filemtime( \Automattic\WooCommerce\Blocks\Package::get_path() . $file ); } return \Automattic\WooCommerce\Blocks\Package::get_version(); } /** * Registers a style according to `wp_register_style`. * * @param string $handle Name of the stylesheet. Should be unique. * @param string $src Full URL of the stylesheet, or path of the stylesheet relative to the WordPress root directory. * @param array $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array. * @param string $media Optional. The media for which this stylesheet has been defined. Default 'all'. Accepts media types like * 'all', 'print' and 'screen', or media queries like '(orientation: portrait)' and '(max-width: 640px)'. * @param boolean $rtl Optional. Whether or not to register RTL styles. */ protected function register_style( $handle, $src, $deps = [], $media = 'all', $rtl = false ) { $filename = str_replace( plugins_url( '/', __DIR__ ), '', $src ); $ver = self::get_file_version( $filename ); wp_register_style( $handle, $src, $deps, $ver, $media ); if ( $rtl ) { wp_style_add_data( $handle, 'rtl', 'replace' ); } } /** * Update block style dependencies after they have been registered. */ public function update_block_style_dependencies() { $wp_styles = wp_styles(); $style = $wp_styles->query( 'wc-blocks-style', 'registered' ); if ( ! $style ) { return; } // In WC < 5.5, `woocommerce-general` is not registered in block editor // screens, so we don't add it as a dependency if it's not registered. // In WC >= 5.5, `woocommerce-general` is registered on `admin_enqueue_scripts`, // so we need to check if it's registered here instead of on `init`. if ( wp_style_is( 'woocommerce-general', 'registered' ) && ! in_array( 'woocommerce-general', $style->deps, true ) ) { $style->deps[] = 'woocommerce-general'; } } } woocommerce-blocks/src/Installer.php 0000644 00000006347 15133551764 0013617 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks; /** * Installer class. * Handles installation of Blocks plugin dependencies. * * @internal */ class Installer { /** * Constructor */ public function __construct() { $this->init(); } /** * Installation tasks ran on admin_init callback. */ public function install() { $this->maybe_create_tables(); } /** * Initialize class features. */ protected function init() { add_action( 'admin_init', array( $this, 'install' ) ); } /** * Set up the database tables which the plugin needs to function. */ public function maybe_create_tables() { global $wpdb; $schema_version = 260; $db_schema_version = (int) get_option( 'wc_blocks_db_schema_version', 0 ); if ( $db_schema_version >= $schema_version && 0 !== $db_schema_version ) { return; } $show_errors = $wpdb->hide_errors(); $table_name = $wpdb->prefix . 'wc_reserved_stock'; $collate = $wpdb->has_cap( 'collation' ) ? $wpdb->get_charset_collate() : ''; $exists = $this->maybe_create_table( $wpdb->prefix . 'wc_reserved_stock', " CREATE TABLE {$wpdb->prefix}wc_reserved_stock ( `order_id` bigint(20) NOT NULL, `product_id` bigint(20) NOT NULL, `stock_quantity` double NOT NULL DEFAULT 0, `timestamp` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `expires` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', PRIMARY KEY (`order_id`, `product_id`) ) $collate; " ); if ( $show_errors ) { $wpdb->show_errors(); } if ( ! $exists ) { return $this->add_create_table_notice( $table_name ); } // Update succeeded. This is only updated when successful and validated. // $schema_version should be incremented when changes to schema are made within this method. update_option( 'wc_blocks_db_schema_version', $schema_version ); } /** * Create database table, if it doesn't already exist. * * Based on admin/install-helper.php maybe_create_table function. * * @param string $table_name Database table name. * @param string $create_sql Create database table SQL. * @return bool False on error, true if already exists or success. */ protected function maybe_create_table( $table_name, $create_sql ) { global $wpdb; if ( in_array( $table_name, $wpdb->get_col( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ), 0 ), true ) ) { return true; } $wpdb->query( $create_sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared return in_array( $table_name, $wpdb->get_col( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ), 0 ), true ); } /** * Add a notice if table creation fails. * * @param string $table_name Name of the missing table. */ protected function add_create_table_notice( $table_name ) { add_action( 'admin_notices', function() use ( $table_name ) { echo '<div class="error"><p>'; printf( /* translators: %1$s table name, %2$s database user, %3$s database name. */ esc_html__( 'WooCommerce %1$s table creation failed. Does the %2$s user have CREATE privileges on the %3$s database?', 'woocommerce' ), '<code>' . esc_html( $table_name ) . '</code>', '<code>' . esc_html( DB_USER ) . '</code>', '<code>' . esc_html( DB_NAME ) . '</code>' ); echo '</p></div>'; } ); } } woocommerce-blocks/src/Integrations/IntegrationInterface.php 0000644 00000001563 15133551764 0020427 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Integrations; /** * Integration.Interface * * Integrations must use this interface when registering themselves with blocks, */ interface IntegrationInterface { /** * The name of the integration. * * @return string */ public function get_name(); /** * When called invokes any initialization/setup for the integration. */ public function initialize(); /** * Returns an array of script handles to enqueue in the frontend context. * * @return string[] */ public function get_script_handles(); /** * Returns an array of script handles to enqueue in the editor context. * * @return string[] */ public function get_editor_script_handles(); /** * An array of key, value pairs of data made available to the block on the client side. * * @return array */ public function get_script_data(); } woocommerce-blocks/src/Integrations/IntegrationRegistry.php 0000644 00000012411 15133551764 0020331 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Integrations; /** * Class used for tracking registered integrations with various Block types. */ class IntegrationRegistry { /** * Integration identifier is used to construct hook names and is given when the integration registry is initialized. * * @var string */ protected $registry_identifier = ''; /** * Registered integrations, as `$name => $instance` pairs. * * @var IntegrationInterface[] */ protected $registered_integrations = []; /** * Initializes all registered integrations. * * Integration identifier is used to construct hook names and is given when the integration registry is initialized. * * @param string $registry_identifier Identifier for this registry. */ public function initialize( $registry_identifier = '' ) { if ( $registry_identifier ) { $this->registry_identifier = $registry_identifier; } if ( empty( $this->registry_identifier ) ) { _doing_it_wrong( __METHOD__, esc_html( __( 'Integration registry requires an identifier.', 'woocommerce' ) ) ); return false; } /** * Hook: integration_registration. * * Runs before integrations are initialized allowing new integration to be registered for use. This should be * used as the primary hook for integrations to include their scripts, styles, and other code extending the * blocks. * * @param IntegrationRegistry $this Instance of the IntegrationRegistry class which exposes the IntegrationRegistry::register() method. */ do_action( 'woocommerce_blocks_' . $this->registry_identifier . '_registration', $this ); foreach ( $this->get_all_registered() as $registered_integration ) { $registered_integration->initialize(); } } /** * Registers an integration. * * @param IntegrationInterface $integration An instance of IntegrationInterface. * * @return boolean True means registered successfully. */ public function register( IntegrationInterface $integration ) { $name = $integration->get_name(); if ( $this->is_registered( $name ) ) { /* translators: %s: Integration name. */ _doing_it_wrong( __METHOD__, esc_html( sprintf( __( '"%s" is already registered.', 'woocommerce' ), $name ) ) ); return false; } $this->registered_integrations[ $name ] = $integration; return true; } /** * Checks if an integration is already registered. * * @param string $name Integration name. * @return bool True if the integration is registered, false otherwise. */ public function is_registered( $name ) { return isset( $this->registered_integrations[ $name ] ); } /** * Un-register an integration. * * @param string|IntegrationInterface $name Integration name, or alternatively a IntegrationInterface instance. * @return boolean|IntegrationInterface Returns the unregistered integration instance if unregistered successfully. */ public function unregister( $name ) { if ( $name instanceof IntegrationInterface ) { $name = $name->get_name(); } if ( ! $this->is_registered( $name ) ) { /* translators: %s: Integration name. */ _doing_it_wrong( __METHOD__, esc_html( sprintf( __( 'Integration "%s" is not registered.', 'woocommerce' ), $name ) ) ); return false; } $unregistered = $this->registered_integrations[ $name ]; unset( $this->registered_integrations[ $name ] ); return $unregistered; } /** * Retrieves a registered Integration by name. * * @param string $name Integration name. * @return IntegrationInterface|null The registered integration, or null if it is not registered. */ public function get_registered( $name ) { return $this->is_registered( $name ) ? $this->registered_integrations[ $name ] : null; } /** * Retrieves all registered integrations. * * @return IntegrationInterface[] */ public function get_all_registered() { return $this->registered_integrations; } /** * Gets an array of all registered integration's script handles for the editor. * * @return string[] */ public function get_all_registered_editor_script_handles() { $script_handles = []; $registered_integrations = $this->get_all_registered(); foreach ( $registered_integrations as $registered_integration ) { $script_handles = array_merge( $script_handles, $registered_integration->get_editor_script_handles() ); } return array_unique( array_filter( $script_handles ) ); } /** * Gets an array of all registered integration's script handles. * * @return string[] */ public function get_all_registered_script_handles() { $script_handles = []; $registered_integrations = $this->get_all_registered(); foreach ( $registered_integrations as $registered_integration ) { $script_handles = array_merge( $script_handles, $registered_integration->get_script_handles() ); } return array_unique( array_filter( $script_handles ) ); } /** * Gets an array of all registered integration's script data. * * @return array */ public function get_all_registered_script_data() { $script_data = []; $registered_integrations = $this->get_all_registered(); foreach ( $registered_integrations as $registered_integration ) { $script_data[ $registered_integration->get_name() . '_data' ] = $registered_integration->get_script_data(); } return array_filter( $script_data ); } } woocommerce-blocks/src/BlockTypes/PriceFilter.php 0000644 00000000335 15133551764 0016140 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * PriceFilter class. */ class PriceFilter extends AbstractBlock { /** * Block name. * * @var string */ protected $block_name = 'price-filter'; } woocommerce-blocks/src/BlockTypes/FeaturedCategory.php 0000644 00000012411 15133551764 0017163 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * FeaturedCategory class. */ class FeaturedCategory extends AbstractDynamicBlock { /** * Block name. * * @var string */ protected $block_name = 'featured-category'; /** * Default attribute values, should match what's set in JS `registerBlockType`. * * @var array */ protected $defaults = array( 'align' => 'none', 'contentAlign' => 'center', 'dimRatio' => 50, 'focalPoint' => false, 'height' => false, 'mediaId' => 0, 'mediaSrc' => '', 'showDesc' => true, ); /** * Render the Featured Category block. * * @param array $attributes Block attributes. * @param string $content Block content. * @return string Rendered block type output. */ protected function render( $attributes, $content ) { $id = absint( isset( $attributes['categoryId'] ) ? $attributes['categoryId'] : 0 ); $category = get_term( $id, 'product_cat' ); if ( ! $category || is_wp_error( $category ) ) { return ''; } $attributes = wp_parse_args( $attributes, $this->defaults ); $attributes['height'] = $attributes['height'] ? $attributes['height'] : wc_get_theme_support( 'featured_block::default_height', 500 ); $title = sprintf( '<h2 class="wc-block-featured-category__title">%s</h2>', wp_kses_post( $category->name ) ); $desc_str = sprintf( '<div class="wc-block-featured-category__description">%s</div>', wc_format_content( wp_kses_post( $category->description ) ) ); $output = sprintf( '<div class="%1$s" style="%2$s">', esc_attr( $this->get_classes( $attributes ) ), esc_attr( $this->get_styles( $attributes, $category ) ) ); $output .= '<div class="wc-block-featured-category__wrapper">'; $output .= $title; if ( $attributes['showDesc'] ) { $output .= $desc_str; } $output .= '<div class="wc-block-featured-category__link">' . $content . '</div>'; $output .= '</div>'; $output .= '</div>'; return $output; } /** * Get the styles for the wrapper element (background image, color). * * @param array $attributes Block attributes. Default empty array. * @param \WP_Term $category Term object. * @return string */ public function get_styles( $attributes, $category ) { $style = ''; $image_size = 'large'; if ( 'none' !== $attributes['align'] || $attributes['height'] > 800 ) { $image_size = 'full'; } if ( $attributes['mediaId'] ) { $image = wp_get_attachment_image_url( $attributes['mediaId'], $image_size ); } else { $image = $this->get_image( $category, $image_size ); } if ( ! empty( $image ) ) { $style .= sprintf( 'background-image:url(%s);', esc_url( $image ) ); } if ( isset( $attributes['customOverlayColor'] ) ) { $style .= sprintf( 'background-color:%s;', esc_attr( $attributes['customOverlayColor'] ) ); } if ( isset( $attributes['height'] ) ) { $style .= sprintf( 'min-height:%dpx;', intval( $attributes['height'] ) ); } if ( is_array( $attributes['focalPoint'] ) && 2 === count( $attributes['focalPoint'] ) ) { $style .= sprintf( 'background-position: %s%% %s%%', $attributes['focalPoint']['x'] * 100, $attributes['focalPoint']['y'] * 100 ); } return $style; } /** * Get class names for the block container. * * @param array $attributes Block attributes. Default empty array. * @return string */ public function get_classes( $attributes ) { $classes = array( 'wc-block-' . $this->block_name ); if ( isset( $attributes['align'] ) ) { $classes[] = "align{$attributes['align']}"; } if ( isset( $attributes['dimRatio'] ) && ( 0 !== $attributes['dimRatio'] ) ) { $classes[] = 'has-background-dim'; if ( 50 !== $attributes['dimRatio'] ) { $classes[] = 'has-background-dim-' . 10 * round( $attributes['dimRatio'] / 10 ); } } if ( isset( $attributes['contentAlign'] ) && 'center' !== $attributes['contentAlign'] ) { $classes[] = "has-{$attributes['contentAlign']}-content"; } if ( isset( $attributes['overlayColor'] ) ) { $classes[] = "has-{$attributes['overlayColor']}-background-color"; } if ( isset( $attributes['className'] ) ) { $classes[] = $attributes['className']; } return implode( ' ', $classes ); } /** * Returns the main product image URL. * * @param \WP_Term $category Term object. * @param string $size Image size, defaults to 'full'. * @return string */ public function get_image( $category, $size = 'full' ) { $image = ''; $image_id = get_term_meta( $category->term_id, 'thumbnail_id', true ); if ( $image_id ) { $image = wp_get_attachment_image_url( $image_id, $size ); } return $image; } /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); $this->asset_data_registry->add( 'min_height', wc_get_theme_support( 'featured_block::min_height', 500 ), true ); $this->asset_data_registry->add( 'default_height', wc_get_theme_support( 'featured_block::default_height', 500 ), true ); } } woocommerce-blocks/src/BlockTypes/ProductCategories.php 0000644 00000026124 15133551764 0017362 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * ProductCategories class. */ class ProductCategories extends AbstractDynamicBlock { /** * Block name. * * @var string */ protected $block_name = 'product-categories'; /** * Default attribute values, should match what's set in JS `registerBlockType`. * * @var array */ protected $defaults = array( 'hasCount' => true, 'hasImage' => false, 'hasEmpty' => false, 'isDropdown' => false, 'isHierarchical' => true, ); /** * Get block attributes. * * @return array */ protected function get_block_type_attributes() { return array_merge( parent::get_block_type_attributes(), array( 'align' => $this->get_schema_align(), 'className' => $this->get_schema_string(), 'hasCount' => $this->get_schema_boolean( true ), 'hasImage' => $this->get_schema_boolean( false ), 'hasEmpty' => $this->get_schema_boolean( false ), 'isDropdown' => $this->get_schema_boolean( false ), 'isHierarchical' => $this->get_schema_boolean( true ), ) ); } /** * Render the Product Categories List block. * * @param array $attributes Block attributes. * @param string $content Block content. * @return string Rendered block type output. */ protected function render( $attributes, $content ) { $uid = uniqid( 'product-categories-' ); $categories = $this->get_categories( $attributes ); if ( empty( $categories ) ) { return ''; } if ( ! empty( $content ) ) { // Deal with legacy attributes (before this was an SSR block) that differ from defaults. if ( strstr( $content, 'data-has-count="false"' ) ) { $attributes['hasCount'] = false; } if ( strstr( $content, 'data-is-dropdown="true"' ) ) { $attributes['isDropdown'] = true; } if ( strstr( $content, 'data-is-hierarchical="false"' ) ) { $attributes['isHierarchical'] = false; } if ( strstr( $content, 'data-has-empty="true"' ) ) { $attributes['hasEmpty'] = true; } } $classes = $this->get_container_classes( $attributes ); $output = '<div class="' . esc_attr( $classes ) . '">'; $output .= ! empty( $attributes['isDropdown'] ) ? $this->renderDropdown( $categories, $attributes, $uid ) : $this->renderList( $categories, $attributes, $uid ); $output .= '</div>'; return $output; } /** * Get the list of classes to apply to this block. * * @param array $attributes Block attributes. Default empty array. * @return string space-separated list of classes. */ protected function get_container_classes( $attributes = array() ) { $classes = array( 'wc-block-product-categories' ); if ( isset( $attributes['align'] ) ) { $classes[] = "align{$attributes['align']}"; } if ( ! empty( $attributes['className'] ) ) { $classes[] = $attributes['className']; } if ( $attributes['isDropdown'] ) { $classes[] = 'is-dropdown'; } else { $classes[] = 'is-list'; } return implode( ' ', $classes ); } /** * Get categories (terms) from the db. * * @param array $attributes Block attributes. Default empty array. * @return array */ protected function get_categories( $attributes ) { $hierarchical = wc_string_to_bool( $attributes['isHierarchical'] ); $categories = get_terms( 'product_cat', [ 'hide_empty' => ! $attributes['hasEmpty'], 'pad_counts' => true, 'hierarchical' => true, ] ); if ( ! is_array( $categories ) || empty( $categories ) ) { return []; } // This ensures that no categories with a product count of 0 is rendered. if ( ! $attributes['hasEmpty'] ) { $categories = array_filter( $categories, function( $category ) { return 0 !== $category->count; } ); } return $hierarchical ? $this->build_category_tree( $categories ) : $categories; } /** * Build hierarchical tree of categories. * * @param array $categories List of terms. * @return array */ protected function build_category_tree( $categories ) { $categories_by_parent = []; foreach ( $categories as $category ) { if ( ! isset( $categories_by_parent[ 'cat-' . $category->parent ] ) ) { $categories_by_parent[ 'cat-' . $category->parent ] = []; } $categories_by_parent[ 'cat-' . $category->parent ][] = $category; } $tree = $categories_by_parent['cat-0']; unset( $categories_by_parent['cat-0'] ); foreach ( $tree as $category ) { if ( ! empty( $categories_by_parent[ 'cat-' . $category->term_id ] ) ) { $category->children = $this->fill_category_children( $categories_by_parent[ 'cat-' . $category->term_id ], $categories_by_parent ); } } return $tree; } /** * Build hierarchical tree of categories by appending children in the tree. * * @param array $categories List of terms. * @param array $categories_by_parent List of terms grouped by parent. * @return array */ protected function fill_category_children( $categories, $categories_by_parent ) { foreach ( $categories as $category ) { if ( ! empty( $categories_by_parent[ 'cat-' . $category->term_id ] ) ) { $category->children = $this->fill_category_children( $categories_by_parent[ 'cat-' . $category->term_id ], $categories_by_parent ); } } return $categories; } /** * Render the category list as a dropdown. * * @param array $categories List of terms. * @param array $attributes Block attributes. Default empty array. * @param int $uid Unique ID for the rendered block, used for HTML IDs. * @return string Rendered output. */ protected function renderDropdown( $categories, $attributes, $uid ) { $aria_label = empty( $attributes['hasCount'] ) ? __( 'List of categories', 'woocommerce' ) : __( 'List of categories with their product counts', 'woocommerce' ); $output = ' <div class="wc-block-product-categories__dropdown"> <label class="screen-reader-text" for="' . esc_attr( $uid ) . '-select" > ' . esc_html__( 'Select a category', 'woocommerce' ) . ' </label> <select aria-label="' . esc_attr( $aria_label ) . '" id="' . esc_attr( $uid ) . '-select"> <option value="false" hidden> ' . esc_html__( 'Select a category', 'woocommerce' ) . ' </option> ' . $this->renderDropdownOptions( $categories, $attributes, $uid ) . ' </select> </div> <button type="button" class="wc-block-product-categories__button" aria-label="' . esc_html__( 'Go to category', 'woocommerce' ) . '" onclick="const url = document.getElementById( \'' . esc_attr( $uid ) . '-select\' ).value; if ( \'false\' !== url ) document.location.href = url;" > <svg aria-hidden="true" role="img" focusable="false" class="dashicon dashicons-arrow-right-alt2" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" > <path d="M6 15l5-5-5-5 1-2 7 7-7 7z" /> </svg> </button> '; return $output; } /** * Render dropdown options list. * * @param array $categories List of terms. * @param array $attributes Block attributes. Default empty array. * @param int $uid Unique ID for the rendered block, used for HTML IDs. * @param int $depth Current depth. * @return string Rendered output. */ protected function renderDropdownOptions( $categories, $attributes, $uid, $depth = 0 ) { $output = ''; foreach ( $categories as $category ) { $output .= ' <option value="' . esc_attr( get_term_link( $category->term_id, 'product_cat' ) ) . '"> ' . str_repeat( '−', $depth ) . ' ' . esc_html( $category->name ) . ' ' . $this->getCount( $category, $attributes ) . ' </option> ' . ( ! empty( $category->children ) ? $this->renderDropdownOptions( $category->children, $attributes, $uid, $depth + 1 ) : '' ) . ' '; } return $output; } /** * Render the category list as a list. * * @param array $categories List of terms. * @param array $attributes Block attributes. Default empty array. * @param int $uid Unique ID for the rendered block, used for HTML IDs. * @param int $depth Current depth. * @return string Rendered output. */ protected function renderList( $categories, $attributes, $uid, $depth = 0 ) { $classes = [ 'wc-block-product-categories-list', 'wc-block-product-categories-list--depth-' . absint( $depth ), ]; if ( ! empty( $attributes['hasImage'] ) ) { $classes[] = 'wc-block-product-categories-list--has-images'; } $output = '<ul class="' . esc_attr( implode( ' ', $classes ) ) . '">' . $this->renderListItems( $categories, $attributes, $uid, $depth ) . '</ul>'; return $output; } /** * Render a list of terms. * * @param array $categories List of terms. * @param array $attributes Block attributes. Default empty array. * @param int $uid Unique ID for the rendered block, used for HTML IDs. * @param int $depth Current depth. * @return string Rendered output. */ protected function renderListItems( $categories, $attributes, $uid, $depth = 0 ) { $output = ''; foreach ( $categories as $category ) { $output .= ' <li class="wc-block-product-categories-list-item"> <a href="' . esc_attr( get_term_link( $category->term_id, 'product_cat' ) ) . '">' . $this->get_image_html( $category, $attributes ) . esc_html( $category->name ) . '</a> ' . $this->getCount( $category, $attributes ) . ' ' . ( ! empty( $category->children ) ? $this->renderList( $category->children, $attributes, $uid, $depth + 1 ) : '' ) . ' </li> '; } return preg_replace( '/\r|\n/', '', $output ); } /** * Returns the category image html * * @param \WP_Term $category Term object. * @param array $attributes Block attributes. Default empty array. * @param string $size Image size, defaults to 'woocommerce_thumbnail'. * @return string */ public function get_image_html( $category, $attributes, $size = 'woocommerce_thumbnail' ) { if ( empty( $attributes['hasImage'] ) ) { return ''; } $image_id = get_term_meta( $category->term_id, 'thumbnail_id', true ); if ( ! $image_id ) { return '<span class="wc-block-product-categories-list-item__image wc-block-product-categories-list-item__image--placeholder">' . wc_placeholder_img( 'woocommerce_thumbnail' ) . '</span>'; } return '<span class="wc-block-product-categories-list-item__image">' . wp_get_attachment_image( $image_id, 'woocommerce_thumbnail' ) . '</span>'; } /** * Get the count, if displaying. * * @param object $category Term object. * @param array $attributes Block attributes. Default empty array. * @return string */ protected function getCount( $category, $attributes ) { if ( empty( $attributes['hasCount'] ) ) { return ''; } if ( $attributes['isDropdown'] ) { return '(' . absint( $category->count ) . ')'; } $screen_reader_text = sprintf( /* translators: %s number of products in cart. */ _n( '%d product', '%d products', absint( $category->count ), 'woocommerce' ), absint( $category->count ) ); return '<span class="wc-block-product-categories-list-item-count">' . '<span aria-hidden="true">' . absint( $category->count ) . '</span>' . '<span class="screen-reader-text">' . esc_html( $screen_reader_text ) . '</span>' . '</span>'; } } woocommerce-blocks/src/BlockTypes/ProductCategory.php 0000644 00000001335 15133551764 0017047 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * ProductCategory class. */ class ProductCategory extends AbstractProductGrid { /** * Block name. * * @var string */ protected $block_name = 'product-category'; /** * Set args specific to this block * * @param array $query_args Query args. */ protected function set_block_query_args( &$query_args ) {} /** * Get block attributes. * * @return array */ protected function get_block_type_attributes() { return array_merge( parent::get_block_type_attributes(), array( 'className' => $this->get_schema_string(), 'orderby' => $this->get_schema_orderby(), 'editMode' => $this->get_schema_boolean( true ), ) ); } } woocommerce-blocks/src/BlockTypes/ProductSearch.php 0000644 00000006665 15133551764 0016512 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * ProductSearch class. */ class ProductSearch extends AbstractBlock { /** * Block name. * * @var string */ protected $block_name = 'product-search'; /** * Get the frontend script handle for this block type. * * @param string $key Data to get, or default to everything. * @return null */ protected function get_block_type_script( $key = null ) { return null; } /** * Render the block. * * @param array $attributes Block attributes. * @param string $content Block content. * @return string Rendered block type output. */ protected function render( $attributes, $content ) { static $instance_id = 0; $attributes = wp_parse_args( $attributes, array( 'hasLabel' => true, 'align' => '', 'className' => '', 'label' => __( 'Search', 'woocommerce' ), 'placeholder' => __( 'Search products…', 'woocommerce' ), ) ); /** * Product Search event. * * Listens for product search form submission, and on submission fires a WP Hook named * `experimental__woocommerce_blocks-product-search`. This can be used by tracking extensions such as Google * Analytics to track searches. */ $this->asset_api->add_inline_script( 'wp-hooks', " window.addEventListener( 'DOMContentLoaded', () => { const forms = document.querySelectorAll( '.wc-block-product-search form' ); for ( const form of forms ) { form.addEventListener( 'submit', ( event ) => { const field = form.querySelector( '.wc-block-product-search__field' ); if ( field && field.value ) { wp.hooks.doAction( 'experimental__woocommerce_blocks-product-search', { event: event, searchTerm: field.value } ); } } ); } } ); ", 'after' ); $input_id = 'wc-block-search__input-' . ( ++$instance_id ); $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', array_filter( [ 'wc-block-product-search', $attributes['align'] ? 'align' . $attributes['align'] : '', ] ) ), ) ); $label_markup = $attributes['hasLabel'] ? sprintf( '<label for="%s" class="wc-block-product-search__label">%s</label>', esc_attr( $input_id ), esc_html( $attributes['label'] ) ) : sprintf( '<label for="%s" class="wc-block-product-search__label screen-reader-text">%s</label>', esc_attr( $input_id ), esc_html( $attributes['label'] ) ); $input_markup = sprintf( '<input type="search" id="%s" class="wc-block-product-search__field" placeholder="%s" name="s" />', esc_attr( $input_id ), esc_attr( $attributes['placeholder'] ) ); $button_markup = sprintf( '<button type="submit" class="wc-block-product-search__button" aria-label="%s"> <svg aria-hidden="true" role="img" focusable="false" class="dashicon dashicons-arrow-right-alt2" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"> <path d="M6 15l5-5-5-5 1-2 7 7-7 7z" /> </svg> </button>', esc_attr__( 'Search', 'woocommerce' ) ); $field_markup = ' <div class="wc-block-product-search__fields"> ' . $input_markup . $button_markup . ' <input type="hidden" name="post_type" value="product" /> </div> '; return sprintf( '<div %s><form role="search" method="get" action="%s">%s</form></div>', $wrapper_attributes, esc_url( home_url( '/' ) ), $label_markup . $field_markup ); } } woocommerce-blocks/src/BlockTypes/CartI2.php 0000644 00000013167 15133551764 0015023 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Assets; use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry; /** * Cart class. * * @internal */ class CartI2 extends AbstractBlock { /** * Block name. * * @var string */ protected $block_name = 'cart-i2'; /** * Get the editor script handle for this block type. * * @param string $key Data to get, or default to everything. * @return array|string; */ protected function get_block_type_editor_script( $key = null ) { $script = [ 'handle' => 'wc-' . $this->block_name . '-block', 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ), 'dependencies' => [ 'wc-blocks' ], ]; return $key ? $script[ $key ] : $script; } /** * Get the frontend script handle for this block type. * * @see $this->register_block_type() * @param string $key Data to get, or default to everything. * @return array|string */ protected function get_block_type_script( $key = null ) { $script = [ 'handle' => 'wc-' . $this->block_name . '-block-frontend', 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ), 'dependencies' => [], ]; return $key ? $script[ $key ] : $script; } /** * Enqueue frontend assets for this block, just in time for rendering. * * @param array $attributes Any attributes that currently are available from the block. */ protected function enqueue_assets( array $attributes ) { do_action( 'woocommerce_blocks_enqueue_cart_block_scripts_before' ); parent::enqueue_assets( $attributes ); do_action( 'woocommerce_blocks_enqueue_cart_block_scripts_after' ); } /** * Append frontend scripts when rendering the Cart block. * * @param array $attributes Block attributes. * @param string $content Block content. * @return string Rendered block type output. */ protected function render( $attributes, $content ) { // Deregister core cart scripts and styles. wp_dequeue_script( 'wc-cart' ); wp_dequeue_script( 'wc-password-strength-meter' ); wp_dequeue_script( 'selectWoo' ); wp_dequeue_style( 'select2' ); return $this->inject_html_data_attributes( $content, $attributes ); } /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); $this->asset_data_registry->add( 'shippingCountries', function() { return $this->deep_sort_with_accents( WC()->countries->get_shipping_countries() ); }, true ); $this->asset_data_registry->add( 'shippingStates', function() { return $this->deep_sort_with_accents( WC()->countries->get_shipping_country_states() ); }, true ); $this->asset_data_registry->add( 'countryLocale', function() { // Merge country and state data to work around https://github.com/woocommerce/woocommerce/issues/28944. $country_locale = wc()->countries->get_country_locale(); $states = wc()->countries->get_states(); foreach ( $states as $country => $states ) { if ( empty( $states ) ) { $country_locale[ $country ]['state']['required'] = false; $country_locale[ $country ]['state']['hidden'] = true; } } return $country_locale; }, true ); $this->asset_data_registry->add( 'baseLocation', wc_get_base_location(), true ); $this->asset_data_registry->add( 'isShippingCalculatorEnabled', filter_var( get_option( 'woocommerce_enable_shipping_calc' ), FILTER_VALIDATE_BOOLEAN ), true ); $this->asset_data_registry->add( 'displayItemizedTaxes', 'itemized' === get_option( 'woocommerce_tax_total_display' ), true ); $this->asset_data_registry->add( 'displayCartPricesIncludingTax', 'incl' === get_option( 'woocommerce_tax_display_cart' ), true ); $this->asset_data_registry->add( 'taxesEnabled', wc_tax_enabled(), true ); $this->asset_data_registry->add( 'couponsEnabled', wc_coupons_enabled(), true ); $this->asset_data_registry->add( 'shippingEnabled', wc_shipping_enabled(), true ); $this->asset_data_registry->add( 'hasDarkEditorStyleSupport', current_theme_supports( 'dark-editor-style' ), true ); $this->asset_data_registry->register_page_id( isset( $attributes['checkoutPageId'] ) ? $attributes['checkoutPageId'] : 0 ); // Hydrate the following data depending on admin or frontend context. if ( ! is_admin() && ! WC()->is_rest_api_request() ) { $this->hydrate_from_api(); } do_action( 'woocommerce_blocks_cart_enqueue_data' ); } /** * Removes accents from an array of values, sorts by the values, then returns the original array values sorted. * * @param array $array Array of values to sort. * @return array Sorted array. */ protected function deep_sort_with_accents( $array ) { if ( ! is_array( $array ) || empty( $array ) ) { return $array; } if ( is_array( reset( $array ) ) ) { return array_map( [ $this, 'deep_sort_with_accents' ], $array ); } $array_without_accents = array_map( 'remove_accents', array_map( 'wc_strtolower', array_map( 'html_entity_decode', $array ) ) ); asort( $array_without_accents ); return array_replace( $array_without_accents, $array ); } /** * Hydrate the cart block with data from the API. */ protected function hydrate_from_api() { $this->asset_data_registry->hydrate_api_request( '/wc/store/cart' ); } } woocommerce-blocks/src/BlockTypes/AbstractDynamicBlock.php 0000644 00000003544 15133551764 0017760 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * AbstractDynamicBlock class. */ abstract class AbstractDynamicBlock extends AbstractBlock { /** * Get the frontend script handle for this block type. * * @param string $key Data to get, or default to everything. * @return null */ protected function get_block_type_script( $key = null ) { return null; } /** * Get block attributes. * * @return array */ protected function get_block_type_attributes() { return array(); } /** * Get the schema for the alignment property. * * @return array Property definition for align. */ protected function get_schema_align() { return array( 'type' => 'string', 'enum' => array( 'left', 'center', 'right', 'wide', 'full' ), ); } /** * Get the schema for a list of IDs. * * @return array Property definition for a list of numeric ids. */ protected function get_schema_list_ids() { return array( 'type' => 'array', 'items' => array( 'type' => 'number', ), 'default' => array(), ); } /** * Get the schema for a boolean value. * * @param string $default The default value. * @return array Property definition. */ protected function get_schema_boolean( $default = true ) { return array( 'type' => 'boolean', 'default' => $default, ); } /** * Get the schema for a numeric value. * * @param string $default The default value. * @return array Property definition. */ protected function get_schema_number( $default ) { return array( 'type' => 'number', 'default' => $default, ); } /** * Get the schema for a string value. * * @param string $default The default value. * @return array Property definition. */ protected function get_schema_string( $default = '' ) { return array( 'type' => 'string', 'default' => $default, ); } } woocommerce-blocks/src/BlockTypes/MiniCart.php 0000644 00000023227 15133551764 0015443 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Assets; use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\CartController; /** * Mini Cart class. * * @internal */ class MiniCart extends AbstractBlock { /** * Block name. * * @var string */ protected $block_name = 'mini-cart'; /** * Array of scripts that will be lazy loaded when interacting with the block. * * @var string[] */ protected $scripts_to_lazy_load = array(); /** * Get the editor script handle for this block type. * * @param string $key Data to get, or default to everything. * @return array|string; */ protected function get_block_type_editor_script( $key = null ) { $script = [ 'handle' => 'wc-' . $this->block_name . '-block', 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ), 'dependencies' => [ 'wc-blocks' ], ]; return $key ? $script[ $key ] : $script; } /** * Get the frontend script handle for this block type. * * @see $this->register_block_type() * @param string $key Data to get, or default to everything. * @return array|string */ protected function get_block_type_script( $key = null ) { if ( is_cart() || is_checkout() ) { return; } $script = [ 'handle' => 'wc-' . $this->block_name . '-block-frontend', 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ), 'dependencies' => [], ]; return $key ? $script[ $key ] : $script; } /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { if ( is_cart() || is_checkout() ) { return; } parent::enqueue_data( $attributes ); // Hydrate the following data depending on admin or frontend context. if ( ! is_admin() && ! WC()->is_rest_api_request() ) { $this->hydrate_from_api(); } $script_data = $this->asset_api->get_script_data( 'build/mini-cart-component-frontend.js' ); $num_dependencies = count( $script_data['dependencies'] ); $wp_scripts = wp_scripts(); for ( $i = 0; $i < $num_dependencies; $i++ ) { $dependency = $script_data['dependencies'][ $i ]; foreach ( $wp_scripts->registered as $script ) { if ( $script->handle === $dependency ) { $this->append_script_and_deps_src( $script ); break; } } } $this->scripts_to_lazy_load['wc-block-mini-cart-component-frontend'] = array( 'src' => $script_data['src'], 'version' => $script_data['version'], ); $this->asset_data_registry->add( 'mini_cart_block_frontend_dependencies', $this->scripts_to_lazy_load, true ); $this->asset_data_registry->add( 'displayCartPricesIncludingTax', 'incl' === get_option( 'woocommerce_tax_display_cart' ), true ); do_action( 'woocommerce_blocks_cart_enqueue_data' ); } /** * Hydrate the cart block with data from the API. */ protected function hydrate_from_api() { $this->asset_data_registry->hydrate_api_request( '/wc/store/cart' ); } /** * Returns the script data given its handle. * * @param string $handle Handle of the script. * * @return array Array containing the script data. */ protected function get_script_from_handle( $handle ) { $wp_scripts = wp_scripts(); foreach ( $wp_scripts->registered as $script ) { if ( $script->handle === $handle ) { return $script; } } return ''; } /** * Recursively appends a scripts and its dependencies into the * scripts_to_lazy_load array. * * @param string $script Array containing script data. */ protected function append_script_and_deps_src( $script ) { $wp_scripts = wp_scripts(); // This script and its dependencies have already been appended. if ( array_key_exists( $script->handle, $this->scripts_to_lazy_load ) ) { return; } if ( count( $script->deps ) > 0 ) { foreach ( $script->deps as $dep ) { if ( ! array_key_exists( $dep, $this->scripts_to_lazy_load ) ) { $dep_script = $this->get_script_from_handle( $dep ); $this->append_script_and_deps_src( $dep_script ); } } } $this->scripts_to_lazy_load[ $script->handle ] = array( 'src' => $script->src, 'version' => $script->ver, 'before' => $wp_scripts->print_inline_script( $script->handle, 'before', false ), 'after' => $wp_scripts->print_inline_script( $script->handle, 'after', false ), 'translations' => $wp_scripts->print_translations( $script->handle, false ), ); } /** * Append frontend scripts when rendering the Mini Cart block. * * @param array $attributes Block attributes. * @param string $content Block content. * * @return string Rendered block type output. */ protected function render( $attributes, $content ) { return $this->inject_html_data_attributes( $content . $this->get_markup(), $attributes ); } /** * Render the markup for the Mini Cart block. * * @return string The HTML markup. */ protected function get_markup() { if ( is_admin() || WC()->is_rest_api_request() ) { // In the editor we will display the placeholder, so no need to load // real cart data and to print the markup. return ''; } $cart_controller = new CartController(); $cart = $cart_controller->get_cart_instance(); $cart_contents_count = $cart->get_cart_contents_count(); $cart_contents = $cart->get_cart(); $cart_contents_total = $cart->get_subtotal(); if ( $cart->display_prices_including_tax() ) { $cart_contents_total += $cart->get_subtotal_tax(); } $button_text = sprintf( /* translators: %d is the number of products in the cart. */ _n( '%d item', '%d items', $cart_contents_count, 'woocommerce' ), $cart_contents_count ); $aria_label = sprintf( /* translators: %1$d is the number of products in the cart. %2$s is the cart total */ _n( '%1$d item in cart, total price of %2$s', '%1$d items in cart, total price of %2$s', $cart_contents_count, 'woocommerce' ), $cart_contents_count, wp_strip_all_tags( wc_price( $cart_contents_total ) ) ); $title = sprintf( /* translators: %d is the count of items in the cart. */ _n( 'Your cart (%d item)', 'Your cart (%d items)', $cart_contents_count, 'woocommerce' ), $cart_contents_count ); if ( is_cart() || is_checkout() ) { return '<div class="wc-block-mini-cart"> <button class="wc-block-mini-cart__button" aria-label="' . $aria_label . '" disabled>' . $button_text . '</button> </div>'; } return '<div class="wc-block-mini-cart"> <button class="wc-block-mini-cart__button" aria-label="' . $aria_label . '">' . $button_text . '</button> <div class="wc-block-mini-cart__drawer is-loading is-mobile wc-block-components-drawer__screen-overlay wc-block-components-drawer__screen-overlay--is-hidden" aria-hidden="true"> <div class="components-modal__frame wc-block-components-drawer"> <div class="components-modal__content"> <div class="components-modal__header"> <div class="components-modal__header-heading-container"> <h1 id="components-modal-header-1" class="components-modal__header-heading">' . $title . '</h1> </div> </div>' . $this->get_cart_contents_markup( $cart_contents ) . '</div> </div> </div> </div>'; } /** * Render the markup of the Cart contents. * * @param array $cart_contents Array of contents in the cart. * * @return string The HTML markup. */ protected function get_cart_contents_markup( $cart_contents ) { // Force mobile styles. return '<table class="wc-block-cart-items"> <thead> <tr class="wc-block-cart-items__header"> <th class="wc-block-cart-items__header-image"><span /></th> <th class="wc-block-cart-items__header-product"><span /></th> <th class="wc-block-cart-items__header-total"><span /></th> </tr> </thead> <tbody>' . implode( array_map( array( $this, 'get_cart_item_markup' ), $cart_contents ) ) . '</tbody> </table>'; } /** * Render the skeleton of a Cart item. * * @return string The skeleton HTML markup. */ protected function get_cart_item_markup() { return '<tr class="wc-block-cart-items__row"> <td class="wc-block-cart-item__image"> <a href=""><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" width="1" height="1" /></a> </td> <td class="wc-block-cart-item__product"> <div class="wc-block-components-product-name"></div> <div class="wc-block-components-product-price"></div> <div class="wc-block-components-product-metadata"></div> <div class="wc-block-cart-item__quantity"> <div class="wc-block-components-quantity-selector"> <input class="wc-block-components-quantity-selector__input" type="number" step="1" min="0" value="1" /> <button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus">-</button> <button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus">+</button> </div> <button class="wc-block-cart-item__remove-link"></button> </div> </td> <td class="wc-block-cart-item__total"> <div class="wc-block-cart-item__total-price-and-sale-badge-wrapper"> <div class="wc-block-components-product-price"></div> </div> </td> </tr>'; } } woocommerce-blocks/src/BlockTypes/ProductNew.php 0000644 00000000700 15133551764 0016016 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * ProductNew class. */ class ProductNew extends AbstractProductGrid { /** * Block name. * * @var string */ protected $block_name = 'product-new'; /** * Set args specific to this block * * @param array $query_args Query args. */ protected function set_block_query_args( &$query_args ) { $query_args['orderby'] = 'date'; $query_args['order'] = 'DESC'; } } woocommerce-blocks/src/BlockTypes/ProductTopRated.php 0000644 00000000650 15133551764 0017013 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * ProductTopRated class. */ class ProductTopRated extends AbstractProductGrid { /** * Block name. * * @var string */ protected $block_name = 'product-top-rated'; /** * Force orderby to rating. * * @param array $query_args Query args. */ protected function set_block_query_args( &$query_args ) { $query_args['orderby'] = 'rating'; } } woocommerce-blocks/src/BlockTypes/SingleProduct.php 0000644 00000001773 15133551764 0016521 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * SingleProduct class. */ class SingleProduct extends AbstractBlock { /** * Block name. * * @var string */ protected $block_name = 'single-product'; /** * Get the editor script handle for this block type. * * @param string $key Data to get, or default to everything. * @return array|string; */ protected function get_block_type_editor_script( $key = null ) { $script = [ 'handle' => 'wc-' . $this->block_name . '-block', 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ), 'dependencies' => [ 'wc-blocks' ], ]; return $key ? $script[ $key ] : $script; } /** * Render the block on the frontend. * * @param array $attributes Block attributes. * @param string $content Block content. * @return string Rendered block type output. */ protected function render( $attributes, $content ) { return $this->inject_html_data_attributes( $content, $attributes ); } } woocommerce-blocks/src/BlockTypes/Cart.php 0000644 00000023634 15133551764 0014630 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Assets; use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry; /** * Cart class. * * @internal */ class Cart extends AbstractBlock { /** * Block name. * * @var string */ protected $block_name = 'cart'; /** * Get the editor script handle for this block type. * * @param string $key Data to get, or default to everything. * @return array|string; */ protected function get_block_type_editor_script( $key = null ) { $script = [ 'handle' => 'wc-' . $this->block_name . '-block', 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ), 'dependencies' => [ 'wc-blocks' ], ]; return $key ? $script[ $key ] : $script; } /** * Get the frontend script handle for this block type. * * @see $this->register_block_type() * @param string $key Data to get, or default to everything. * @return array|string */ protected function get_block_type_script( $key = null ) { $script = [ 'handle' => 'wc-' . $this->block_name . '-block-frontend', 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ), 'dependencies' => [], ]; return $key ? $script[ $key ] : $script; } /** * Enqueue frontend assets for this block, just in time for rendering. * * @param array $attributes Any attributes that currently are available from the block. */ protected function enqueue_assets( array $attributes ) { do_action( 'woocommerce_blocks_enqueue_cart_block_scripts_before' ); parent::enqueue_assets( $attributes ); do_action( 'woocommerce_blocks_enqueue_cart_block_scripts_after' ); } /** * Append frontend scripts when rendering the Cart block. * * @param array $attributes Block attributes. * @param string $content Block content. * @return string Rendered block type output. */ protected function render( $attributes, $content ) { // Deregister core cart scripts and styles. wp_dequeue_script( 'wc-cart' ); wp_dequeue_script( 'wc-password-strength-meter' ); wp_dequeue_script( 'selectWoo' ); wp_dequeue_style( 'select2' ); return $this->inject_html_data_attributes( $content . $this->get_skeleton(), $attributes ); } /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); $this->asset_data_registry->add( 'shippingCountries', function() { return $this->deep_sort_with_accents( WC()->countries->get_shipping_countries() ); }, true ); $this->asset_data_registry->add( 'shippingStates', function() { return $this->deep_sort_with_accents( WC()->countries->get_shipping_country_states() ); }, true ); $this->asset_data_registry->add( 'countryLocale', function() { // Merge country and state data to work around https://github.com/woocommerce/woocommerce/issues/28944. $country_locale = wc()->countries->get_country_locale(); $states = wc()->countries->get_states(); foreach ( $states as $country => $states ) { if ( empty( $states ) ) { $country_locale[ $country ]['state']['required'] = false; $country_locale[ $country ]['state']['hidden'] = true; } } return $country_locale; }, true ); $this->asset_data_registry->add( 'baseLocation', wc_get_base_location(), true ); $this->asset_data_registry->add( 'isShippingCalculatorEnabled', filter_var( get_option( 'woocommerce_enable_shipping_calc' ), FILTER_VALIDATE_BOOLEAN ), true ); $this->asset_data_registry->add( 'displayItemizedTaxes', 'itemized' === get_option( 'woocommerce_tax_total_display' ), true ); $this->asset_data_registry->add( 'displayCartPricesIncludingTax', 'incl' === get_option( 'woocommerce_tax_display_cart' ), true ); $this->asset_data_registry->add( 'taxesEnabled', wc_tax_enabled(), true ); $this->asset_data_registry->add( 'couponsEnabled', wc_coupons_enabled(), true ); $this->asset_data_registry->add( 'shippingEnabled', wc_shipping_enabled(), true ); $this->asset_data_registry->add( 'hasDarkEditorStyleSupport', current_theme_supports( 'dark-editor-style' ), true ); $this->asset_data_registry->register_page_id( isset( $attributes['checkoutPageId'] ) ? $attributes['checkoutPageId'] : 0 ); // Hydrate the following data depending on admin or frontend context. if ( ! is_admin() && ! WC()->is_rest_api_request() ) { $this->hydrate_from_api(); } do_action( 'woocommerce_blocks_cart_enqueue_data' ); } /** * Removes accents from an array of values, sorts by the values, then returns the original array values sorted. * * @param array $array Array of values to sort. * @return array Sorted array. */ protected function deep_sort_with_accents( $array ) { if ( ! is_array( $array ) || empty( $array ) ) { return $array; } if ( is_array( reset( $array ) ) ) { return array_map( [ $this, 'deep_sort_with_accents' ], $array ); } $array_without_accents = array_map( 'remove_accents', array_map( 'wc_strtolower', array_map( 'html_entity_decode', $array ) ) ); asort( $array_without_accents ); return array_replace( $array_without_accents, $array ); } /** * Hydrate the cart block with data from the API. */ protected function hydrate_from_api() { $this->asset_data_registry->hydrate_api_request( '/wc/store/cart' ); } /** * Render skeleton markup for the cart block. */ protected function get_skeleton() { return ' <div class="wc-block-skeleton wc-block-components-sidebar-layout wc-block-cart wc-block-cart--is-loading wc-block-cart--skeleton hidden" aria-hidden="true"> <div class="wc-block-components-main wc-block-cart__main"> <h2 class="wc-block-components-title"><span></span></h2> <table class="wc-block-cart-items"> <thead> <tr class="wc-block-cart-items__header"> <th class="wc-block-cart-items__header-image"><span /></th> <th class="wc-block-cart-items__header-product"><span /></th> <th class="wc-block-cart-items__header-total"><span /></th> </tr> </thead> <tbody> <tr class="wc-block-cart-items__row"> <td class="wc-block-cart-item__image"> <a href=""><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" width="1" height="1" /></a> </td> <td class="wc-block-cart-item__product"> <div class="wc-block-components-product-name"></div> <div class="wc-block-components-product-price"></div> <div class="wc-block-components-product-metadata"></div> <div class="wc-block-components-quantity-selector"> <input class="wc-block-components-quantity-selector__input" type="number" step="1" min="0" value="1" /> <button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus">-</button> <button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus">+</button> </div> </td> <td class="wc-block-cart-item__total"> <div class="wc-block-components-product-price"></div> </td> </tr> <tr class="wc-block-cart-items__row"> <td class="wc-block-cart-item__image"> <a href=""><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" width="1" height="1" /></a> </td> <td class="wc-block-cart-item__product"> <div class="wc-block-components-product-name"></div> <div class="wc-block-components-product-price"></div> <div class="wc-block-components-product-metadata"></div> <div class="wc-block-components-quantity-selector"> <input class="wc-block-components-quantity-selector__input" type="number" step="1" min="0" value="1" /> <button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus">-</button> <button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus">+</button> </div> </td> <td class="wc-block-cart-item__total"> <div class="wc-block-components-product-price"></div> </td> </tr> <tr class="wc-block-cart-items__row"> <td class="wc-block-cart-item__image"> <a href=""><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" width="1" height="1" /></a> </td> <td class="wc-block-cart-item__product"> <div class="wc-block-components-product-name"></div> <div class="wc-block-components-product-price"></div> <div class="wc-block-components-product-metadata"></div> <div class="wc-block-components-quantity-selector"> <input class="wc-block-components-quantity-selector__input" type="number" step="1" min="0" value="1" /> <button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus">-</button> <button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus">+</button> </div> </td> <td class="wc-block-cart-item__total"> <div class="wc-block-components-product-price"></div> </td> </tr> </tbody> </table> </div> <div class="wc-block-components-sidebar wc-block-cart__sidebar"> <div class="components-card"></div> </div> </div> ' . $this->get_skeleton_inline_script(); } } woocommerce-blocks/src/BlockTypes/AttributeFilter.php 0000644 00000001404 15133551764 0017037 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * AttributeFilter class. */ class AttributeFilter extends AbstractBlock { /** * Block name. * * @var string */ protected $block_name = 'attribute-filter'; /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); $this->asset_data_registry->add( 'attributes', array_values( wc_get_attribute_taxonomies() ), true ); } } woocommerce-blocks/src/BlockTypes/AbstractProductGrid.php 0000644 00000036645 15133551764 0017657 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; use Automattic\WooCommerce\Blocks\Utils\BlocksWpQuery; use Automattic\WooCommerce\Blocks\StoreApi\SchemaController; use Automattic\WooCommerce\Blocks\Package; /** * AbstractProductGrid class. */ abstract class AbstractProductGrid extends AbstractDynamicBlock { /** * Attributes. * * @var array */ protected $attributes = array(); /** * InnerBlocks content. * * @var string */ protected $content = ''; /** * Query args. * * @var array */ protected $query_args = array(); /** * Get a set of attributes shared across most of the grid blocks. * * @return array List of block attributes with type and defaults. */ protected function get_block_type_attributes() { return array( 'className' => $this->get_schema_string(), 'columns' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ), 'rows' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 3 ) ), 'categories' => $this->get_schema_list_ids(), 'catOperator' => array( 'type' => 'string', 'default' => 'any', ), 'contentVisibility' => $this->get_schema_content_visibility(), 'align' => $this->get_schema_align(), 'alignButtons' => $this->get_schema_boolean( false ), 'isPreview' => $this->get_schema_boolean( false ), ); } /** * Include and render the dynamic block. * * @param array $attributes Block attributes. Default empty array. * @param string $content Block content. Default empty string. * @return string Rendered block type output. */ protected function render( $attributes = array(), $content = '' ) { $this->attributes = $this->parse_attributes( $attributes ); $this->content = $content; $this->query_args = $this->parse_query_args(); $products = array_filter( array_map( 'wc_get_product', $this->get_products() ) ); if ( ! $products ) { return ''; } /** * Product List Render event. * * Fires a WP Hook named `experimental__woocommerce_blocks-product-list-render` on render so that the client * can add event handling when certain products are displayed. This can be used by tracking extensions such * as Google Analytics to track impressions. * * Provides the list of product data (shaped like the Store API responses) and the block name. */ $this->asset_api->add_inline_script( 'wp-hooks', ' window.addEventListener( "DOMContentLoaded", () => { wp.hooks.doAction( "experimental__woocommerce_blocks-product-list-render", { products: JSON.parse( decodeURIComponent( "' . esc_js( rawurlencode( wp_json_encode( array_map( [ Package::container()->get( SchemaController::class )->get( 'product' ), 'get_item_response' ], $products ) ) ) ) . '" ) ), listName: "' . esc_js( $this->block_name ) . '" } ); } ); ', 'after' ); return sprintf( '<div class="%s"><ul class="wc-block-grid__products">%s</ul></div>', esc_attr( $this->get_container_classes() ), implode( '', array_map( array( $this, 'render_product' ), $products ) ) ); } /** * Get the schema for the contentVisibility attribute * * @return array List of block attributes with type and defaults. */ protected function get_schema_content_visibility() { return array( 'type' => 'object', 'properties' => array( 'title' => $this->get_schema_boolean( true ), 'price' => $this->get_schema_boolean( true ), 'rating' => $this->get_schema_boolean( true ), 'button' => $this->get_schema_boolean( true ), ), ); } /** * Get the schema for the orderby attribute. * * @return array Property definition of `orderby` attribute. */ protected function get_schema_orderby() { return array( 'type' => 'string', 'enum' => array( 'date', 'popularity', 'price_asc', 'price_desc', 'rating', 'title', 'menu_order' ), 'default' => 'date', ); } /** * Get the block's attributes. * * @param array $attributes Block attributes. Default empty array. * @return array Block attributes merged with defaults. */ protected function parse_attributes( $attributes ) { // These should match what's set in JS `registerBlockType`. $defaults = array( 'columns' => wc_get_theme_support( 'product_blocks::default_columns', 3 ), 'rows' => wc_get_theme_support( 'product_blocks::default_rows', 3 ), 'alignButtons' => false, 'categories' => array(), 'catOperator' => 'any', 'contentVisibility' => array( 'title' => true, 'price' => true, 'rating' => true, 'button' => true, ), ); return wp_parse_args( $attributes, $defaults ); } /** * Parse query args. * * @return array */ protected function parse_query_args() { $query_args = array( 'post_type' => 'product', 'post_status' => 'publish', 'fields' => 'ids', 'ignore_sticky_posts' => true, 'no_found_rows' => false, 'orderby' => '', 'order' => '', 'meta_query' => WC()->query->get_meta_query(), // phpcs:ignore WordPress.DB.SlowDBQuery 'tax_query' => array(), // phpcs:ignore WordPress.DB.SlowDBQuery 'posts_per_page' => $this->get_products_limit(), ); $this->set_block_query_args( $query_args ); $this->set_ordering_query_args( $query_args ); $this->set_categories_query_args( $query_args ); $this->set_visibility_query_args( $query_args ); return $query_args; } /** * Parse query args. * * @param array $query_args Query args. */ protected function set_ordering_query_args( &$query_args ) { if ( isset( $this->attributes['orderby'] ) ) { if ( 'price_desc' === $this->attributes['orderby'] ) { $query_args['orderby'] = 'price'; $query_args['order'] = 'DESC'; } elseif ( 'price_asc' === $this->attributes['orderby'] ) { $query_args['orderby'] = 'price'; $query_args['order'] = 'ASC'; } elseif ( 'date' === $this->attributes['orderby'] ) { $query_args['orderby'] = 'date'; $query_args['order'] = 'DESC'; } else { $query_args['orderby'] = $this->attributes['orderby']; } } $query_args = array_merge( $query_args, WC()->query->get_catalog_ordering_args( $query_args['orderby'], $query_args['order'] ) ); } /** * Set args specific to this block * * @param array $query_args Query args. */ abstract protected function set_block_query_args( &$query_args ); /** * Set categories query args. * * @param array $query_args Query args. */ protected function set_categories_query_args( &$query_args ) { if ( ! empty( $this->attributes['categories'] ) ) { $categories = array_map( 'absint', $this->attributes['categories'] ); $query_args['tax_query'][] = array( 'taxonomy' => 'product_cat', 'terms' => $categories, 'field' => 'term_id', 'operator' => 'all' === $this->attributes['catOperator'] ? 'AND' : 'IN', /* * When cat_operator is AND, the children categories should be excluded, * as only products belonging to all the children categories would be selected. */ 'include_children' => 'all' === $this->attributes['catOperator'] ? false : true, ); } } /** * Set visibility query args. * * @param array $query_args Query args. */ protected function set_visibility_query_args( &$query_args ) { $product_visibility_terms = wc_get_product_visibility_term_ids(); $product_visibility_not_in = array( $product_visibility_terms['exclude-from-catalog'] ); if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) { $product_visibility_not_in[] = $product_visibility_terms['outofstock']; } $query_args['tax_query'][] = array( 'taxonomy' => 'product_visibility', 'field' => 'term_taxonomy_id', 'terms' => $product_visibility_not_in, 'operator' => 'NOT IN', ); } /** * Works out the item limit based on rows and columns, or returns default. * * @return int */ protected function get_products_limit() { if ( isset( $this->attributes['rows'], $this->attributes['columns'] ) && ! empty( $this->attributes['rows'] ) ) { $this->attributes['limit'] = intval( $this->attributes['columns'] ) * intval( $this->attributes['rows'] ); } return intval( $this->attributes['limit'] ); } /** * Run the query and return an array of product IDs * * @return array List of product IDs */ protected function get_products() { $is_cacheable = (bool) apply_filters( 'woocommerce_blocks_product_grid_is_cacheable', true, $this->query_args ); $transient_version = \WC_Cache_Helper::get_transient_version( 'product_query' ); $query = new BlocksWpQuery( $this->query_args ); $results = wp_parse_id_list( $is_cacheable ? $query->get_cached_posts( $transient_version ) : $query->get_posts() ); // Remove ordering query arguments which may have been added by get_catalog_ordering_args. WC()->query->remove_ordering_args(); // Prime caches to reduce future queries. if ( is_callable( '_prime_post_caches' ) ) { _prime_post_caches( $results ); } return $results; } /** * Get the list of classes to apply to this block. * * @return string space-separated list of classes. */ protected function get_container_classes() { $classes = array( 'wc-block-grid', "wp-block-{$this->block_name}", "wc-block-{$this->block_name}", "has-{$this->attributes['columns']}-columns", ); if ( $this->attributes['rows'] > 1 ) { $classes[] = 'has-multiple-rows'; } if ( isset( $this->attributes['align'] ) ) { $classes[] = "align{$this->attributes['align']}"; } if ( ! empty( $this->attributes['alignButtons'] ) ) { $classes[] = 'has-aligned-buttons'; } if ( ! empty( $this->attributes['className'] ) ) { $classes[] = $this->attributes['className']; } return implode( ' ', $classes ); } /** * Render a single products. * * @param \WC_Product $product Product object. * @return string Rendered product output. */ protected function render_product( $product ) { $data = (object) array( 'permalink' => esc_url( $product->get_permalink() ), 'image' => $this->get_image_html( $product ), 'title' => $this->get_title_html( $product ), 'rating' => $this->get_rating_html( $product ), 'price' => $this->get_price_html( $product ), 'badge' => $this->get_sale_badge_html( $product ), 'button' => $this->get_button_html( $product ), ); return apply_filters( 'woocommerce_blocks_product_grid_item_html', "<li class=\"wc-block-grid__product\"> <a href=\"{$data->permalink}\" class=\"wc-block-grid__product-link\"> {$data->image} {$data->title} </a> {$data->badge} {$data->price} {$data->rating} {$data->button} </li>", $data, $product ); } /** * Get the product image. * * @param \WC_Product $product Product. * @return string */ protected function get_image_html( $product ) { return '<div class="wc-block-grid__product-image">' . $product->get_image( 'woocommerce_thumbnail' ) . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * Get the product title. * * @param \WC_Product $product Product. * @return string */ protected function get_title_html( $product ) { if ( empty( $this->attributes['contentVisibility']['title'] ) ) { return ''; } return '<div class="wc-block-grid__product-title">' . wp_kses_post( $product->get_title() ) . '</div>'; } /** * Render the rating icons. * * @param WC_Product $product Product. * @return string Rendered product output. */ protected function get_rating_html( $product ) { if ( empty( $this->attributes['contentVisibility']['rating'] ) ) { return ''; } $rating_count = $product->get_rating_count(); $review_count = $product->get_review_count(); $average = $product->get_average_rating(); if ( $rating_count > 0 ) { return sprintf( '<div class="wc-block-grid__product-rating">%s</div>', wc_get_rating_html( $average, $rating_count ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ); } return ''; } /** * Get the price. * * @param \WC_Product $product Product. * @return string Rendered product output. */ protected function get_price_html( $product ) { if ( empty( $this->attributes['contentVisibility']['price'] ) ) { return ''; } return sprintf( '<div class="wc-block-grid__product-price price">%s</div>', wp_kses_post( $product->get_price_html() ) ); } /** * Get the sale badge. * * @param \WC_Product $product Product. * @return string Rendered product output. */ protected function get_sale_badge_html( $product ) { if ( empty( $this->attributes['contentVisibility']['price'] ) ) { return ''; } if ( ! $product->is_on_sale() ) { return; } return '<div class="wc-block-grid__product-onsale"> <span aria-hidden="true">' . esc_html__( 'Sale', 'woocommerce' ) . '</span> <span class="screen-reader-text">' . esc_html__( 'Product on sale', 'woocommerce' ) . '</span> </div>'; } /** * Get the button. * * @param \WC_Product $product Product. * @return string Rendered product output. */ protected function get_button_html( $product ) { if ( empty( $this->attributes['contentVisibility']['button'] ) ) { return ''; } return '<div class="wp-block-button wc-block-grid__product-add-to-cart">' . $this->get_add_to_cart( $product ) . '</div>'; } /** * Get the "add to cart" button. * * @param \WC_Product $product Product. * @return string Rendered product output. */ protected function get_add_to_cart( $product ) { $attributes = array( 'aria-label' => $product->add_to_cart_description(), 'data-quantity' => '1', 'data-product_id' => $product->get_id(), 'data-product_sku' => $product->get_sku(), 'rel' => 'nofollow', 'class' => 'wp-block-button__link add_to_cart_button', ); if ( $product->supports( 'ajax_add_to_cart' ) && $product->is_purchasable() && ( $product->is_in_stock() || $product->backorders_allowed() ) ) { $attributes['class'] .= ' ajax_add_to_cart'; } return sprintf( '<a href="%s" %s>%s</a>', esc_url( $product->add_to_cart_url() ), wc_implode_html_attributes( $attributes ), esc_html( $product->add_to_cart_text() ) ); } /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); $this->asset_data_registry->add( 'min_columns', wc_get_theme_support( 'product_blocks::min_columns', 1 ), true ); $this->asset_data_registry->add( 'max_columns', wc_get_theme_support( 'product_blocks::max_columns', 6 ), true ); $this->asset_data_registry->add( 'default_columns', wc_get_theme_support( 'product_blocks::default_columns', 3 ), true ); $this->asset_data_registry->add( 'min_rows', wc_get_theme_support( 'product_blocks::min_rows', 1 ), true ); $this->asset_data_registry->add( 'max_rows', wc_get_theme_support( 'product_blocks::max_rows', 6 ), true ); $this->asset_data_registry->add( 'default_rows', wc_get_theme_support( 'product_blocks::default_rows', 3 ), true ); } } woocommerce-blocks/src/BlockTypes/AtomicBlock.php 0000644 00000003155 15133551764 0016122 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * AtomicBlock class. * * @internal */ class AtomicBlock extends AbstractBlock { /** * Inject attributes and block name. * * @param array $attributes Block attributes. * @param string $content Block content. * @return string Rendered block type output. */ protected function render( $attributes, $content ) { return $this->inject_html_data_attributes( $content, $attributes ); } /** * Get the editor script data for this block type. * * @param string $key Data to get, or default to everything. * @return null */ protected function get_block_type_editor_script( $key = null ) { return null; } /** * Get the editor style handle for this block type. * * @return null */ protected function get_block_type_editor_style() { return null; } /** * Get the frontend script handle for this block type. * * @param string $key Data to get, or default to everything. * @return null */ protected function get_block_type_script( $key = null ) { return null; } /** * Get the frontend style handle for this block type. * * @return null */ protected function get_block_type_style() { return null; } /** * Converts block attributes to HTML data attributes. * * @param array $attributes Key value pairs of attributes. * @return string Rendered HTML attributes. */ protected function get_html_data_attributes( array $attributes ) { $data = parent::get_html_data_attributes( $attributes ); return trim( $data . ' data-block-name="' . esc_attr( $this->namespace . '/' . $this->block_name ) . '"' ); } } woocommerce-blocks/src/BlockTypes/Checkout.php 0000644 00000027405 15133551764 0015504 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * Checkout class. * * @internal */ class Checkout extends AbstractBlock { /** * Block name. * * @var string */ protected $block_name = 'checkout'; /** * Get the editor script handle for this block type. * * @param string $key Data to get, or default to everything. * @return array|string; */ protected function get_block_type_editor_script( $key = null ) { $script = [ 'handle' => 'wc-' . $this->block_name . '-block', 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ), 'dependencies' => [ 'wc-blocks' ], ]; return $key ? $script[ $key ] : $script; } /** * Get the frontend script handle for this block type. * * @see $this->register_block_type() * @param string $key Data to get, or default to everything. * @return array|string */ protected function get_block_type_script( $key = null ) { $script = [ 'handle' => 'wc-' . $this->block_name . '-block-frontend', 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ), 'dependencies' => [], ]; return $key ? $script[ $key ] : $script; } /** * Enqueue frontend assets for this block, just in time for rendering. * * @param array $attributes Any attributes that currently are available from the block. */ protected function enqueue_assets( array $attributes ) { do_action( 'woocommerce_blocks_enqueue_checkout_block_scripts_before' ); parent::enqueue_assets( $attributes ); do_action( 'woocommerce_blocks_enqueue_checkout_block_scripts_after' ); } /** * Append frontend scripts when rendering the block. * * @param array $attributes Block attributes. * @param string $content Block content. * @return string Rendered block type output. */ protected function render( $attributes, $content ) { if ( $this->is_checkout_endpoint() ) { // Note: Currently the block only takes care of the main checkout form -- if an endpoint is set, refer to the // legacy shortcode instead and do not render block. return '[woocommerce_checkout]'; } // Deregister core checkout scripts and styles. wp_dequeue_script( 'wc-checkout' ); wp_dequeue_script( 'wc-password-strength-meter' ); wp_dequeue_script( 'selectWoo' ); wp_dequeue_style( 'select2' ); // If the content is empty, we may have transformed from an older checkout block. Insert the default list of blocks. $regex_for_empty_block = '/<div class="[a-zA-Z0-9_\- ]*wp-block-woocommerce-checkout[a-zA-Z0-9_\- ]*"><\/div>/mi'; $is_empty = preg_match( $regex_for_empty_block, $content ); if ( $is_empty ) { $inner_blocks_html = '<div data-block-name="woocommerce/checkout-fields-block" class="wp-block-woocommerce-checkout-fields-block"></div><div data-block-name="woocommerce/checkout-totals-block" class="wp-block-woocommerce-checkout-totals-block"></div>'; $content = str_replace( '</div>', $inner_blocks_html . '</div>', $content ); } return $this->inject_html_data_attributes( $content, $attributes ); } /** * Check if we're viewing a checkout page endpoint, rather than the main checkout page itself. * * @return boolean */ protected function is_checkout_endpoint() { return is_wc_endpoint_url( 'order-pay' ) || is_wc_endpoint_url( 'order-received' ); } /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); $this->asset_data_registry->add( 'allowedCountries', function() { return $this->deep_sort_with_accents( WC()->countries->get_allowed_countries() ); }, true ); $this->asset_data_registry->add( 'allowedStates', function() { return $this->deep_sort_with_accents( WC()->countries->get_allowed_country_states() ); }, true ); $this->asset_data_registry->add( 'shippingCountries', function() { return $this->deep_sort_with_accents( WC()->countries->get_shipping_countries() ); }, true ); $this->asset_data_registry->add( 'shippingStates', function() { return $this->deep_sort_with_accents( WC()->countries->get_shipping_country_states() ); }, true ); $this->asset_data_registry->add( 'countryLocale', function() { // Merge country and state data to work around https://github.com/woocommerce/woocommerce/issues/28944. $country_locale = wc()->countries->get_country_locale(); $states = wc()->countries->get_states(); foreach ( $states as $country => $states ) { if ( empty( $states ) ) { $country_locale[ $country ]['state']['required'] = false; $country_locale[ $country ]['state']['hidden'] = true; } } return $country_locale; }, true ); $this->asset_data_registry->add( 'baseLocation', wc_get_base_location(), true ); $this->asset_data_registry->add( 'checkoutAllowsGuest', false === filter_var( WC()->checkout()->is_registration_required(), FILTER_VALIDATE_BOOLEAN ), true ); $this->asset_data_registry->add( 'checkoutAllowsSignup', filter_var( WC()->checkout()->is_registration_enabled(), FILTER_VALIDATE_BOOLEAN ), true ); $this->asset_data_registry->add( 'checkoutShowLoginReminder', filter_var( get_option( 'woocommerce_enable_checkout_login_reminder' ), FILTER_VALIDATE_BOOLEAN ), true ); $this->asset_data_registry->add( 'displayCartPricesIncludingTax', 'incl' === get_option( 'woocommerce_tax_display_cart' ), true ); $this->asset_data_registry->add( 'displayItemizedTaxes', 'itemized' === get_option( 'woocommerce_tax_total_display' ), true ); $this->asset_data_registry->add( 'taxesEnabled', wc_tax_enabled(), true ); $this->asset_data_registry->add( 'couponsEnabled', wc_coupons_enabled(), true ); $this->asset_data_registry->add( 'shippingEnabled', wc_shipping_enabled(), true ); $this->asset_data_registry->add( 'hasDarkEditorStyleSupport', current_theme_supports( 'dark-editor-style' ), true ); $this->asset_data_registry->register_page_id( isset( $attributes['cartPageId'] ) ? $attributes['cartPageId'] : 0 ); $is_block_editor = $this->is_block_editor(); // Hydrate the following data depending on admin or frontend context. if ( $is_block_editor && ! $this->asset_data_registry->exists( 'shippingMethodsExist' ) ) { $methods_exist = wc_get_shipping_method_count( false, true ) > 0; $this->asset_data_registry->add( 'shippingMethodsExist', $methods_exist ); } if ( $is_block_editor && ! $this->asset_data_registry->exists( 'globalShippingMethods' ) ) { $shipping_methods = WC()->shipping()->get_shipping_methods(); $formatted_shipping_methods = array_reduce( $shipping_methods, function( $acc, $method ) { if ( $method->supports( 'settings' ) ) { $acc[] = [ 'id' => $method->id, 'title' => $method->method_title, 'description' => $method->method_description, ]; } return $acc; }, [] ); $this->asset_data_registry->add( 'globalShippingMethods', $formatted_shipping_methods ); } if ( $is_block_editor && ! $this->asset_data_registry->exists( 'activeShippingZones' ) && class_exists( '\WC_Shipping_Zones' ) ) { $shipping_zones = \WC_Shipping_Zones::get_zones(); $formatted_shipping_zones = array_reduce( $shipping_zones, function( $acc, $zone ) { $acc[] = [ 'id' => $zone['id'], 'title' => $zone['zone_name'], 'description' => $zone['formatted_zone_location'], ]; return $acc; }, [] ); $formatted_shipping_zones[] = [ 'id' => 0, 'title' => __( 'International', 'woocommerce' ), 'description' => __( 'Locations outside all other zones', 'woocommerce' ), ]; $this->asset_data_registry->add( 'activeShippingZones', $formatted_shipping_zones ); } if ( $is_block_editor && ! $this->asset_data_registry->exists( 'globalPaymentMethods' ) ) { $payment_methods = WC()->payment_gateways->payment_gateways(); $formatted_payment_methods = array_reduce( $payment_methods, function( $acc, $method ) { if ( 'yes' === $method->enabled ) { $acc[] = [ 'id' => $method->id, 'title' => $method->method_title, 'description' => $method->method_description, ]; } return $acc; }, [] ); $this->asset_data_registry->add( 'globalPaymentMethods', $formatted_payment_methods ); } if ( ! is_admin() && ! WC()->is_rest_api_request() ) { $this->hydrate_from_api(); $this->hydrate_customer_payment_methods(); } do_action( 'woocommerce_blocks_checkout_enqueue_data' ); } /** * Are we currently on the admin block editor screen? */ protected function is_block_editor() { if ( ! is_admin() || ! function_exists( 'get_current_screen' ) ) { return false; } $screen = get_current_screen(); return $screen && $screen->is_block_editor(); } /** * Removes accents from an array of values, sorts by the values, then returns the original array values sorted. * * @param array $array Array of values to sort. * @return array Sorted array. */ protected function deep_sort_with_accents( $array ) { if ( ! is_array( $array ) || empty( $array ) ) { return $array; } if ( is_array( reset( $array ) ) ) { return array_map( [ $this, 'deep_sort_with_accents' ], $array ); } $array_without_accents = array_map( 'remove_accents', array_map( 'wc_strtolower', array_map( 'html_entity_decode', $array ) ) ); asort( $array_without_accents ); return array_replace( $array_without_accents, $array ); } /** * Get customer payment methods for use in checkout. */ protected function hydrate_customer_payment_methods() { if ( ! is_user_logged_in() || $this->asset_data_registry->exists( 'customerPaymentMethods' ) ) { return; } add_filter( 'woocommerce_payment_methods_list_item', [ $this, 'include_token_id_with_payment_methods' ], 10, 2 ); $this->asset_data_registry->add( 'customerPaymentMethods', wc_get_customer_saved_methods_list( get_current_user_id() ) ); remove_filter( 'woocommerce_payment_methods_list_item', [ $this, 'include_token_id_with_payment_methods' ], 10, 2 ); } /** * Hydrate the checkout block with data from the API. */ protected function hydrate_from_api() { // Print existing notices now, otherwise they are caught by the Cart // Controller and converted to exceptions. wc_print_notices(); add_filter( 'woocommerce_store_api_disable_nonce_check', '__return_true' ); $this->asset_data_registry->hydrate_api_request( '/wc/store/cart' ); $this->asset_data_registry->hydrate_api_request( '/wc/store/checkout' ); remove_filter( 'woocommerce_store_api_disable_nonce_check', '__return_true' ); } /** * Callback for woocommerce_payment_methods_list_item filter to add token id * to the generated list. * * @param array $list_item The current list item for the saved payment method. * @param \WC_Token $token The token for the current list item. * * @return array The list item with the token id added. */ public static function include_token_id_with_payment_methods( $list_item, $token ) { $list_item['tokenId'] = $token->get_id(); $brand = ! empty( $list_item['method']['brand'] ) ? strtolower( $list_item['method']['brand'] ) : ''; // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- need to match on translated value from core. if ( ! empty( $brand ) && esc_html__( 'Credit card', 'woocommerce' ) !== $brand ) { $list_item['method']['brand'] = wc_get_credit_card_type_label( $brand ); } return $list_item; } } woocommerce-blocks/src/BlockTypes/AllReviews.php 0000644 00000002461 15133551764 0016007 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * AllReviews class. */ class AllReviews extends AbstractBlock { /** * Block name. * * @var string */ protected $block_name = 'all-reviews'; /** * Get the frontend script handle for this block type. * * @see $this->register_block_type() * @param string $key Data to get, or default to everything. * @return array|string */ protected function get_block_type_script( $key = null ) { $script = [ 'handle' => 'wc-reviews-block-frontend', 'path' => $this->asset_api->get_block_asset_build_path( 'reviews-frontend' ), 'dependencies' => [], ]; return $key ? $script[ $key ] : $script; } /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); $this->asset_data_registry->add( 'reviewRatingsEnabled', wc_review_ratings_enabled(), true ); $this->asset_data_registry->add( 'showAvatars', '1' === get_option( 'show_avatars' ), true ); } } woocommerce-blocks/src/BlockTypes/AllProducts.php 0000644 00000004121 15133551764 0016161 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * AllProducts class. */ class AllProducts extends AbstractBlock { /** * Block name. * * @var string */ protected $block_name = 'all-products'; /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); $this->asset_data_registry->add( 'min_columns', wc_get_theme_support( 'product_blocks::min_columns', 1 ), true ); $this->asset_data_registry->add( 'max_columns', wc_get_theme_support( 'product_blocks::max_columns', 6 ), true ); $this->asset_data_registry->add( 'default_columns', wc_get_theme_support( 'product_blocks::default_columns', 3 ), true ); $this->asset_data_registry->add( 'min_rows', wc_get_theme_support( 'product_blocks::min_rows', 1 ), true ); $this->asset_data_registry->add( 'max_rows', wc_get_theme_support( 'product_blocks::max_rows', 6 ), true ); $this->asset_data_registry->add( 'default_rows', wc_get_theme_support( 'product_blocks::default_rows', 3 ), true ); } /** * Register script and style assets for the block type before it is registered. * * This registers the scripts; it does not enqueue them. */ protected function register_block_type_assets() { parent::register_block_type_assets(); $this->register_chunk_translations( [ 'atomic-block-components/price', 'atomic-block-components/image', 'atomic-block-components/title', 'atomic-block-components/rating', 'atomic-block-components/button', 'atomic-block-components/summary', 'atomic-block-components/sale-badge', 'atomic-block-components/sku', 'atomic-block-components/category-list', 'atomic-block-components/tag-list', 'atomic-block-components/stock-indicator', 'atomic-block-components/add-to-cart', ] ); } } woocommerce-blocks/src/BlockTypes/ProductTag.php 0000644 00000004244 15133551764 0016007 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * ProductTag class. */ class ProductTag extends AbstractProductGrid { /** * Block name. * * @var string */ protected $block_name = 'product-tag'; /** * Set args specific to this block. * * @param array $query_args Query args. */ protected function set_block_query_args( &$query_args ) { if ( ! empty( $this->attributes['tags'] ) ) { $query_args['tax_query'][] = array( 'taxonomy' => 'product_tag', 'terms' => array_map( 'absint', $this->attributes['tags'] ), 'field' => 'id', 'operator' => isset( $this->attributes['tagOperator'] ) && 'any' === $this->attributes['tagOperator'] ? 'IN' : 'AND', ); } } /** * Get block attributes. * * @return array */ protected function get_block_type_attributes() { return array( 'className' => $this->get_schema_string(), 'columns' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ), 'rows' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 3 ) ), 'contentVisibility' => $this->get_schema_content_visibility(), 'align' => $this->get_schema_align(), 'alignButtons' => $this->get_schema_boolean( false ), 'orderby' => $this->get_schema_orderby(), 'tags' => $this->get_schema_list_ids(), 'tagOperator' => array( 'type' => 'string', 'default' => 'any', ), 'isPreview' => $this->get_schema_boolean( false ), ); } /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); $tag_count = wp_count_terms( 'product_tag' ); $this->asset_data_registry->add( 'hasTags', $tag_count > 0, true ); $this->asset_data_registry->add( 'limitTags', $tag_count > 100, true ); } } woocommerce-blocks/src/BlockTypes/StockFilter.php 0000644 00000001616 15133551764 0016164 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * AttributeFilter class. */ class StockFilter extends AbstractBlock { /** * Block name. * * @var string */ protected $block_name = 'stock-filter'; /** * Extra data passed through from server to client for block. * * @param array $stock_statuses Any stock statuses that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $stock_statuses = [] ) { parent::enqueue_data( $stock_statuses ); $this->asset_data_registry->add( 'stockStatusOptions', wc_get_product_stock_status_options(), true ); $this->asset_data_registry->add( 'hideOutOfStockItems', 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ), true ); } } woocommerce-blocks/src/BlockTypes/HandpickedProducts.php 0000644 00000003471 15133551764 0017512 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * HandpickedProducts class. */ class HandpickedProducts extends AbstractProductGrid { /** * Block name. * * @var string */ protected $block_name = 'handpicked-products'; /** * Set args specific to this block * * @param array $query_args Query args. */ protected function set_block_query_args( &$query_args ) { $ids = array_map( 'absint', $this->attributes['products'] ); $query_args['post__in'] = $ids; $query_args['posts_per_page'] = count( $ids ); } /** * Set visibility query args. Handpicked products will show hidden products if chosen. * * @param array $query_args Query args. */ protected function set_visibility_query_args( &$query_args ) { if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) { $product_visibility_terms = wc_get_product_visibility_term_ids(); $query_args['tax_query'][] = array( 'taxonomy' => 'product_visibility', 'field' => 'term_taxonomy_id', 'terms' => array( $product_visibility_terms['outofstock'] ), 'operator' => 'NOT IN', ); } } /** * Get block attributes. * * @return array */ protected function get_block_type_attributes() { return array( 'align' => $this->get_schema_align(), 'alignButtons' => $this->get_schema_boolean( false ), 'className' => $this->get_schema_string(), 'columns' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ), 'editMode' => $this->get_schema_boolean( true ), 'orderby' => $this->get_schema_orderby(), 'products' => $this->get_schema_list_ids(), 'contentVisibility' => $this->get_schema_content_visibility(), 'isPreview' => $this->get_schema_boolean( false ), ); } } woocommerce-blocks/src/BlockTypes/ReviewsByProduct.php 0000644 00000002504 15133551764 0017210 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * ReviewsByProduct class. */ class ReviewsByProduct extends AbstractBlock { /** * Block name. * * @var string */ protected $block_name = 'reviews-by-product'; /** * Get the frontend script handle for this block type. * * @see $this->register_block_type() * @param string $key Data to get, or default to everything. * @return array|string */ protected function get_block_type_script( $key = null ) { $script = [ 'handle' => 'wc-reviews-block-frontend', 'path' => $this->asset_api->get_block_asset_build_path( 'reviews-frontend' ), 'dependencies' => [], ]; return $key ? $script[ $key ] : $script; } /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); $this->asset_data_registry->add( 'reviewRatingsEnabled', wc_review_ratings_enabled(), true ); $this->asset_data_registry->add( 'showAvatars', '1' === get_option( 'show_avatars' ), true ); } } woocommerce-blocks/src/BlockTypes/AbstractBlock.php 0000644 00000031620 15133551764 0016447 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; use WP_Block; use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry; use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi; use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry; use Automattic\WooCommerce\Blocks\RestApi; /** * AbstractBlock class. */ abstract class AbstractBlock { /** * Block namespace. * * @var string */ protected $namespace = 'woocommerce'; /** * Block name within this namespace. * * @var string */ protected $block_name = ''; /** * Tracks if assets have been enqueued. * * @var boolean */ protected $enqueued_assets = false; /** * Instance of the asset API. * * @var AssetApi */ protected $asset_api; /** * Instance of the asset data registry. * * @var AssetDataRegistry */ protected $asset_data_registry; /** * Instance of the integration registry. * * @var IntegrationRegistry */ protected $integration_registry; /** * Constructor. * * @param AssetApi $asset_api Instance of the asset API. * @param AssetDataRegistry $asset_data_registry Instance of the asset data registry. * @param IntegrationRegistry $integration_registry Instance of the integration registry. * @param string $block_name Optionally set block name during construct. */ public function __construct( AssetApi $asset_api, AssetDataRegistry $asset_data_registry, IntegrationRegistry $integration_registry, $block_name = '' ) { $this->asset_api = $asset_api; $this->asset_data_registry = $asset_data_registry; $this->integration_registry = $integration_registry; $this->block_name = $block_name ? $block_name : $this->block_name; $this->initialize(); } /** * The default render_callback for all blocks. This will ensure assets are enqueued just in time, then render * the block (if applicable). * * @param array|WP_Block $attributes Block attributes, or an instance of a WP_Block. Defaults to an empty array. * @param string $content Block content. Default empty string. * @return string Rendered block type output. */ public function render_callback( $attributes = [], $content = '' ) { $render_callback_attributes = $this->parse_render_callback_attributes( $attributes ); if ( ! is_admin() ) { $this->enqueue_assets( $render_callback_attributes ); } return $this->render( $render_callback_attributes, $content ); } /** * Enqueue assets used for rendering the block in editor context. * * This is needed if a block is not yet within the post content--`render` and `enqueue_assets` may not have ran. */ public function enqueue_editor_assets() { if ( $this->enqueued_assets ) { return; } $this->enqueue_data(); } /** * Initialize this block type. * * - Hook into WP lifecycle. * - Register the block with WordPress. */ protected function initialize() { if ( empty( $this->block_name ) ) { _doing_it_wrong( __METHOD__, esc_html( __( 'Block name is required.', 'woocommerce' ) ), '4.5.0' ); return false; } $this->integration_registry->initialize( $this->block_name . '_block' ); $this->register_block_type_assets(); $this->register_block_type(); add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] ); } /** * Register script and style assets for the block type before it is registered. * * This registers the scripts; it does not enqueue them. */ protected function register_block_type_assets() { if ( null !== $this->get_block_type_editor_script() ) { $data = $this->asset_api->get_script_data( $this->get_block_type_editor_script( 'path' ) ); $has_i18n = in_array( 'wp-i18n', $data['dependencies'], true ); $this->asset_api->register_script( $this->get_block_type_editor_script( 'handle' ), $this->get_block_type_editor_script( 'path' ), array_merge( $this->get_block_type_editor_script( 'dependencies' ), $this->integration_registry->get_all_registered_editor_script_handles() ), $has_i18n ); } if ( null !== $this->get_block_type_script() ) { $data = $this->asset_api->get_script_data( $this->get_block_type_script( 'path' ) ); $has_i18n = in_array( 'wp-i18n', $data['dependencies'], true ); $this->asset_api->register_script( $this->get_block_type_script( 'handle' ), $this->get_block_type_script( 'path' ), array_merge( $this->get_block_type_script( 'dependencies' ), $this->integration_registry->get_all_registered_script_handles() ), $has_i18n ); } } /** * Injects Chunk Translations into the page so translations work for lazy loaded components. * * The chunk names are defined when creating lazy loaded components using webpackChunkName. * * @param string[] $chunks Array of chunk names. */ protected function register_chunk_translations( $chunks ) { foreach ( $chunks as $chunk ) { $handle = 'wc-blocks-' . $chunk . '-chunk'; $this->asset_api->register_script( $handle, $this->asset_api->get_block_asset_build_path( $chunk ), [], true ); wp_add_inline_script( $this->get_block_type_script( 'handle' ), wp_scripts()->print_translations( $handle, false ), 'before' ); wp_deregister_script( $handle ); } } /** * Registers the block type with WordPress. */ protected function register_block_type() { register_block_type( $this->get_block_type(), array( 'render_callback' => $this->get_block_type_render_callback(), 'editor_script' => $this->get_block_type_editor_script( 'handle' ), 'editor_style' => $this->get_block_type_editor_style(), 'style' => $this->get_block_type_style(), 'attributes' => $this->get_block_type_attributes(), 'supports' => $this->get_block_type_supports(), ) ); } /** * Get the block type. * * @return string */ protected function get_block_type() { return $this->namespace . '/' . $this->block_name; } /** * Get the render callback for this block type. * * Dynamic blocks should return a callback, for example, `return [ $this, 'render' ];` * * @see $this->register_block_type() * @return callable|null; */ protected function get_block_type_render_callback() { return [ $this, 'render_callback' ]; } /** * Get the editor script data for this block type. * * @see $this->register_block_type() * @param string $key Data to get, or default to everything. * @return array|string */ protected function get_block_type_editor_script( $key = null ) { $script = [ 'handle' => 'wc-' . $this->block_name . '-block', 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ), 'dependencies' => [ 'wc-blocks' ], ]; return $key ? $script[ $key ] : $script; } /** * Get the editor style handle for this block type. * * @see $this->register_block_type() * @return string|null */ protected function get_block_type_editor_style() { return 'wc-blocks-editor-style'; } /** * Get the frontend script handle for this block type. * * @see $this->register_block_type() * @param string $key Data to get, or default to everything. * @return array|string */ protected function get_block_type_script( $key = null ) { $script = [ 'handle' => 'wc-' . $this->block_name . '-block-frontend', 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ), 'dependencies' => [], ]; return $key ? $script[ $key ] : $script; } /** * Get the frontend style handle for this block type. * * @see $this->register_block_type() * @return string|null */ protected function get_block_type_style() { return 'wc-blocks-style'; } /** * Get the supports array for this block type. * * @see $this->register_block_type() * @return string; */ protected function get_block_type_supports() { return []; } /** * Get block attributes. * * @return array; */ protected function get_block_type_attributes() { return []; } /** * Parses block attributes from the render_callback. * * @param array|WP_Block $attributes Block attributes, or an instance of a WP_Block. Defaults to an empty array. * @return array */ protected function parse_render_callback_attributes( $attributes ) { return is_a( $attributes, 'WP_Block' ) ? $attributes->attributes : $attributes; } /** * Render the block. Extended by children. * * @param array $attributes Block attributes. * @param string $content Block content. * @return string Rendered block type output. */ protected function render( $attributes, $content ) { return $content; } /** * Enqueue frontend assets for this block, just in time for rendering. * * @internal This prevents the block script being enqueued on all pages. It is only enqueued as needed. Note that * we intentionally do not pass 'script' to register_block_type. * * @param array $attributes Any attributes that currently are available from the block. */ protected function enqueue_assets( array $attributes ) { if ( $this->enqueued_assets ) { return; } $this->enqueue_data( $attributes ); $this->enqueue_scripts( $attributes ); $this->enqueued_assets = true; } /** * Injects block attributes into the block. * * @param string $content HTML content to inject into. * @param array $attributes Key value pairs of attributes. * @return string Rendered block with data attributes. */ protected function inject_html_data_attributes( $content, array $attributes ) { return preg_replace( '/<div /', '<div ' . $this->get_html_data_attributes( $attributes ) . ' ', $content, 1 ); } /** * Converts block attributes to HTML data attributes. * * @param array $attributes Key value pairs of attributes. * @return string Rendered HTML attributes. */ protected function get_html_data_attributes( array $attributes ) { $data = []; foreach ( $attributes as $key => $value ) { if ( is_bool( $value ) ) { $value = $value ? 'true' : 'false'; } if ( ! is_scalar( $value ) ) { $value = wp_json_encode( $value ); } $data[] = 'data-' . esc_attr( strtolower( preg_replace( '/(?<!\ )[A-Z]/', '-$0', $key ) ) ) . '="' . esc_attr( $value ) . '"'; } return implode( ' ', $data ); } /** * Data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { $registered_script_data = $this->integration_registry->get_all_registered_script_data(); foreach ( $registered_script_data as $asset_data_key => $asset_data_value ) { if ( ! $this->asset_data_registry->exists( $asset_data_key ) ) { $this->asset_data_registry->add( $asset_data_key, $asset_data_value ); } } if ( ! $this->asset_data_registry->exists( 'wcBlocksConfig' ) ) { $this->asset_data_registry->add( 'wcBlocksConfig', [ 'buildPhase' => Package::feature()->get_flag(), 'pluginUrl' => plugins_url( '/', dirname( __DIR__ ) ), 'productCount' => array_sum( (array) wp_count_posts( 'product' ) ), 'restApiRoutes' => [ '/wc/store' => array_keys( Package::container()->get( RestApi::class )->get_routes_from_namespace( 'wc/store' ) ), ], 'defaultAvatar' => get_avatar_url( 0, [ 'force_default' => true ] ), /* * translators: If your word count is based on single characters (e.g. East Asian characters), * enter 'characters_excluding_spaces' or 'characters_including_spaces'. Otherwise, enter 'words'. * Do not translate into your own language. */ 'wordCountType' => _x( 'words', 'Word count type. Do not translate!', 'woocommerce' ), ] ); } } /** * Register/enqueue scripts used for this block on the frontend, during render. * * @param array $attributes Any attributes that currently are available from the block. */ protected function enqueue_scripts( array $attributes = [] ) { if ( null !== $this->get_block_type_script() ) { wp_enqueue_script( $this->get_block_type_script( 'handle' ) ); } } /** * Script to append the correct sizing class to a block skeleton. * * @return string */ protected function get_skeleton_inline_script() { return "<script> var containers = document.querySelectorAll( 'div.wc-block-skeleton' ); if ( containers.length ) { Array.prototype.forEach.call( containers, function( el, i ) { var w = el.offsetWidth; var classname = ''; if ( w > 700 ) classname = 'is-large'; else if ( w > 520 ) classname = 'is-medium'; else if ( w > 400 ) classname = 'is-small'; else classname = 'is-mobile'; if ( ! el.classList.contains( classname ) ) { el.classList.add( classname ); } el.classList.remove( 'hidden' ); } ); } </script>"; } } woocommerce-blocks/src/BlockTypes/ProductBestSellers.php 0000644 00000000674 15133551764 0017526 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * ProductBestSellers class. */ class ProductBestSellers extends AbstractProductGrid { /** * Block name. * * @var string */ protected $block_name = 'product-best-sellers'; /** * Set args specific to this block * * @param array $query_args Query args. */ protected function set_block_query_args( &$query_args ) { $query_args['orderby'] = 'popularity'; } } woocommerce-blocks/src/BlockTypes/ProductOnSale.php 0000644 00000001371 15133551764 0016453 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * ProductOnSale class. */ class ProductOnSale extends AbstractProductGrid { /** * Block name. * * @var string */ protected $block_name = 'product-on-sale'; /** * Set args specific to this block * * @param array $query_args Query args. */ protected function set_block_query_args( &$query_args ) { $query_args['post__in'] = array_merge( array( 0 ), wc_get_product_ids_on_sale() ); } /** * Get block attributes. * * @return array */ protected function get_block_type_attributes() { return array_merge( parent::get_block_type_attributes(), array( 'className' => $this->get_schema_string(), 'orderby' => $this->get_schema_orderby(), ) ); } } woocommerce-blocks/src/BlockTypes/ActiveFilters.php 0000644 00000000342 15133551764 0016472 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * ActiveFilters class. */ class ActiveFilters extends AbstractBlock { /** * Block name. * * @var string */ protected $block_name = 'active-filters'; } woocommerce-blocks/src/BlockTypes/FeaturedProduct.php 0000644 00000013677 15133551764 0017045 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * FeaturedProduct class. */ class FeaturedProduct extends AbstractDynamicBlock { /** * Block name. * * @var string */ protected $block_name = 'featured-product'; /** * Default attribute values, should match what's set in JS `registerBlockType`. * * @var array */ protected $defaults = array( 'align' => 'none', 'contentAlign' => 'center', 'dimRatio' => 50, 'focalPoint' => false, 'height' => false, 'mediaId' => 0, 'mediaSrc' => '', 'showDesc' => true, 'showPrice' => true, ); /** * Render the Featured Product block. * * @param array $attributes Block attributes. * @param string $content Block content. * @return string Rendered block type output. */ protected function render( $attributes, $content ) { $id = absint( isset( $attributes['productId'] ) ? $attributes['productId'] : 0 ); $product = wc_get_product( $id ); if ( ! $product ) { return ''; } $attributes = wp_parse_args( $attributes, $this->defaults ); $attributes['height'] = $attributes['height'] ? $attributes['height'] : wc_get_theme_support( 'featured_block::default_height', 500 ); $title = sprintf( '<h2 class="wc-block-featured-product__title">%s</h2>', wp_kses_post( $product->get_title() ) ); if ( $product->is_type( 'variation' ) ) { $title .= sprintf( '<h3 class="wc-block-featured-product__variation">%s</h3>', wp_kses_post( wc_get_formatted_variation( $product, true, true, false ) ) ); } $desc_str = sprintf( '<div class="wc-block-featured-product__description">%s</div>', wc_format_content( wp_kses_post( $product->get_short_description() ? $product->get_short_description() : wc_trim_string( $product->get_description(), 400 ) ) ) ); $price_str = sprintf( '<div class="wc-block-featured-product__price">%s</div>', wp_kses_post( $product->get_price_html() ) ); $output = sprintf( '<div class="%1$s" style="%2$s">', esc_attr( $this->get_classes( $attributes ) ), esc_attr( $this->get_styles( $attributes, $product ) ) ); $output .= '<div class="wc-block-featured-product__wrapper">'; $output .= $title; if ( $attributes['showDesc'] ) { $output .= $desc_str; } if ( $attributes['showPrice'] ) { $output .= $price_str; } $output .= '<div class="wc-block-featured-product__link">' . $content . '</div>'; $output .= '</div>'; $output .= '</div>'; return $output; } /** * Get the styles for the wrapper element (background image, color). * * @param array $attributes Block attributes. Default empty array. * @param \WC_Product $product Product object. * @return string */ public function get_styles( $attributes, $product ) { $style = ''; $image_size = 'large'; if ( 'none' !== $attributes['align'] || $attributes['height'] > 800 ) { $image_size = 'full'; } if ( $attributes['mediaId'] ) { $image = wp_get_attachment_image_url( $attributes['mediaId'], $image_size ); } else { $image = $this->get_image( $product, $image_size ); } if ( ! empty( $image ) ) { $style .= sprintf( 'background-image:url(%s);', esc_url( $image ) ); } if ( isset( $attributes['customOverlayColor'] ) ) { $style .= sprintf( 'background-color:%s;', esc_attr( $attributes['customOverlayColor'] ) ); } if ( isset( $attributes['height'] ) ) { $style .= sprintf( 'min-height:%dpx;', intval( $attributes['height'] ) ); } if ( is_array( $attributes['focalPoint'] ) && 2 === count( $attributes['focalPoint'] ) ) { $style .= sprintf( 'background-position: %s%% %s%%', $attributes['focalPoint']['x'] * 100, $attributes['focalPoint']['y'] * 100 ); } return $style; } /** * Get class names for the block container. * * @param array $attributes Block attributes. Default empty array. * @return string */ public function get_classes( $attributes ) { $classes = array( 'wc-block-' . $this->block_name ); if ( isset( $attributes['align'] ) ) { $classes[] = "align{$attributes['align']}"; } if ( isset( $attributes['dimRatio'] ) && ( 0 !== $attributes['dimRatio'] ) ) { $classes[] = 'has-background-dim'; if ( 50 !== $attributes['dimRatio'] ) { $classes[] = 'has-background-dim-' . 10 * round( $attributes['dimRatio'] / 10 ); } } if ( isset( $attributes['contentAlign'] ) && 'center' !== $attributes['contentAlign'] ) { $classes[] = "has-{$attributes['contentAlign']}-content"; } if ( isset( $attributes['overlayColor'] ) ) { $classes[] = "has-{$attributes['overlayColor']}-background-color"; } if ( isset( $attributes['className'] ) ) { $classes[] = $attributes['className']; } return implode( ' ', $classes ); } /** * Returns the main product image URL. * * @param \WC_Product $product Product object. * @param string $size Image size, defaults to 'full'. * @return string */ public function get_image( $product, $size = 'full' ) { $image = ''; if ( $product->get_image_id() ) { $image = wp_get_attachment_image_url( $product->get_image_id(), $size ); } elseif ( $product->get_parent_id() ) { $parent_product = wc_get_product( $product->get_parent_id() ); if ( $parent_product ) { $image = wp_get_attachment_image_url( $parent_product->get_image_id(), $size ); } } return $image; } /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); $this->asset_data_registry->add( 'min_height', wc_get_theme_support( 'featured_block::min_height', 500 ), true ); $this->asset_data_registry->add( 'default_height', wc_get_theme_support( 'featured_block::default_height', 500 ), true ); } } woocommerce-blocks/src/BlockTypes/ReviewsByCategory.php 0000644 00000002507 15133551764 0017350 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * ReviewsByCategory class. */ class ReviewsByCategory extends AbstractBlock { /** * Block name. * * @var string */ protected $block_name = 'reviews-by-category'; /** * Get the frontend script handle for this block type. * * @see $this->register_block_type() * @param string $key Data to get, or default to everything. * @return array|string */ protected function get_block_type_script( $key = null ) { $script = [ 'handle' => 'wc-reviews-block-frontend', 'path' => $this->asset_api->get_block_asset_build_path( 'reviews-frontend' ), 'dependencies' => [], ]; return $key ? $script[ $key ] : $script; } /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); $this->asset_data_registry->add( 'reviewRatingsEnabled', wc_review_ratings_enabled(), true ); $this->asset_data_registry->add( 'showAvatars', '1' === get_option( 'show_avatars' ), true ); } } woocommerce-blocks/src/BlockTypes/ProductsByAttribute.php 0000644 00000003756 15133551764 0017724 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\BlockTypes; /** * ProductsByAttribute class. */ class ProductsByAttribute extends AbstractProductGrid { /** * Block name. * * @var string */ protected $block_name = 'products-by-attribute'; /** * Set args specific to this block * * @param array $query_args Query args. */ protected function set_block_query_args( &$query_args ) { if ( ! empty( $this->attributes['attributes'] ) ) { $taxonomy = sanitize_title( $this->attributes['attributes'][0]['attr_slug'] ); $terms = wp_list_pluck( $this->attributes['attributes'], 'id' ); $query_args['tax_query'][] = array( 'taxonomy' => $taxonomy, 'terms' => array_map( 'absint', $terms ), 'field' => 'term_id', 'operator' => 'all' === $this->attributes['attrOperator'] ? 'AND' : 'IN', ); } } /** * Get block attributes. * * @return array */ protected function get_block_type_attributes() { return array( 'align' => $this->get_schema_align(), 'alignButtons' => $this->get_schema_boolean( false ), 'attributes' => array( 'type' => 'array', 'items' => array( 'type' => 'object', 'properties' => array( 'id' => array( 'type' => 'number', ), 'attr_slug' => array( 'type' => 'string', ), ), ), 'default' => array(), ), 'attrOperator' => array( 'type' => 'string', 'default' => 'any', ), 'className' => $this->get_schema_string(), 'columns' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ), 'contentVisibility' => $this->get_schema_content_visibility(), 'editMode' => $this->get_schema_boolean( true ), 'orderby' => $this->get_schema_orderby(), 'rows' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 3 ) ), 'isPreview' => $this->get_schema_boolean( false ), ); } } woocommerce-blocks/src/Utils/BlocksWpQuery.php 0000644 00000004124 15133551764 0015523 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Utils; use WP_Query; /** * BlocksWpQuery query. * * Wrapper for WP Query with additonal helper methods. * Allows query args to be set and parsed without doing running it, so that a cache can be used. * * @deprecated 2.5.0 */ class BlocksWpQuery extends WP_Query { /** * Constructor. * * Sets up the WordPress query, if parameter is not empty. * * Unlike the constructor in WP_Query, this does not RUN the query. * * @param string|array $query URL query string or array of vars. */ public function __construct( $query = '' ) { if ( ! empty( $query ) ) { $this->init(); $this->query = wp_parse_args( $query ); $this->query_vars = $this->query; $this->parse_query_vars(); } } /** * Get cached posts, if a cache exists. * * A hash is generated using the array of query_vars. If doing custom queries via filters such as posts_where * (where the SQL query is manipulated directly) you can still ensure there is a unique hash by injecting custom * query vars via the parse_query filter. For example: * * add_filter( 'parse_query', function( $wp_query ) { * $wp_query->query_vars['my_custom_query_var'] = true; * } ); * * Doing so won't have any negative effect on the query itself, and it will cause the hash to change. * * @param string $transient_version Transient version to allow for invalidation. * @return WP_Post[]|int[] Array of post objects or post IDs. */ public function get_cached_posts( $transient_version = '' ) { $hash = md5( wp_json_encode( $this->query_vars ) ); $transient_name = 'wc_blocks_query_' . $hash; $transient_value = get_transient( $transient_name ); if ( isset( $transient_value, $transient_value['version'], $transient_value['value'] ) && $transient_value['version'] === $transient_version ) { return $transient_value['value']; } $results = $this->get_posts(); set_transient( $transient_name, array( 'version' => $transient_version, 'value' => $results, ), DAY_IN_SECONDS * 30 ); return $results; } } woocommerce-blocks/src/Utils/ArrayUtils.php 0000644 00000002024 15133551764 0015045 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Utils; /** * ArrayUtils class used for custom functions to operate on arrays */ class ArrayUtils { /** * Join a string with a natural language conjunction at the end. * * @param array $array The array to join together with the natural language conjunction. * @param bool $enclose_items_with_quotes Whether each item in the array should be enclosed within quotation marks. * * @return string a string containing a list of items and a natural language conjuction. */ public static function natural_language_join( $array, $enclose_items_with_quotes = false ) { if ( true === $enclose_items_with_quotes ) { $array = array_map( function( $item ) { return '"' . $item . '"'; }, $array ); } $last = array_pop( $array ); if ( $array ) { return sprintf( /* translators: 1: The first n-1 items of a list 2: the last item in the list. */ __( '%1$s and %2$s', 'woocommerce' ), implode( ', ', $array ), $last ); } return $last; } } woocommerce-blocks/src/Package.php 0000644 00000006054 15133551764 0013210 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks; use Automattic\WooCommerce\Blocks\Domain\Package as NewPackage; use Automattic\WooCommerce\Blocks\Domain\Bootstrap; use Automattic\WooCommerce\Blocks\Registry\Container; use Automattic\WooCommerce\Blocks\Domain\Services\FeatureGating; /** * Main package class. * * Returns information about the package and handles init. * * In the context of this plugin, it handles init and is called from the main * plugin file (woocommerce-gutenberg-products-block.php). * * In the context of WooCommere core, it handles init and is called from * WooCommerce's package loader. The main plugin file is _not_ loaded. * * @since 2.5.0 */ class Package { /** * For back compat this is provided. Ideally, you should register your * class with Automattic\Woocommerce\Blocks\Container and make Package a * dependency. * * @since 2.5.0 * @return Package The Package instance class */ protected static function get_package() { return self::container()->get( NewPackage::class ); } /** * Init the package - load the blocks library and define constants. * * @since 2.5.0 Handled by new NewPackage. */ public static function init() { self::container()->get( Bootstrap::class ); } /** * Return the version of the package. * * @return string */ public static function get_version() { return self::get_package()->get_version(); } /** * Return the path to the package. * * @return string */ public static function get_path() { return self::get_package()->get_path(); } /** * Returns an instance of the the FeatureGating class. * * @return FeatureGating */ public static function feature() { return self::get_package()->feature(); } /** * Checks if we're executing the code in an experimental build mode. * * @return boolean */ public static function is_experimental_build() { return self::get_package()->is_experimental_build(); } /** * Checks if we're executing the code in an feature plugin or experimental build mode. * * @return boolean */ public static function is_feature_plugin_build() { return self::get_package()->is_feature_plugin_build(); } /** * Loads the dependency injection container for woocommerce blocks. * * @param boolean $reset Used to reset the container to a fresh instance. * Note: this means all dependencies will be * reconstructed. */ public static function container( $reset = false ) { static $container; if ( ! $container instanceof Container || $reset ) { $container = new Container(); // register Package. $container->register( NewPackage::class, function ( $container ) { // leave for automated version bumping. $version = '6.1.0'; return new NewPackage( $version, dirname( __DIR__ ), new FeatureGating() ); } ); // register Bootstrap. $container->register( Bootstrap::class, function ( $container ) { return new Bootstrap( $container ); } ); } return $container; } } woocommerce-blocks/src/Domain/Bootstrap.php 0000644 00000024215 15133551764 0015040 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Domain; use Automattic\WooCommerce\Blocks\AssetsController as AssetsController; use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi; use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry; use Automattic\WooCommerce\Blocks\BlockTypesController; use Automattic\WooCommerce\Blocks\InboxNotifications; use Automattic\WooCommerce\Blocks\Installer; use Automattic\WooCommerce\Blocks\Registry\Container; use Automattic\WooCommerce\Blocks\RestApi; use Automattic\WooCommerce\Blocks\Payments\Api as PaymentsApi; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; use Automattic\WooCommerce\Blocks\Payments\Integrations\Stripe; use Automattic\WooCommerce\Blocks\Payments\Integrations\Cheque; use Automattic\WooCommerce\Blocks\Payments\Integrations\PayPal; use Automattic\WooCommerce\Blocks\Payments\Integrations\BankTransfer; use Automattic\WooCommerce\Blocks\Payments\Integrations\CashOnDelivery; use Automattic\WooCommerce\Blocks\Domain\Services\FeatureGating; use Automattic\WooCommerce\Blocks\Domain\Services\DraftOrders; use Automattic\WooCommerce\Blocks\Domain\Services\CreateAccount; use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi; use Automattic\WooCommerce\Blocks\Domain\Services\Email\CustomerNewAccount; use Automattic\WooCommerce\Blocks\StoreApi\Formatters; use Automattic\WooCommerce\Blocks\StoreApi\Formatters\MoneyFormatter; use Automattic\WooCommerce\Blocks\StoreApi\Formatters\HtmlFormatter; use Automattic\WooCommerce\Blocks\StoreApi\Formatters\CurrencyFormatter; use Automattic\WooCommerce\Blocks\StoreApi\RoutesController; use Automattic\WooCommerce\Blocks\StoreApi\SchemaController; use Automattic\WooCommerce\Blocks\Domain\Services\GoogleAnalytics; /** * Takes care of bootstrapping the plugin. * * @since 2.5.0 */ class Bootstrap { /** * Holds the Dependency Injection Container * * @var Container */ private $container; /** * Holds the Package instance * * @var Package */ private $package; /** * Constructor * * @param Container $container The Dependency Injection Container. */ public function __construct( Container $container ) { $this->container = $container; $this->package = $container->get( Package::class ); if ( $this->has_core_dependencies() ) { $this->init(); /** * Usable as a safe event hook for when the plugin has been loaded. */ do_action( 'woocommerce_blocks_loaded' ); } } /** * Init the package - load the blocks library and define constants. */ protected function init() { $this->register_dependencies(); $this->register_payment_methods(); add_action( 'admin_init', function() { InboxNotifications::create_surface_cart_checkout_blocks_notification(); }, 10, 0 ); $is_rest = wc()->is_rest_api_request(); // Load assets in admin and on the frontend. if ( ! $is_rest ) { $this->add_build_notice(); $this->container->get( AssetDataRegistry::class ); $this->container->get( Installer::class ); $this->container->get( AssetsController::class ); } $this->container->get( DraftOrders::class )->init(); $this->container->get( CreateAccount::class )->init(); $this->container->get( ExtendRestApi::class ); $this->container->get( RestApi::class ); $this->container->get( GoogleAnalytics::class ); $this->container->get( BlockTypesController::class ); if ( $this->package->feature()->is_feature_plugin_build() ) { $this->container->get( PaymentsApi::class ); } } /** * Check core dependencies exist. * * @return boolean */ protected function has_core_dependencies() { $has_needed_dependencies = class_exists( 'WooCommerce', false ); if ( $has_needed_dependencies ) { $plugin_data = \get_file_data( $this->package->get_path( 'woocommerce-gutenberg-products-block.php' ), [ 'RequiredWCVersion' => 'WC requires at least', ] ); if ( isset( $plugin_data['RequiredWCVersion'] ) && version_compare( \WC()->version, $plugin_data['RequiredWCVersion'], '<' ) ) { $has_needed_dependencies = false; add_action( 'admin_notices', function() { if ( should_display_compatibility_notices() ) { ?> <div class="notice notice-error"> <p><?php esc_html_e( 'The WooCommerce Blocks feature plugin requires a more recent version of WooCommerce and has been paused. Please update WooCommerce to the latest version to continue enjoying WooCommerce Blocks.', 'woocommerce' ); ?></p> </div> <?php } } ); } } return $has_needed_dependencies; } /** * See if files have been built or not. * * @return bool */ protected function is_built() { return file_exists( $this->package->get_path( 'build/featured-product.js' ) ); } /** * Add a notice stating that the build has not been done yet. */ protected function add_build_notice() { if ( $this->is_built() ) { return; } add_action( 'admin_notices', function() { echo '<div class="error"><p>'; printf( /* translators: %1$s is the install command, %2$s is the build command, %3$s is the watch command. */ esc_html__( 'WooCommerce Blocks development mode requires files to be built. From the plugin directory, run %1$s to install dependencies, %2$s to build the files or %3$s to build the files and watch for changes.', 'woocommerce' ), '<code>npm install</code>', '<code>npm run build</code>', '<code>npm start</code>' ); echo '</p></div>'; } ); } /** * Register core dependencies with the container. */ protected function register_dependencies() { $this->container->register( FeatureGating::class, function ( Container $container ) { return new FeatureGating(); } ); $this->container->register( AssetApi::class, function ( Container $container ) { return new AssetApi( $container->get( Package::class ) ); } ); $this->container->register( AssetDataRegistry::class, function( Container $container ) { return new AssetDataRegistry( $container->get( AssetApi::class ) ); } ); $this->container->register( AssetsController::class, function( Container $container ) { return new AssetsController( $container->get( AssetApi::class ) ); } ); $this->container->register( PaymentMethodRegistry::class, function( Container $container ) { return new PaymentMethodRegistry(); } ); $this->container->register( RestApi::class, function ( Container $container ) { return new RestApi( $container->get( RoutesController::class ) ); } ); $this->container->register( Installer::class, function ( Container $container ) { return new Installer(); } ); $this->container->register( BlockTypesController::class, function ( Container $container ) { $asset_api = $container->get( AssetApi::class ); $asset_data_registry = $container->get( AssetDataRegistry::class ); return new BlockTypesController( $asset_api, $asset_data_registry ); } ); $this->container->register( DraftOrders::class, function( Container $container ) { return new DraftOrders( $container->get( Package::class ) ); } ); $this->container->register( CreateAccount::class, function( Container $container ) { return new CreateAccount( $container->get( Package::class ) ); } ); $this->container->register( Formatters::class, function( Container $container ) { $formatters = new Formatters(); $formatters->register( 'money', MoneyFormatter::class ); $formatters->register( 'html', HtmlFormatter::class ); $formatters->register( 'currency', CurrencyFormatter::class ); return $formatters; } ); $this->container->register( SchemaController::class, function( Container $container ) { return new SchemaController( $container->get( ExtendRestApi::class ) ); } ); $this->container->register( RoutesController::class, function( Container $container ) { return new RoutesController( $container->get( SchemaController::class ) ); } ); $this->container->register( ExtendRestApi::class, function( Container $container ) { return new ExtendRestApi( $container->get( Package::class ), $container->get( Formatters::class ) ); } ); $this->container->register( GoogleAnalytics::class, function( Container $container ) { // Require Google Analytics Integration to be activated. if ( ! class_exists( 'WC_Google_Analytics_Integration', false ) ) { return; } $asset_api = $container->get( AssetApi::class ); return new GoogleAnalytics( $asset_api ); } ); if ( $this->package->feature()->is_feature_plugin_build() ) { $this->container->register( PaymentsApi::class, function ( Container $container ) { $payment_method_registry = $container->get( PaymentMethodRegistry::class ); $asset_data_registry = $container->get( AssetDataRegistry::class ); return new PaymentsApi( $payment_method_registry, $asset_data_registry ); } ); } } /** * Register payment method integrations with the container. * * @internal Stripe is a temporary method that is used for setting up payment method integrations with Cart and * Checkout blocks. This logic should get moved to the payment gateway extensions. */ protected function register_payment_methods() { $this->container->register( Stripe::class, function( Container $container ) { $asset_api = $container->get( AssetApi::class ); return new Stripe( $asset_api ); } ); $this->container->register( Cheque::class, function( Container $container ) { $asset_api = $container->get( AssetApi::class ); return new Cheque( $asset_api ); } ); $this->container->register( PayPal::class, function( Container $container ) { $asset_api = $container->get( AssetApi::class ); return new PayPal( $asset_api ); } ); $this->container->register( BankTransfer::class, function( Container $container ) { $asset_api = $container->get( AssetApi::class ); return new BankTransfer( $asset_api ); } ); $this->container->register( CashOnDelivery::class, function( Container $container ) { $asset_api = $container->get( AssetApi::class ); return new CashOnDelivery( $asset_api ); } ); } } woocommerce-blocks/src/Domain/Package.php 0000644 00000004746 15133551764 0014425 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Domain; use Automattic\WooCommerce\Blocks\Package as NewPackage; use Automattic\WooCommerce\Blocks\Domain\Services\FeatureGating; /** * Main package class. * * Returns information about the package and handles init. * * @since 2.5.0 */ class Package { /** * Holds the current version of the blocks plugin. * * @var string */ private $version; /** * Holds the main path to the blocks plugin directory. * * @var string */ private $path; /** * Holds the feature gating class instance. * * @var FeatureGating */ private $feature_gating; /** * Constructor * * @param string $version Version of the plugin. * @param string $plugin_path Path to the main plugin file. * @param FeatureGating $feature_gating Feature gating class instance. */ public function __construct( $version, $plugin_path, FeatureGating $feature_gating ) { $this->version = $version; $this->path = $plugin_path; $this->feature_gating = $feature_gating; } /** * Returns the version of the plugin. * * @return string */ public function get_version() { return $this->version; } /** * Returns the path to the plugin directory. * * @param string $relative_path If provided, the relative path will be * appended to the plugin path. * * @return string */ public function get_path( $relative_path = '' ) { return trailingslashit( $this->path ) . $relative_path; } /** * Returns the url to the blocks plugin directory. * * @param string $relative_url If provided, the relative url will be * appended to the plugin url. * * @return string */ public function get_url( $relative_url = '' ) { // Append index.php so WP does not return the parent directory. return plugin_dir_url( $this->path . '/index.php' ) . $relative_url; } /** * Returns an instance of the the FeatureGating class. * * @return FeatureGating */ public function feature() { return $this->feature_gating; } /** * Checks if we're executing the code in an experimental build mode. * * @return boolean */ public function is_experimental_build() { return $this->feature()->is_experimental_build(); } /** * Checks if we're executing the code in an feature plugin or experimental build mode. * * @return boolean */ public function is_feature_plugin_build() { return $this->feature()->is_feature_plugin_build(); } } woocommerce-blocks/src/Domain/Services/CreateAccount.php 0000644 00000020776 15133551764 0017376 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Domain\Services; use \WP_REST_Request; use \WC_Order; use Automattic\WooCommerce\Blocks\Domain\Package; use Automattic\WooCommerce\Blocks\Domain\Services\Email\CustomerNewAccount; /** * Service class implementing new create account behaviour for order processing. */ class CreateAccount { /** * Reference to the Package instance * * @var Package */ private $package; /** * Constructor. * * @param Package $package An instance of (Woo Blocks) Package. */ public function __construct( Package $package ) { $this->package = $package; } /** * Single method for feature gating logic. Used to gate all non-private methods. * * @return True if Checkout sign-up feature should be made available. */ private function is_feature_enabled() { // Checkout signup is feature gated to WooCommerce 4.7 and newer; // uses updated my-account/lost-password screen from 4.7+ for // setting initial password. // This service is feature gated to plugin only, to match the // availability of the Checkout block (feature plugin only). return $this->package->feature()->is_feature_plugin_build() && defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '4.7', '>=' ); } /** * Init - register handlers for WooCommerce core email hooks. */ public function init() { if ( ! self::is_feature_enabled() ) { return; } // Override core email handlers to add our new improved "new account" email. add_action( 'woocommerce_email', function ( $wc_emails_instance ) { // Remove core "new account" handler; we are going to replace it. remove_action( 'woocommerce_created_customer_notification', array( $wc_emails_instance, 'customer_new_account' ), 10, 3 ); // Add custom "new account" handler. add_action( 'woocommerce_created_customer_notification', function( $customer_id, $new_customer_data = array(), $password_generated = false ) use ( $wc_emails_instance ) { // If this is a block-based signup, send a new email // with password reset link (no password in email). if ( isset( $new_customer_data['is_checkout_block_customer_signup'] ) ) { $this->customer_new_account( $customer_id, $new_customer_data ); return; } // Otherwise, trigger the existing legacy email (with new password inline). $wc_emails_instance->customer_new_account( $customer_id, $new_customer_data, $password_generated ); }, 10, 3 ); } ); } /** * Trigger new account email. * This is intended as a replacement to WC_Emails::customer_new_account(), * with a set password link instead of emailing the new password in email * content. * * @param int $customer_id The ID of the new customer account. * @param array $new_customer_data Assoc array of data for the new account. */ public function customer_new_account( $customer_id = 0, array $new_customer_data = array() ) { if ( ! self::is_feature_enabled() ) { return; } if ( ! $customer_id ) { return; } $new_account_email = new CustomerNewAccount( $this->package ); $new_account_email->trigger( $customer_id, $new_customer_data ); } /** * Create a user account for specified request (if necessary). * If a new account is created: * - The user is logged in. * * @param \WP_REST_Request $request The current request object being handled. * * @throws Exception On error. * @return int The new user id, or 0 if no user was created. */ public function from_order_request( \WP_REST_Request $request ) { if ( ! self::is_feature_enabled() || ! $this->should_create_customer_account( $request ) ) { return 0; } $customer_id = $this->create_customer_account( $request['billing_address']['email'], $request['billing_address']['first_name'], $request['billing_address']['last_name'] ); // Log the customer in and associate with the order. wc_set_customer_auth_cookie( $customer_id ); return $customer_id; } /** * Check request options and store (shop) config to determine if a user account * should be created as part of order processing. * * @param \WP_REST_Request $request The current request object being handled. * * @return boolean True if a new user account should be created. */ protected function should_create_customer_account( \WP_REST_Request $request ) { if ( is_user_logged_in() ) { // User is already logged in - no need to create an account. return false; } // From here we know that the shopper is not logged in. // check for whether account creation is enabled at the global level. $checkout = WC()->checkout(); if ( ! $checkout instanceof \WC_Checkout ) { // If checkout class is not available, we have major problems, don't create account. return false; } if ( false === filter_var( $checkout->is_registration_enabled(), FILTER_VALIDATE_BOOLEAN ) ) { // Registration is not enabled for the store, so return false. return false; } if ( true === filter_var( $checkout->is_registration_required(), FILTER_VALIDATE_BOOLEAN ) ) { // Store requires an account for all checkouts (purchases). // Create an account independent of shopper option in $request. // Note - checkbox is not displayed to shopper in this case. return true; } // From here we know that the store allows guest checkout; // shopper can choose whether they sign up (`should_create_account`). if ( true === filter_var( $request['should_create_account'], FILTER_VALIDATE_BOOLEAN ) ) { // User has requested an account as part of checkout processing. return true; } return false; } /** * Convert an account creation error to an exception. * * @param \WP_Error $error An error object. * * @return Exception. */ private function map_create_account_error( \WP_Error $error ) { switch ( $error->get_error_code() ) { // WordPress core error codes. case 'empty_username': case 'invalid_username': case 'empty_email': case 'invalid_email': case 'email_exists': case 'registerfail': return new \Exception( 'woocommerce_rest_checkout_create_account_failure' ); } return new \Exception( 'woocommerce_rest_checkout_create_account_failure' ); } /** * Create a new account for a customer (using a new blocks-specific PHP API). * * The account is created with a generated username. The customer is sent * an email notifying them about the account and containing a link to set * their (initial) password. * * Intended as a replacement for wc_create_new_customer in WC core. * * @throws \Exception If an error is encountered when creating the user account. * * @param string $user_email The email address to use for the new account. * @param string $first_name The first name to use for the new account. * @param string $last_name The last name to use for the new account. * * @return int User id if successful */ private function create_customer_account( $user_email, $first_name, $last_name ) { if ( empty( $user_email ) || ! is_email( $user_email ) ) { throw new \Exception( 'registration-error-invalid-email' ); } if ( email_exists( $user_email ) ) { throw new \Exception( 'registration-error-email-exists' ); } $username = wc_create_new_customer_username( $user_email ); // Handle password creation. $password = wp_generate_password(); $password_generated = true; // Use WP_Error to handle registration errors. $errors = new \WP_Error(); do_action( 'woocommerce_register_post', $username, $user_email, $errors ); $errors = apply_filters( 'woocommerce_registration_errors', $errors, $username, $user_email ); if ( $errors->get_error_code() ) { throw new \Exception( $errors->get_error_code() ); } $new_customer_data = apply_filters( 'woocommerce_new_customer_data', array( 'is_checkout_block_customer_signup' => true, 'user_login' => $username, 'user_pass' => $password, 'user_email' => $user_email, 'first_name' => $first_name, 'last_name' => $last_name, 'role' => 'customer', ) ); $customer_id = wp_insert_user( $new_customer_data ); if ( is_wp_error( $customer_id ) ) { throw $this->map_create_account_error( $customer_id ); } // Set account flag to remind customer to update generated password. update_user_option( $customer_id, 'default_password_nag', true, true ); do_action( 'woocommerce_created_customer', $customer_id, $new_customer_data, $password_generated ); return $customer_id; } } woocommerce-blocks/src/Domain/Services/DraftOrders.php 0000644 00000017724 15133551764 0017074 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Domain\Services; use Automattic\WooCommerce\Blocks\Domain\Package; use Exception; use WC_Order; /** * Service class for adding DraftOrder functionality to WooCommerce core. * * Sets up all logic related to the Checkout Draft Orders service * * @internal */ class DraftOrders { const DB_STATUS = 'wc-checkout-draft'; const STATUS = 'checkout-draft'; /** * Holds the Package instance * * @var Package */ private $package; /** * Constructor * * @param Package $package An instance of the package class. */ public function __construct( Package $package ) { $this->package = $package; } /** * Set all hooks related to adding Checkout Draft order functionality to Woo Core. */ public function init() { if ( $this->package->feature()->is_feature_plugin_build() ) { add_filter( 'wc_order_statuses', [ $this, 'register_draft_order_status' ] ); add_filter( 'woocommerce_register_shop_order_post_statuses', [ $this, 'register_draft_order_post_status' ] ); add_filter( 'woocommerce_analytics_excluded_order_statuses', [ $this, 'append_draft_order_post_status' ] ); add_filter( 'woocommerce_valid_order_statuses_for_payment', [ $this, 'append_draft_order_post_status' ] ); add_filter( 'woocommerce_valid_order_statuses_for_payment_complete', [ $this, 'append_draft_order_post_status' ] ); // Hook into the query to retrieve My Account orders so draft status is excluded. add_action( 'woocommerce_my_account_my_orders_query', [ $this, 'delete_draft_order_post_status_from_args' ] ); add_action( 'woocommerce_cleanup_draft_orders', [ $this, 'delete_expired_draft_orders' ] ); add_action( 'admin_init', [ $this, 'install' ] ); } else { // Maybe remove existing cronjob if present because it shouldn't be needed in the environment. add_action( 'admin_init', [ $this, 'uninstall' ] ); } } /** * Installation related logic for Draft order functionality. * * @internal */ public function install() { $this->maybe_create_cronjobs(); } /** * Remove cronjobs if they exist (but only from admin). * * @internal */ public function uninstall() { $this->maybe_remove_cronjobs(); } /** * Maybe create cron events. */ protected function maybe_create_cronjobs() { if ( function_exists( 'as_next_scheduled_action' ) && false === as_next_scheduled_action( 'woocommerce_cleanup_draft_orders' ) ) { as_schedule_recurring_action( strtotime( 'midnight tonight' ), DAY_IN_SECONDS, 'woocommerce_cleanup_draft_orders' ); } } /** * Unschedule cron jobs that are present. */ protected function maybe_remove_cronjobs() { if ( function_exists( 'as_next_scheduled_action' ) && as_next_scheduled_action( 'woocommerce_cleanup_draft_orders' ) ) { as_unschedule_all_actions( 'woocommerce_cleanup_draft_orders' ); } } /** * Register custom order status for orders created via the API during checkout. * * Draft order status is used before payment is attempted, during checkout, when a cart is converted to an order. * * @param array $statuses Array of statuses. * @internal * @return array */ public function register_draft_order_status( array $statuses ) { $statuses[ self::DB_STATUS ] = _x( 'Draft', 'Order status', 'woocommerce' ); return $statuses; } /** * Register custom order post status for orders created via the API during checkout. * * @param array $statuses Array of statuses. * @internal * @return array */ public function register_draft_order_post_status( array $statuses ) { $statuses[ self::DB_STATUS ] = $this->get_post_status_properties(); return $statuses; } /** * Returns the properties of this post status for registration. * * @return array */ private function get_post_status_properties() { return [ 'label' => _x( 'Draft', 'Order status', 'woocommerce' ), 'public' => false, 'exclude_from_search' => false, 'show_in_admin_all_list' => false, 'show_in_admin_status_list' => true, /* translators: %s: number of orders */ 'label_count' => _n_noop( 'Drafts <span class="count">(%s)</span>', 'Drafts <span class="count">(%s)</span>', 'woocommerce' ), ]; } /** * Remove draft status from the 'status' argument of an $args array. * * @param array $args Array of arguments containing statuses in the status key. * @internal * @return array */ public function delete_draft_order_post_status_from_args( $args ) { if ( ! array_key_exists( 'status', $args ) ) { $statuses = []; foreach ( wc_get_order_statuses() as $key => $label ) { if ( self::DB_STATUS !== $key ) { $statuses[] = str_replace( 'wc-', '', $key ); } } $args['status'] = $statuses; } elseif ( self::DB_STATUS === $args['status'] ) { $args['status'] = ''; } elseif ( is_array( $args['status'] ) ) { $args['status'] = array_diff_key( $args['status'], array( self::STATUS => null ) ); } return $args; } /** * Append draft status to a list of statuses. * * @param array $statuses Array of statuses. * @internal * @return array */ public function append_draft_order_post_status( $statuses ) { $statuses[] = self::STATUS; return $statuses; } /** * Delete draft orders older than a day in batches of 20. * * Ran on a daily cron schedule. * * @internal */ public function delete_expired_draft_orders() { $count = 0; $batch_size = 20; $this->ensure_draft_status_registered(); $orders = wc_get_orders( [ 'date_modified' => '<=' . strtotime( '-1 DAY' ), 'limit' => $batch_size, 'status' => self::DB_STATUS, 'type' => 'shop_order', ] ); // do we bail because the query results are unexpected? try { $this->assert_order_results( $orders, $batch_size ); if ( $orders ) { foreach ( $orders as $order ) { $order->delete( true ); $count ++; } } if ( $batch_size === $count && function_exists( 'as_enqueue_async_action' ) ) { as_enqueue_async_action( 'woocommerce_cleanup_draft_orders' ); } } catch ( Exception $error ) { wc_caught_exception( $error, __METHOD__ ); } } /** * Since it's possible for third party code to clobber the `$wp_post_statuses` global, * we need to do a final check here to make sure the draft post status is * registered with the global so that it is not removed by WP_Query status * validation checks. */ private function ensure_draft_status_registered() { $is_registered = get_post_stati( [ 'name' => self::DB_STATUS ] ); if ( empty( $is_registered ) ) { register_post_status( self::DB_STATUS, $this->get_post_status_properties() ); } } /** * Asserts whether incoming order results are expected given the query * this service class executes. * * @param WC_Order[] $order_results The order results being asserted. * @param int $expected_batch_size The expected batch size for the results. * @throws Exception If any assertions fail, an exception is thrown. */ private function assert_order_results( $order_results, $expected_batch_size ) { // if not an array, then just return because it won't get handled // anyways. if ( ! is_array( $order_results ) ) { return; } $suffix = ' This is an indicator that something is filtering WooCommerce or WordPress queries and modifying the query parameters.'; // if count is greater than our expected batch size, then that's a problem. if ( count( $order_results ) > 20 ) { throw new Exception( 'There are an unexpected number of results returned from the query.' . $suffix ); } // if any of the returned orders are not draft (or not a WC_Order), then that's a problem. foreach ( $order_results as $order ) { if ( ! ( $order instanceof WC_Order ) ) { throw new Exception( 'The returned results contain a value that is not a WC_Order.' . $suffix ); } if ( ! $order->has_status( self::STATUS ) ) { throw new Exception( 'The results contain an order that is not a `wc-checkout-draft` status in the results.' . $suffix ); } } } } woocommerce-blocks/src/Domain/Services/GoogleAnalytics.php 0000644 00000005563 15133551764 0017737 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Domain\Services; use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi; /** * Service class to integrate Blocks with the Google Analytics extension, */ class GoogleAnalytics { /** * Instance of the asset API. * * @var AssetApi */ protected $asset_api; /** * Constructor. * * @param AssetApi $asset_api Instance of the asset API. */ public function __construct( AssetApi $asset_api ) { $this->asset_api = $asset_api; $this->init(); } /** * Hook into WP. */ protected function init() { add_action( 'init', array( $this, 'register_assets' ) ); add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); add_filter( 'script_loader_tag', array( $this, 'async_script_loader_tags' ), 10, 3 ); } /** * Register scripts. */ public function register_assets() { $this->asset_api->register_script( 'wc-blocks-google-analytics', 'build/wc-blocks-google-analytics.js', [ 'google-tag-manager' ] ); } /** * Enqueue the Google Tag Manager script if prerequisites are met. */ public function enqueue_scripts() { $settings = $this->get_google_analytics_settings(); // Require tracking to be enabled with a valid GA ID. if ( ! stristr( $settings['ga_id'], 'G-' ) || apply_filters( 'woocommerce_ga_disable_tracking', ! wc_string_to_bool( $settings['ga_event_tracking_enabled'] ) ) ) { return; } if ( ! wp_script_is( 'google-tag-manager', 'registered' ) ) { // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion wp_register_script( 'google-tag-manager', 'https://www.googletagmanager.com/gtag/js?id=' . $settings['ga_id'], [], null, false ); wp_add_inline_script( 'google-tag-manager', " window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', '" . esc_js( $settings['ga_id'] ) . "', { 'send_page_view': false });" ); } wp_enqueue_script( 'wc-blocks-google-analytics' ); } /** * Get settings from the GA integration extension. * * @return array */ private function get_google_analytics_settings() { return wp_parse_args( get_option( 'woocommerce_google_analytics_settings' ), [ 'ga_id' => '', 'ga_event_tracking_enabled' => 'no', ] ); } /** * Add async to script tags with defined handles. * * @param string $tag HTML for the script tag. * @param string $handle Handle of script. * @param string $src Src of script. * @return string */ public function async_script_loader_tags( $tag, $handle, $src ) { if ( ! in_array( $handle, array( 'google-tag-manager' ), true ) ) { return $tag; } // If script was output manually in wp_head, abort. if ( did_action( 'woocommerce_gtag_snippet' ) ) { return ''; } return str_replace( '<script src', '<script async src', $tag ); } } woocommerce-blocks/src/Domain/Services/Email/CustomerNewAccount.php 0000644 00000011133 15133551764 0021460 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Domain\Services\Email; use \WP_User; use \WC_Email; use Automattic\WooCommerce\Blocks\Domain\Package; /** * Customer New Account. * * An email sent to the customer when they create an account. * This is intended as a replacement to WC_Email_Customer_New_Account(), * with a set password link instead of emailing the new password in email * content. * * @extends WC_Email */ class CustomerNewAccount extends \WC_Email { /** * User login name. * * @var string */ public $user_login; /** * User email. * * @var string */ public $user_email; /** * Magic link to set initial password. * * @var string */ public $set_password_url; /** * Override (force) default template path * * @var string */ public $default_template_path; /** * Constructor. * * @param Package $package An instance of (Woo Blocks) Package. */ public function __construct( Package $package ) { // Note - we're using the same ID as the real email. // This ensures that any merchant tweaks (Settings > Emails) // apply to this email (consistent with the core email). $this->id = 'customer_new_account'; $this->customer_email = true; $this->title = __( 'New account', 'woocommerce' ); $this->description = __( 'Customer "new account" emails are sent to the customer when a customer signs up via checkout or account blocks.', 'woocommerce' ); $this->template_html = 'emails/customer-new-account-blocks.php'; $this->template_plain = 'emails/plain/customer-new-account-blocks.php'; $this->default_template_path = $package->get_path( '/templates/' ); // Call parent constructor. parent::__construct(); } /** * Get email subject. * * @since 3.1.0 * @return string */ public function get_default_subject() { return __( 'Your {site_title} account has been created!', 'woocommerce' ); } /** * Get email heading. * * @since 3.1.0 * @return string */ public function get_default_heading() { return __( 'Welcome to {site_title}', 'woocommerce' ); } /** * Trigger. * * @param int $user_id User ID. * @param string $user_pass User password. * @param bool $password_generated Whether the password was generated automatically or not. */ public function trigger( $user_id, $user_pass = '', $password_generated = false ) { $this->setup_locale(); if ( $user_id ) { $this->object = new \WP_User( $user_id ); // Generate a magic link so user can set initial password. $key = get_password_reset_key( $this->object ); if ( ! is_wp_error( $key ) ) { $action = 'newaccount'; $this->set_password_url = wc_get_account_endpoint_url( 'lost-password' ) . "?action=$action&key=$key&login=" . rawurlencode( $this->object->user_login ); } $this->user_login = stripslashes( $this->object->user_login ); $this->user_email = stripslashes( $this->object->user_email ); $this->recipient = $this->user_email; } if ( $this->is_enabled() && $this->get_recipient() ) { $this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments(), $this->set_password_url ); } $this->restore_locale(); } /** * Get content html. * * @return string */ public function get_content_html() { return wc_get_template_html( $this->template_html, array( 'email_heading' => $this->get_heading(), 'additional_content' => $this->get_additional_content(), 'user_login' => $this->user_login, 'blogname' => $this->get_blogname(), 'set_password_url' => $this->set_password_url, 'sent_to_admin' => false, 'plain_text' => false, 'email' => $this, ), '', $this->default_template_path ); } /** * Get content plain. * * @return string */ public function get_content_plain() { return wc_get_template_html( $this->template_plain, array( 'email_heading' => $this->get_heading(), 'additional_content' => $this->get_additional_content(), 'user_login' => $this->user_login, 'blogname' => $this->get_blogname(), 'set_password_url' => $this->set_password_url, 'sent_to_admin' => false, 'plain_text' => true, 'email' => $this, ), '', $this->default_template_path ); } /** * Default content to show below main email content. * * @since 3.7.0 * @return string */ public function get_default_additional_content() { return __( 'We look forward to seeing you soon.', 'woocommerce' ); } } woocommerce-blocks/src/Domain/Services/FeatureGating.php 0000644 00000006777 15133551764 0017410 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Domain\Services; /** * Service class that handles the feature flags. * * @internal */ class FeatureGating { /** * Current flag value. * * @var int */ private $flag; const EXPERIMENTAL_FLAG = 3; const FEATURE_PLUGIN_FLAG = 2; const CORE_FLAG = 1; /** * Current environment * * @var string */ private $environment; const PRODUCTION_ENVIRONMENT = 'production'; const DEVELOPMENT_ENVIRONMENT = 'development'; const TEST_ENVIRONMENT = 'test'; /** * Constructor * * @param int $flag Hardcoded flag value. Useful for tests. * @param string $environment Hardcoded environment value. Useful for tests. */ public function __construct( $flag = 0, $environment = 'unset' ) { $this->flag = $flag; $this->environment = $environment; $this->load_flag(); $this->load_environment(); } /** * Set correct flag. */ public function load_flag() { if ( 0 === $this->flag ) { $default_flag = defined( 'WC_BLOCKS_IS_FEATURE_PLUGIN' ) ? self::FEATURE_PLUGIN_FLAG : self::CORE_FLAG; if ( file_exists( __DIR__ . '/../../../blocks.ini' ) ) { $allowed_flags = [ self::EXPERIMENTAL_FLAG, self::FEATURE_PLUGIN_FLAG, self::CORE_FLAG ]; $woo_options = parse_ini_file( __DIR__ . '/../../../blocks.ini' ); $this->flag = is_array( $woo_options ) && in_array( intval( $woo_options['woocommerce_blocks_phase'] ), $allowed_flags, true ) ? $woo_options['woocommerce_blocks_phase'] : $default_flag; } else { $this->flag = $default_flag; } } } /** * Set correct environment. */ public function load_environment() { if ( 'unset' === $this->environment ) { if ( file_exists( __DIR__ . '/../../../blocks.ini' ) ) { $allowed_environments = [ self::PRODUCTION_ENVIRONMENT, self::DEVELOPMENT_ENVIRONMENT, self::TEST_ENVIRONMENT ]; $woo_options = parse_ini_file( __DIR__ . '/../../../blocks.ini' ); $this->environment = is_array( $woo_options ) && in_array( $woo_options['woocommerce_blocks_env'], $allowed_environments, true ) ? $woo_options['woocommerce_blocks_env'] : self::PRODUCTION_ENVIRONMENT; } else { $this->environment = self::PRODUCTION_ENVIRONMENT; } } } /** * Returns the current flag value. * * @return int */ public function get_flag() { return $this->flag; } /** * Checks if we're executing the code in an experimental build mode. * * @return boolean */ public function is_experimental_build() { return $this->flag >= self::EXPERIMENTAL_FLAG; } /** * Checks if we're executing the code in an feature plugin or experimental build mode. * * @return boolean */ public function is_feature_plugin_build() { return $this->flag >= self::FEATURE_PLUGIN_FLAG; } /** * Returns the current environment value. * * @return string */ public function get_environment() { return $this->environment; } /** * Checks if we're executing the code in an development environment. * * @return boolean */ public function is_development_environment() { return self::DEVELOPMENT_ENVIRONMENT === $this->environment; } /** * Checks if we're executing the code in a production environment. * * @return boolean */ public function is_production_environment() { return self::PRODUCTION_ENVIRONMENT === $this->environment; } /** * Checks if we're executing the code in a test environment. * * @return boolean */ public function is_test_environment() { return self::TEST_ENVIRONMENT === $this->environment; } } woocommerce-blocks/src/Domain/Services/ExtendRestApi.php 0000644 00000026270 15133551764 0017370 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\Domain\Services; use Automattic\WooCommerce\Blocks\Domain\Package; use Automattic\WooCommerce\Blocks\StoreApi\Routes\RouteException; use Automattic\WooCommerce\Blocks\StoreApi\Formatters; use Throwable; use Exception; /** * Service class to provide utility functions to extend REST API. */ final class ExtendRestApi { /** * List of Store API schema that is allowed to be extended by extensions. * * @var array */ private $endpoints = [ \Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartItemSchema::IDENTIFIER, \Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartSchema::IDENTIFIER, \Automattic\WooCommerce\Blocks\StoreApi\Schemas\CheckoutSchema::IDENTIFIER, ]; /** * Holds the Package instance * * @var Package */ private $package; /** * Holds the formatters class instance. * * @var Formatters */ private $formatters; /** * Constructor * * @param Package $package An instance of the package class. * @param Formatters $formatters An instance of the formatters class. */ public function __construct( Package $package, Formatters $formatters ) { $this->package = $package; $this->formatters = $formatters; } /** * Returns a formatter instance. * * @param string $name Formatter name. * @return FormatterInterface */ public function get_formatter( $name ) { return $this->formatters->$name; } /** * Data to be extended * * @var array */ private $extend_data = []; /** * Data to be extended * * @var array */ private $callback_methods = []; /** * Array of payment requirements * * @var array */ private $payment_requirements = []; /** * An endpoint that validates registration method call * * @param array $args { * An array of elements that make up a post to update or insert. * * @type string $endpoint The endpoint to extend. * @type string $namespace Plugin namespace. * @type callable $schema_callback Callback executed to add schema data. * @type callable $data_callback Callback executed to add endpoint data. * @type string $schema_type The type of data, object or array. * } * * @throws Exception On failure to register. * @return boolean True on success. */ public function register_endpoint_data( $args ) { if ( ! is_string( $args['namespace'] ) ) { $this->throw_exception( 'You must provide a plugin namespace when extending a Store REST endpoint.' ); } if ( ! is_string( $args['endpoint'] ) || ! in_array( $args['endpoint'], $this->endpoints, true ) ) { $this->throw_exception( sprintf( 'You must provide a valid Store REST endpoint to extend, valid endpoints are: %1$s. You provided %2$s.', implode( ', ', $this->endpoints ), $args['endpoint'] ) ); } if ( isset( $args['schema_callback'] ) && ! is_callable( $args['schema_callback'] ) ) { $this->throw_exception( '$schema_callback must be a callable function.' ); } if ( isset( $args['data_callback'] ) && ! is_callable( $args['data_callback'] ) ) { $this->throw_exception( '$data_callback must be a callable function.' ); } if ( isset( $args['schema_type'] ) && ! in_array( $args['schema_type'], [ ARRAY_N, ARRAY_A ], true ) ) { $this->throw_exception( sprintf( 'Data type must be either ARRAY_N for a numeric array or ARRAY_A for an object like array. You provided %1$s.', $args['schema_type'] ) ); } $this->extend_data[ $args['endpoint'] ][ $args['namespace'] ] = [ 'schema_callback' => isset( $args['schema_callback'] ) ? $args['schema_callback'] : null, 'data_callback' => isset( $args['data_callback'] ) ? $args['data_callback'] : null, 'schema_type' => isset( $args['schema_type'] ) ? $args['schema_type'] : ARRAY_A, ]; return true; } /** * Add callback functions that can be executed by the cart/extensions endpoint. * * @param array $args { * An array of elements that make up the callback configuration. * * @type string $endpoint The endpoint to extend. * @type string $namespace Plugin namespace. * @type callable $callback The function/callable to execute. * } * * @throws RouteException On failure to register. * @returns boolean True on success. */ public function register_update_callback( $args ) { if ( ! array_key_exists( 'namespace', $args ) || ! is_string( $args['namespace'] ) ) { throw new RouteException( 'woocommerce_rest_cart_extensions_error', 'You must provide a plugin namespace when extending a Store REST endpoint.', 400 ); } if ( ! array_key_exists( 'callback', $args ) || ! is_callable( $args['callback'] ) ) { throw new RouteException( 'woocommerce_rest_cart_extensions_error', 'There is no valid callback supplied to register_update_callback.', 400 ); } $this->callback_methods[ $args['namespace'] ] = [ 'callback' => $args['callback'], ]; return true; } /** * Get callback for a specific endpoint and namespace. * * @param string $namespace The namespace to get callbacks for. * * @return callable The callback registered by the extension. * @throws RouteException When callback is not callable or parameters are incorrect. */ public function get_update_callback( $namespace ) { $method = null; if ( ! is_string( $namespace ) ) { throw new RouteException( 'woocommerce_rest_cart_extensions_error', 'You must provide a plugin namespace when extending a Store REST endpoint.', 400 ); } if ( ! array_key_exists( $namespace, $this->callback_methods ) ) { throw new RouteException( 'woocommerce_rest_cart_extensions_error', sprintf( 'There is no such namespace registered: %1$s.', $namespace ), 400 ); } if ( ! array_key_exists( 'callback', $this->callback_methods[ $namespace ] ) || ! is_callable( $this->callback_methods[ $namespace ]['callback'] ) ) { throw new RouteException( 'woocommerce_rest_cart_extensions_error', sprintf( 'There is no valid callback registered for: %1$s.', $namespace ), 400 ); } return $this->callback_methods[ $namespace ]['callback']; } /** * Returns the registered endpoint data * * @param string $endpoint A valid identifier. * @param array $passed_args Passed arguments from the Schema class. * @return object Returns an casted object with registered endpoint data. * @throws Exception If a registered callback throws an error, or silently logs it. */ public function get_endpoint_data( $endpoint, array $passed_args = [] ) { $registered_data = []; if ( ! isset( $this->extend_data[ $endpoint ] ) ) { return (object) $registered_data; } foreach ( $this->extend_data[ $endpoint ] as $namespace => $callbacks ) { $data = []; if ( is_null( $callbacks['data_callback'] ) ) { continue; } try { $data = $callbacks['data_callback']( ...$passed_args ); if ( ! is_array( $data ) ) { throw new Exception( '$data_callback must return an array.' ); } } catch ( Throwable $e ) { $this->throw_exception( $e ); continue; } $registered_data[ $namespace ] = $data; } return (object) $registered_data; } /** * Returns the registered endpoint schema * * @param string $endpoint A valid identifier. * @param array $passed_args Passed arguments from the Schema class. * @return array Returns an array with registered schema data. * @throws Exception If a registered callback throws an error, or silently logs it. */ public function get_endpoint_schema( $endpoint, array $passed_args = [] ) { $registered_schema = []; if ( ! isset( $this->extend_data[ $endpoint ] ) ) { return (object) $registered_schema; } foreach ( $this->extend_data[ $endpoint ] as $namespace => $callbacks ) { $schema = []; if ( is_null( $callbacks['schema_callback'] ) ) { continue; } try { $schema = $callbacks['schema_callback']( ...$passed_args ); if ( ! is_array( $schema ) ) { throw new Exception( '$schema_callback must return an array.' ); } } catch ( Throwable $e ) { $this->throw_exception( $e ); continue; } $schema = $this->format_extensions_properties( $namespace, $schema, $callbacks['schema_type'] ); $registered_schema[ $namespace ] = $schema; } return (object) $registered_schema; } /** * Registers and validates payment requirements callbacks. * * @param array $args { * Array of registration data. * * @type callable $data_callback Callback executed to add payment requirements data. * } * * @throws Exception On failure to register. * @return boolean True on success. */ public function register_payment_requirements( $args ) { if ( ! is_callable( $args['data_callback'] ) ) { $this->throw_exception( '$data_callback must be a callable function.' ); } $this->payment_requirements[] = $args['data_callback']; return true; } /** * Returns the additional payment requirements. * * @param array $initial_requirements list of requirements that should be added to the collected requirements. * @return array Returns a list of payment requirements. * @throws Exception If a registered callback throws an error, or silently logs it. */ public function get_payment_requirements( array $initial_requirements = [ 'products' ] ) { $requirements = $initial_requirements; if ( empty( $this->payment_requirements ) ) { return $initial_requirements; } foreach ( $this->payment_requirements as $callback ) { $data = []; try { $data = $callback(); if ( ! is_array( $data ) ) { throw new Exception( '$data_callback must return an array.' ); } } catch ( Throwable $e ) { $this->throw_exception( $e ); continue; } $requirements = array_merge( $requirements, $data ); } return array_unique( $requirements ); } /** * Throws error and/or silently logs it. * * @param string|Throwable $exception_or_error Error message or Exception. * @throws Exception An error to throw if we have debug enabled and user is admin. */ private function throw_exception( $exception_or_error ) { if ( is_string( $exception_or_error ) ) { $exception = new Exception( $exception_or_error ); } else { $exception = $exception_or_error; } // Always log an error. wc_caught_exception( $exception ); if ( defined( 'WP_DEBUG' ) && WP_DEBUG && current_user_can( 'manage_woocommerce' ) ) { throw $exception; } } /** * Format schema for an extension. * * @param string $namespace Error message or Exception. * @param array $schema An error to throw if we have debug enabled and user is admin. * @param string $schema_type How should data be shaped. * * @return array Formatted schema. */ private function format_extensions_properties( $namespace, $schema, $schema_type ) { if ( ARRAY_N === $schema_type ) { return [ /* translators: %s: extension namespace */ 'description' => sprintf( __( 'Extension data registered by %s', 'woocommerce' ), $namespace ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => $schema, ]; } return [ /* translators: %s: extension namespace */ 'description' => sprintf( __( 'Extension data registered by %s', 'woocommerce' ), $namespace ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'properties' => $schema, ]; } } woocommerce-blocks/src/Assets.php 0000644 00000005427 15133551764 0013122 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks; use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi; /** * Assets class. * * @deprecated 5.0.0 This class will be removed in a future release. This has been replaced by AssetsController. * @internal */ class Assets { /** * Initialize class features on init. * * @since 2.5.0 * @deprecated 5.0.0 */ public static function init() { _deprecated_function( 'Assets::init', '5.0.0' ); } /** * Register block scripts & styles. * * @since 2.5.0 * @deprecated 5.0.0 */ public static function register_assets() { _deprecated_function( 'Assets::register_assets', '5.0.0' ); } /** * Register the vendors style file. We need to do it after the other files * because we need to check if `wp-edit-post` has been enqueued. * * @deprecated 5.0.0 */ public static function enqueue_scripts() { _deprecated_function( 'Assets::enqueue_scripts', '5.0.0' ); } /** * Add body classes. * * @deprecated 5.0.0 * @param array $classes Array of CSS classnames. * @return array Modified array of CSS classnames. */ public static function add_theme_body_class( $classes = [] ) { _deprecated_function( 'Assets::add_theme_body_class', '5.0.0' ); return $classes; } /** * Add theme class to admin body. * * @deprecated 5.0.0 * @param array $classes String with the CSS classnames. * @return array Modified string of CSS classnames. */ public static function add_theme_admin_body_class( $classes = '' ) { _deprecated_function( 'Assets::add_theme_admin_body_class', '5.0.0' ); return $classes; } /** * Adds a redirect field to the login form so blocks can redirect users after login. * * @deprecated 5.0.0 */ public static function redirect_to_field() { _deprecated_function( 'Assets::redirect_to_field', '5.0.0' ); } /** * Queues a block script in the frontend. * * @since 2.3.0 * @since 2.6.0 Changed $name to $script_name and added $handle argument. * @since 2.9.0 Made it so scripts are not loaded in admin pages. * @deprecated 4.5.0 Block types register the scripts themselves. * * @param string $script_name Name of the script used to identify the file inside build folder. * @param string $handle Optional. Provided if the handle should be different than the script name. `wc-` prefix automatically added. * @param array $dependencies Optional. An array of registered script handles this script depends on. Default empty array. */ public static function register_block_script( $script_name, $handle = '', $dependencies = [] ) { _deprecated_function( 'register_block_script', '4.5.0' ); $asset_api = Package::container()->get( AssetApi::class ); $asset_api->register_block_script( $script_name, $handle, $dependencies ); } } woocommerce-blocks/src/BlockTypesController.php 0000644 00000014740 15133551764 0016001 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks; use Automattic\WooCommerce\Blocks\BlockTypes\AtomicBlock; use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry; use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi; use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry; /** * BlockTypesController class. * * @since 5.0.0 * @internal */ final class BlockTypesController { /** * Instance of the asset API. * * @var AssetApi */ protected $asset_api; /** * Instance of the asset data registry. * * @var AssetDataRegistry */ protected $asset_data_registry; /** * Constructor. * * @param AssetApi $asset_api Instance of the asset API. * @param AssetDataRegistry $asset_data_registry Instance of the asset data registry. */ public function __construct( AssetApi $asset_api, AssetDataRegistry $asset_data_registry ) { $this->asset_api = $asset_api; $this->asset_data_registry = $asset_data_registry; $this->init(); } /** * Initialize class features. */ protected function init() { add_action( 'init', array( $this, 'register_blocks' ) ); add_filter( 'render_block', array( $this, 'add_data_attributes' ), 10, 2 ); add_action( 'woocommerce_login_form_end', array( $this, 'redirect_to_field' ) ); add_filter( 'widget_types_to_hide_from_legacy_widget_block', array( $this, 'hide_legacy_widgets_with_block_equivalent' ) ); } /** * Register blocks, hooking up assets and render functions as needed. */ public function register_blocks() { $block_types = $this->get_block_types(); foreach ( $block_types as $block_type ) { $block_type_class = __NAMESPACE__ . '\\BlockTypes\\' . $block_type; $block_type_instance = new $block_type_class( $this->asset_api, $this->asset_data_registry, new IntegrationRegistry() ); } foreach ( self::get_atomic_blocks() as $block_type ) { $block_type_instance = new AtomicBlock( $this->asset_api, $this->asset_data_registry, new IntegrationRegistry(), $block_type ); } } /** * Add data- attributes to blocks when rendered if the block is under the woocommerce/ namespace. * * @param string $content Block content. * @param array $block Parsed block data. * @return string */ public function add_data_attributes( $content, $block ) { $block_name = $block['blockName']; $block_namespace = strtok( $block_name, '/' ); /** * WooCommerce Blocks Namespaces * * This hook defines which block namespaces should have block name and attribute data- attributes appended on render. * * @param array $allowed_namespaces List of namespaces. */ $allowed_namespaces = array_merge( [ 'woocommerce', 'woocommerce-checkout' ], (array) apply_filters( '__experimental_woocommerce_blocks_add_data_attributes_to_namespace', [] ) ); /** * WooCommerce Blocks Block Names * * This hook defines which block names should have block name and attribute data- attributes appended on render. * * @param array $allowed_namespaces List of namespaces. */ $allowed_blocks = (array) apply_filters( '__experimental_woocommerce_blocks_add_data_attributes_to_block', [] ); if ( ! in_array( $block_namespace, $allowed_namespaces, true ) && ! in_array( $block_name, $allowed_blocks, true ) ) { return $content; } $attributes = (array) $block['attrs']; $escaped_data_attributes = [ 'data-block-name="' . esc_attr( $block['blockName'] ) . '"', ]; foreach ( $attributes as $key => $value ) { if ( is_bool( $value ) ) { $value = $value ? 'true' : 'false'; } if ( ! is_scalar( $value ) ) { $value = wp_json_encode( $value ); } $escaped_data_attributes[] = 'data-' . esc_attr( strtolower( preg_replace( '/(?<!\ )[A-Z]/', '-$0', $key ) ) ) . '="' . esc_attr( $value ) . '"'; } return preg_replace( '/^<div /', '<div ' . implode( ' ', $escaped_data_attributes ) . ' ', trim( $content ) ); } /** * Adds a redirect field to the login form so blocks can redirect users after login. */ public function redirect_to_field() { // phpcs:ignore WordPress.Security.NonceVerification if ( empty( $_GET['redirect_to'] ) ) { return; } echo '<input type="hidden" name="redirect" value="' . esc_attr( esc_url_raw( wp_unslash( $_GET['redirect_to'] ) ) ) . '" />'; // phpcs:ignore WordPress.Security.NonceVerification } /** * Hide legacy widgets with a feature complete block equivalent in the inserter * and prevent them from showing as an option in the Legacy Widget block. * * @param array $widget_types An array of widgets hidden in core. * @return array $widget_types An array inluding the WooCommerce widgets to hide. */ public function hide_legacy_widgets_with_block_equivalent( $widget_types ) { array_push( $widget_types, 'woocommerce_product_search', 'woocommerce_product_categories', 'woocommerce_recent_reviews' ); return $widget_types; } /** * Get list of block types. * * @return array */ protected function get_block_types() { global $wp_version, $pagenow; $block_types = [ 'AllReviews', 'FeaturedCategory', 'FeaturedProduct', 'HandpickedProducts', 'ProductBestSellers', 'ProductCategories', 'ProductCategory', 'ProductNew', 'ProductOnSale', 'ProductsByAttribute', 'ProductTopRated', 'ReviewsByProduct', 'ReviewsByCategory', 'ProductSearch', 'ProductTag', 'AllProducts', 'PriceFilter', 'AttributeFilter', 'StockFilter', 'ActiveFilters', ]; if ( Package::feature()->is_feature_plugin_build() ) { $block_types[] = 'Checkout'; $block_types[] = 'Cart'; } if ( Package::feature()->is_experimental_build() ) { $block_types[] = 'SingleProduct'; $block_types[] = 'CartI2'; $block_types[] = 'MiniCart'; } /** * This disables specific blocks in Widget Areas by not registering them. */ if ( in_array( $pagenow, [ 'widgets.php', 'themes.php', 'customize.php' ], true ) ) { $block_types = array_diff( $block_types, [ 'AllProducts', 'PriceFilter', 'AttributeFilter', 'StockFilter', 'ActiveFilters', 'Cart', 'Checkout', ] ); } return $block_types; } /** * Get atomic blocks types. * * @return array */ protected function get_atomic_blocks() { return [ 'product-title', 'product-button', 'product-image', 'product-price', 'product-rating', 'product-sale-badge', 'product-summary', 'product-sku', 'product-category-list', 'product-tag-list', 'product-stock-indicator', 'product-add-to-cart', ]; } } woocommerce-blocks/src/Library.php 0000644 00000002076 15133551764 0013261 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks; use Automattic\WooCommerce\Blocks\BlockTypes\AtomicBlock; use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry; use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi; use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry; /** * Library class. * * @deprecated 5.0.0 This class will be removed in a future release. This has been replaced by BlockTypesController. * @internal */ class Library { /** * Initialize block library features. * * @deprecated 5.0.0 */ public static function init() { _deprecated_function( 'Library::init', '5.0.0' ); } /** * Register custom tables within $wpdb object. * * @deprecated 5.0.0 */ public static function define_tables() { _deprecated_function( 'Library::define_tables', '5.0.0' ); } /** * Register blocks, hooking up assets and render functions as needed. * * @deprecated 5.0.0 */ public static function register_blocks() { _deprecated_function( 'Library::register_blocks', '5.0.0' ); } } woocommerce-blocks/src/StoreApi/SchemaController.php 0000644 00000011364 15133551764 0016647 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi; use Exception; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\AbstractSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\BillingAddressSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ShippingAddressSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartShippingRateSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartItemSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartCouponSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartExtensionsSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartFeeSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ErrorSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CheckoutSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ProductSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ImageAttachmentSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ProductAttributeSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ProductCategorySchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ProductCollectionDataSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ProductReviewSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\TermSchema; use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi; /** * SchemaController class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class SchemaController { /** * Stores schema class instances. * * @var AbstractSchema[] */ protected $schemas = []; /** * Stores Rest Extending instance * * @var ExtendRestApi */ private $extend; /** * Constructor. * * @param ExtendRestApi $extend Rest Extending instance. */ public function __construct( ExtendRestApi $extend ) { $this->extend = $extend; $this->initialize(); } /** * Get a schema class instance. * * @throws Exception If the schema does not exist. * * @param string $name Name of schema. * @return AbstractSchema */ public function get( $name ) { if ( ! isset( $this->schemas[ $name ] ) ) { throw new Exception( $name . ' schema does not exist' ); } return $this->schemas[ $name ]; } /** * Load schema class instances. */ protected function initialize() { $this->schemas = []; $this->schemas[ ErrorSchema::IDENTIFIER ] = new ErrorSchema( $this->extend ); $this->schemas[ ImageAttachmentSchema::IDENTIFIER ] = new ImageAttachmentSchema( $this->extend ); $this->schemas[ TermSchema::IDENTIFIER ] = new TermSchema( $this->extend ); $this->schemas[ BillingAddressSchema::IDENTIFIER ] = new BillingAddressSchema( $this->extend ); $this->schemas[ ShippingAddressSchema::IDENTIFIER ] = new ShippingAddressSchema( $this->extend ); $this->schemas[ CartShippingRateSchema::IDENTIFIER ] = new CartShippingRateSchema( $this->extend ); $this->schemas[ CartCouponSchema::IDENTIFIER ] = new CartCouponSchema( $this->extend ); $this->schemas[ CartFeeSchema::IDENTIFIER ] = new CartFeeSchema( $this->extend ); $this->schemas[ CartItemSchema::IDENTIFIER ] = new CartItemSchema( $this->extend, $this->schemas[ ImageAttachmentSchema::IDENTIFIER ] ); $this->schemas[ CartSchema::IDENTIFIER ] = new CartSchema( $this->extend, $this->schemas[ CartItemSchema::IDENTIFIER ], $this->schemas[ CartCouponSchema::IDENTIFIER ], $this->schemas[ CartFeeSchema::IDENTIFIER ], $this->schemas[ CartShippingRateSchema::IDENTIFIER ], $this->schemas[ ShippingAddressSchema::IDENTIFIER ], $this->schemas[ BillingAddressSchema::IDENTIFIER ], $this->schemas[ ErrorSchema::IDENTIFIER ] ); $this->schemas[ CartExtensionsSchema::IDENTIFIER ] = new CartExtensionsSchema( $this->extend ); $this->schemas[ CheckoutSchema::IDENTIFIER ] = new CheckoutSchema( $this->extend, $this->schemas[ BillingAddressSchema::IDENTIFIER ], $this->schemas[ ShippingAddressSchema::IDENTIFIER ] ); $this->schemas[ ProductSchema::IDENTIFIER ] = new ProductSchema( $this->extend, $this->schemas[ ImageAttachmentSchema::IDENTIFIER ] ); $this->schemas[ ProductAttributeSchema::IDENTIFIER ] = new ProductAttributeSchema( $this->extend ); $this->schemas[ ProductCategorySchema::IDENTIFIER ] = new ProductCategorySchema( $this->extend, $this->schemas[ ImageAttachmentSchema::IDENTIFIER ] ); $this->schemas[ ProductCollectionDataSchema::IDENTIFIER ] = new ProductCollectionDataSchema( $this->extend ); $this->schemas[ ProductReviewSchema::IDENTIFIER ] = new ProductReviewSchema( $this->extend, $this->schemas[ ImageAttachmentSchema::IDENTIFIER ] ); } } woocommerce-blocks/src/StoreApi/Formatters.php 0000644 00000002346 15133551764 0015531 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi; use \Exception; use Automattic\WooCommerce\Blocks\StoreApi\Formatters\DefaultFormatter; /** * Formatters class. * * Allows formatter classes to be registered. Formatters are exposed to extensions via the ExtendRestApi class. */ class Formatters { /** * Holds an array of formatter class instances. * * @var array */ private $formatters = []; /** * Get a new instance of a formatter class. * * @throws Exception An Exception is thrown if a non-existing formatter is used and the user is admin. * * @param string $name Name of the formatter. * @return FormatterInterface Formatter class instance. */ public function __get( $name ) { if ( ! isset( $this->formatters[ $name ] ) ) { if ( defined( 'WP_DEBUG' ) && WP_DEBUG && current_user_can( 'manage_woocommerce' ) ) { throw new Exception( $name . ' formatter does not exist' ); } return new DefaultFormatter(); } return $this->formatters[ $name ]; } /** * Register a formatter class for usage. * * @param string $name Name of the formatter. * @param string $class A formatter class name. */ public function register( $name, $class ) { $this->formatters[ $name ] = new $class(); } } woocommerce-blocks/src/StoreApi/Utilities/PartialOutOfStockException.php 0000644 00000000542 15133551764 0022606 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Utilities; /** * PartialOutOfStockException class. * * @internal This API is used internally by Blocks, this exception is thrown when an item in a draft order has a * quantity greater than what is available in stock. */ class PartialOutOfStockException extends StockAvailabilityException { } woocommerce-blocks/src/StoreApi/Utilities/NotPurchasableException.php 0000644 00000000474 15133551764 0022147 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Utilities; /** * NotPurchasableException class. * * @internal This API is used internally by Blocks, this exception is thrown when an item in the cart is not able to be * purchased. */ class NotPurchasableException extends StockAvailabilityException { } woocommerce-blocks/src/StoreApi/Utilities/Pagination.php 0000644 00000004254 15133551764 0017447 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Utilities; /** * Pagination class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. * @since 2.5.0 */ class Pagination { /** * Add pagination headers to a response object. * * @param \WP_REST_Response $response Reference to the response object. * @param \WP_REST_Request $request The request object. * @param int $total_items Total items found. * @param int $total_pages Total pages found. * @return \WP_REST_Response */ public function add_headers( $response, $request, $total_items, $total_pages ) { $response->header( 'X-WP-Total', $total_items ); $response->header( 'X-WP-TotalPages', $total_pages ); $current_page = $this->get_current_page( $request ); $link_base = $this->get_link_base( $request ); if ( $current_page > 1 ) { $previous_page = $current_page - 1; if ( $previous_page > $total_pages ) { $previous_page = $total_pages; } $this->add_page_link( $response, 'prev', $previous_page, $link_base ); } if ( $total_pages > $current_page ) { $this->add_page_link( $response, 'next', ( $current_page + 1 ), $link_base ); } return $response; } /** * Get current page. * * @param \WP_REST_Request $request The request object. * @return int Get the page from the request object. */ protected function get_current_page( $request ) { return (int) $request->get_param( 'page' ); } /** * Get base for links from the request object. * * @param \WP_REST_Request $request The request object. * @return string */ protected function get_link_base( $request ) { return add_query_arg( $request->get_query_params(), rest_url( $request->get_route() ) ); } /** * Add a page link. * * @param \WP_REST_Response $response Reference to the response object. * @param string $name Page link name. e.g. prev. * @param int $page Page number. * @param string $link_base Base URL. */ protected function add_page_link( &$response, $name, $page, $link_base ) { $response->link_header( $name, add_query_arg( 'page', $page, $link_base ) ); } } woocommerce-blocks/src/StoreApi/Utilities/ProductQueryFilters.php 0000644 00000020107 15133551764 0021350 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Utilities; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\ProductQuery; /** * Product Query filters class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. * @since 2.5.0 */ class ProductQueryFilters { /** * Get filtered min price for current products. * * @param \WP_REST_Request $request The request object. * @return array */ public function get_filtered_price( $request ) { global $wpdb; // Regenerate the products query without min/max price request params. unset( $request['min_price'], $request['max_price'] ); // Grab the request from the WP Query object, and remove SQL_CALC_FOUND_ROWS and Limits so we get a list of all products. $product_query = new ProductQuery(); add_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10, 2 ); add_filter( 'posts_pre_query', '__return_empty_array' ); $query_args = $product_query->prepare_objects_query( $request ); $query_args['no_found_rows'] = true; $query_args['posts_per_page'] = -1; $query = new \WP_Query(); $result = $query->query( $query_args ); $product_query_sql = $query->request; remove_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10 ); remove_filter( 'posts_pre_query', '__return_empty_array' ); $price_filter_sql = " SELECT min( min_price ) as min_price, MAX( max_price ) as max_price FROM {$wpdb->wc_product_meta_lookup} WHERE product_id IN ( {$product_query_sql} ) "; return $wpdb->get_row( $price_filter_sql ); // phpcs:ignore } /** * Get stock status counts for the current products. * * @param \WP_REST_Request $request The request object. * @return array status=>count pairs. */ public function get_stock_status_counts( $request ) { global $wpdb; $product_query = new ProductQuery(); $stock_status_options = array_map( 'esc_sql', array_keys( wc_get_product_stock_status_options() ) ); $hide_outofstock_items = get_option( 'woocommerce_hide_out_of_stock_items' ); if ( 'yes' === $hide_outofstock_items ) { unset( $stock_status_options['outofstock'] ); } add_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10, 2 ); add_filter( 'posts_pre_query', '__return_empty_array' ); $query_args = $product_query->prepare_objects_query( $request ); unset( $query_args['stock_status'] ); $query_args['no_found_rows'] = true; $query_args['posts_per_page'] = -1; $query = new \WP_Query(); $result = $query->query( $query_args ); $product_query_sql = $query->request; remove_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10 ); remove_filter( 'posts_pre_query', '__return_empty_array' ); $stock_status_counts = array(); foreach ( $stock_status_options as $status ) { $stock_status_count_sql = $this->generate_stock_status_count_query( $status, $product_query_sql, $stock_status_options ); $result = $wpdb->get_row( $stock_status_count_sql ); // phpcs:ignore $stock_status_counts[ $status ] = $result->status_count; } return $stock_status_counts; } /** * Generate calculate query by stock status. * * @param string $status status to calculate. * @param string $product_query_sql product query for current filter state. * @param array $stock_status_options available stock status options. * * @return false|string */ private function generate_stock_status_count_query( $status, $product_query_sql, $stock_status_options ) { if ( ! in_array( $status, $stock_status_options, true ) ) { return false; } global $wpdb; $status = esc_sql( $status ); return " SELECT COUNT( DISTINCT posts.ID ) as status_count FROM {$wpdb->posts} as posts INNER JOIN {$wpdb->postmeta} as postmeta ON posts.ID = postmeta.post_id AND postmeta.meta_key = '_stock_status' AND postmeta.meta_value = '{$status}' WHERE posts.ID IN ( {$product_query_sql} ) "; } /** * Get attribute counts for the current products. * * @param \WP_REST_Request $request The request object. * @param array $attributes Attributes to count, either names or ids. * @return array termId=>count pairs. */ public function get_attribute_counts( $request, $attributes = [] ) { global $wpdb; // Remove paging and sorting params from the request. $request->set_param( 'page', null ); $request->set_param( 'per_page', null ); $request->set_param( 'order', null ); $request->set_param( 'orderby', null ); // Grab the request from the WP Query object, and remove SQL_CALC_FOUND_ROWS and Limits so we get a list of all products. $product_query = new ProductQuery(); add_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10, 2 ); add_filter( 'posts_pre_query', '__return_empty_array' ); $query_args = $product_query->prepare_objects_query( $request ); $query_args['no_found_rows'] = true; $query_args['posts_per_page'] = -1; $query = new \WP_Query(); $result = $query->query( $query_args ); $product_query_sql = $query->request; remove_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10 ); remove_filter( 'posts_pre_query', '__return_empty_array' ); if ( count( $attributes ) === count( array_filter( $attributes, 'is_numeric' ) ) ) { $attributes = array_map( 'wc_attribute_taxonomy_name_by_id', wp_parse_id_list( $attributes ) ); } $attributes_to_count = array_map( function( $attribute ) { $attribute = wc_sanitize_taxonomy_name( $attribute ); return esc_sql( $attribute ); }, $attributes ); $attributes_to_count_sql = 'AND term_taxonomy.taxonomy IN ("' . implode( '","', $attributes_to_count ) . '")'; $attribute_count_sql = " SELECT COUNT( DISTINCT posts.ID ) as term_count, terms.term_id as term_count_id FROM {$wpdb->posts} AS posts INNER JOIN {$wpdb->term_relationships} AS term_relationships ON posts.ID = term_relationships.object_id INNER JOIN {$wpdb->term_taxonomy} AS term_taxonomy USING( term_taxonomy_id ) INNER JOIN {$wpdb->terms} AS terms USING( term_id ) WHERE posts.ID IN ( {$product_query_sql} ) {$attributes_to_count_sql} GROUP BY terms.term_id "; $results = $wpdb->get_results( $attribute_count_sql ); // phpcs:ignore return array_map( 'absint', wp_list_pluck( $results, 'term_count', 'term_count_id' ) ); } /** * Get rating counts for the current products. * * @param \WP_REST_Request $request The request object. * @return array rating=>count pairs. */ public function get_rating_counts( $request ) { global $wpdb; // Regenerate the products query without rating request params. unset( $request['rating'] ); // Grab the request from the WP Query object, and remove SQL_CALC_FOUND_ROWS and Limits so we get a list of all products. $product_query = new ProductQuery(); add_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10, 2 ); add_filter( 'posts_pre_query', '__return_empty_array' ); $query_args = $product_query->prepare_objects_query( $request ); $query_args['no_found_rows'] = true; $query_args['posts_per_page'] = -1; $query = new \WP_Query(); $result = $query->query( $query_args ); $product_query_sql = $query->request; remove_filter( 'posts_clauses', array( $product_query, 'add_query_clauses' ), 10 ); remove_filter( 'posts_pre_query', '__return_empty_array' ); $rating_count_sql = " SELECT COUNT( DISTINCT product_id ) as product_count, ROUND( average_rating, 0 ) as rounded_average_rating FROM {$wpdb->wc_product_meta_lookup} WHERE product_id IN ( {$product_query_sql} ) AND average_rating > 0 GROUP BY rounded_average_rating ORDER BY rounded_average_rating ASC "; $results = $wpdb->get_results( $rating_count_sql ); // phpcs:ignore return array_map( 'absint', wp_list_pluck( $results, 'product_count', 'rounded_average_rating' ) ); } } woocommerce-blocks/src/StoreApi/Utilities/ProductQuery.php 0000644 00000037572 15133551764 0020035 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Utilities; use WC_Tax; /** * Product Query class. * Helper class to handle product queries for the API. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. * @since 2.5.0 */ class ProductQuery { /** * Prepare query args to pass to WP_Query for a REST API request. * * @param \WP_REST_Request $request Request data. * @return array */ public function prepare_objects_query( $request ) { $args = [ 'offset' => $request['offset'], 'order' => $request['order'], 'orderby' => $request['orderby'], 'paged' => $request['page'], 'post__in' => $request['include'], 'post__not_in' => $request['exclude'], 'posts_per_page' => $request['per_page'] ? $request['per_page'] : -1, 'post_parent__in' => $request['parent'], 'post_parent__not_in' => $request['parent_exclude'], 'search' => $request['search'], // This uses search rather than s intentionally to handle searches internally. 'fields' => 'ids', 'ignore_sticky_posts' => true, 'post_status' => 'publish', 'date_query' => [], 'post_type' => 'product', ]; // If searching for a specific SKU, allow any post type. if ( ! empty( $request['sku'] ) ) { $args['post_type'] = [ 'product', 'product_variation' ]; } // Taxonomy query to filter products by type, category, tag, shipping class, and attribute. $tax_query = []; // Filter product type by slug. if ( ! empty( $request['type'] ) ) { if ( 'variation' === $request['type'] ) { $args['post_type'] = 'product_variation'; } else { $args['post_type'] = 'product'; $tax_query[] = [ 'taxonomy' => 'product_type', 'field' => 'slug', 'terms' => $request['type'], ]; } } if ( 'date' === $args['orderby'] ) { $args['orderby'] = 'date ID'; } // Set before into date query. Date query must be specified as an array of an array. if ( isset( $request['before'] ) ) { $args['date_query'][0]['before'] = $request['before']; } // Set after into date query. Date query must be specified as an array of an array. if ( isset( $request['after'] ) ) { $args['date_query'][0]['after'] = $request['after']; } // Set date query column. Defaults to post_date. if ( isset( $request['date_column'] ) && ! empty( $args['date_query'][0] ) ) { $args['date_query'][0]['column'] = 'post_' . $request['date_column']; } // Set custom args to handle later during clauses. $custom_keys = [ 'sku', 'min_price', 'max_price', 'stock_status', ]; foreach ( $custom_keys as $key ) { if ( ! empty( $request[ $key ] ) ) { $args[ $key ] = $request[ $key ]; } } $operator_mapping = [ 'in' => 'IN', 'not_in' => 'NOT IN', 'and' => 'AND', ]; // Map between taxonomy name and arg key. $taxonomies = [ 'product_cat' => 'category', 'product_tag' => 'tag', ]; // Set tax_query for each passed arg. foreach ( $taxonomies as $taxonomy => $key ) { if ( ! empty( $request[ $key ] ) ) { $operator = $request->get_param( $key . '_operator' ) && isset( $operator_mapping[ $request->get_param( $key . '_operator' ) ] ) ? $operator_mapping[ $request->get_param( $key . '_operator' ) ] : 'IN'; $tax_query[] = [ 'taxonomy' => $taxonomy, 'field' => 'term_id', 'terms' => $request[ $key ], 'operator' => $operator, ]; } } // Filter by attributes. if ( ! empty( $request['attributes'] ) ) { $att_queries = []; foreach ( $request['attributes'] as $attribute ) { if ( empty( $attribute['term_id'] ) && empty( $attribute['slug'] ) ) { continue; } if ( in_array( $attribute['attribute'], wc_get_attribute_taxonomy_names(), true ) ) { $operator = isset( $attribute['operator'], $operator_mapping[ $attribute['operator'] ] ) ? $operator_mapping[ $attribute['operator'] ] : 'IN'; $att_queries[] = [ 'taxonomy' => $attribute['attribute'], 'field' => ! empty( $attribute['term_id'] ) ? 'term_id' : 'slug', 'terms' => ! empty( $attribute['term_id'] ) ? $attribute['term_id'] : $attribute['slug'], 'operator' => $operator, ]; } } if ( 1 < count( $att_queries ) ) { // Add relation arg when using multiple attributes. $relation = $request->get_param( 'attribute_relation' ) && isset( $operator_mapping[ $request->get_param( 'attribute_relation' ) ] ) ? $operator_mapping[ $request->get_param( 'attribute_relation' ) ] : 'IN'; $tax_query[] = [ 'relation' => $relation, $att_queries, ]; } else { $tax_query = array_merge( $tax_query, $att_queries ); } } // Build tax_query if taxonomies are set. if ( ! empty( $tax_query ) ) { if ( ! empty( $args['tax_query'] ) ) { $args['tax_query'] = array_merge( $tax_query, $args['tax_query'] ); // phpcs:ignore } else { $args['tax_query'] = $tax_query; // phpcs:ignore } } // Filter featured. if ( is_bool( $request['featured'] ) ) { $args['tax_query'][] = [ 'taxonomy' => 'product_visibility', 'field' => 'name', 'terms' => 'featured', 'operator' => true === $request['featured'] ? 'IN' : 'NOT IN', ]; } // Filter by on sale products. if ( is_bool( $request['on_sale'] ) ) { $on_sale_key = $request['on_sale'] ? 'post__in' : 'post__not_in'; $on_sale_ids = wc_get_product_ids_on_sale(); // Use 0 when there's no on sale products to avoid return all products. $on_sale_ids = empty( $on_sale_ids ) ? [ 0 ] : $on_sale_ids; $args[ $on_sale_key ] += $on_sale_ids; } $catalog_visibility = $request->get_param( 'catalog_visibility' ); $rating = $request->get_param( 'rating' ); $visibility_options = wc_get_product_visibility_options(); if ( in_array( $catalog_visibility, array_keys( $visibility_options ), true ) ) { $exclude_from_catalog = 'search' === $catalog_visibility ? '' : 'exclude-from-catalog'; $exclude_from_search = 'catalog' === $catalog_visibility ? '' : 'exclude-from-search'; $args['tax_query'][] = [ 'taxonomy' => 'product_visibility', 'field' => 'name', 'terms' => [ $exclude_from_catalog, $exclude_from_search ], 'operator' => 'hidden' === $catalog_visibility ? 'AND' : 'NOT IN', 'rating_filter' => true, ]; } if ( $rating ) { $rating_terms = []; foreach ( $rating as $value ) { $rating_terms[] = 'rated-' . $value; } $args['tax_query'][] = [ 'taxonomy' => 'product_visibility', 'field' => 'name', 'terms' => $rating_terms, ]; } $orderby = $request->get_param( 'orderby' ); $order = $request->get_param( 'order' ); $ordering_args = wc()->query->get_catalog_ordering_args( $orderby, $order ); $args['orderby'] = $ordering_args['orderby']; $args['order'] = $ordering_args['order']; if ( 'include' === $orderby ) { $args['orderby'] = 'post__in'; } elseif ( 'id' === $orderby ) { $args['orderby'] = 'ID'; // ID must be capitalized. } elseif ( 'slug' === $orderby ) { $args['orderby'] = 'name'; } if ( $ordering_args['meta_key'] ) { $args['meta_key'] = $ordering_args['meta_key']; // phpcs:ignore } return $args; } /** * Get results of query. * * @param \WP_REST_Request $request Request data. * @return array */ public function get_results( $request ) { $query_args = $this->prepare_objects_query( $request ); add_filter( 'posts_clauses', [ $this, 'add_query_clauses' ], 10, 2 ); $query = new \WP_Query(); $results = $query->query( $query_args ); $total_posts = $query->found_posts; // Out-of-bounds, run the query again without LIMIT for total count. if ( $total_posts < 1 && $query_args['paged'] > 1 ) { unset( $query_args['paged'] ); $count_query = new \WP_Query(); $count_query->query( $query_args ); $total_posts = $count_query->found_posts; } remove_filter( 'posts_clauses', [ $this, 'add_query_clauses' ], 10 ); return [ 'results' => $results, 'total' => (int) $total_posts, 'pages' => $query->query_vars['posts_per_page'] > 0 ? (int) ceil( $total_posts / (int) $query->query_vars['posts_per_page'] ) : 1, ]; } /** * Get objects. * * @param \WP_REST_Request $request Request data. * @return array */ public function get_objects( $request ) { $results = $this->get_results( $request ); return [ 'objects' => array_map( 'wc_get_product', $results['results'] ), 'total' => $results['total'], 'pages' => $results['pages'], ]; } /** * Get last modified date for all products. * * @return int timestamp. */ public function get_last_modified() { global $wpdb; return strtotime( $wpdb->get_var( "SELECT MAX( post_modified_gmt ) FROM {$wpdb->posts} WHERE post_type IN ( 'product', 'product_variation' );" ) ); } /** * Add in conditional search filters for products. * * @param array $args Query args. * @param \WC_Query $wp_query WC_Query object. * @return array */ public function add_query_clauses( $args, $wp_query ) { global $wpdb; if ( $wp_query->get( 'search' ) ) { $search = '%' . $wpdb->esc_like( $wp_query->get( 'search' ) ) . '%'; $search_query = wc_product_sku_enabled() ? $wpdb->prepare( " AND ( $wpdb->posts.post_title LIKE %s OR wc_product_meta_lookup.sku LIKE %s ) ", $search, $search ) : $wpdb->prepare( " AND $wpdb->posts.post_title LIKE %s ", $search ); $args['where'] .= $search_query; $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); } if ( $wp_query->get( 'sku' ) ) { $skus = explode( ',', $wp_query->get( 'sku' ) ); // Include the current string as a SKU too. if ( 1 < count( $skus ) ) { $skus[] = $wp_query->get( 'sku' ); } $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); $args['where'] .= ' AND wc_product_meta_lookup.sku IN ("' . implode( '","', array_map( 'esc_sql', $skus ) ) . '")'; } if ( $wp_query->get( 'stock_status' ) ) { $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); $args['where'] .= ' AND wc_product_meta_lookup.stock_status IN ("' . implode( '","', array_map( 'esc_sql', $wp_query->get( 'stock_status' ) ) ) . '")'; } elseif ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) { $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); $args['where'] .= ' AND wc_product_meta_lookup.stock_status NOT IN ("outofstock")'; } if ( $wp_query->get( 'min_price' ) || $wp_query->get( 'max_price' ) ) { $args = $this->add_price_filter_clauses( $args, $wp_query ); } return $args; } /** * Add in conditional price filters. * * @param array $args Query args. * @param \WC_Query $wp_query WC_Query object. * @return array */ protected function add_price_filter_clauses( $args, $wp_query ) { global $wpdb; $adjust_for_taxes = $this->adjust_price_filters_for_displayed_taxes(); $args['join'] = $this->append_product_sorting_table_join( $args['join'] ); if ( $wp_query->get( 'min_price' ) ) { $min_price_filter = $this->prepare_price_filter( $wp_query->get( 'min_price' ) ); if ( $adjust_for_taxes ) { $args['where'] .= $this->get_price_filter_query_for_displayed_taxes( $min_price_filter, 'min_price', '>=' ); } else { $args['where'] .= $wpdb->prepare( ' AND wc_product_meta_lookup.min_price >= %f ', $min_price_filter ); } } if ( $wp_query->get( 'max_price' ) ) { $max_price_filter = $this->prepare_price_filter( $wp_query->get( 'max_price' ) ); if ( $adjust_for_taxes ) { $args['where'] .= $this->get_price_filter_query_for_displayed_taxes( $max_price_filter, 'max_price', '<=' ); } else { $args['where'] .= $wpdb->prepare( ' AND wc_product_meta_lookup.max_price <= %f ', $max_price_filter ); } } return $args; } /** * Get query for price filters when dealing with displayed taxes. * * @param float $price_filter Price filter to apply. * @param string $column Price being filtered (min or max). * @param string $operator Comparison operator for column. * @return string Constructed query. */ protected function get_price_filter_query_for_displayed_taxes( $price_filter, $column = 'min_price', $operator = '>=' ) { global $wpdb; // Select only used tax classes to avoid unwanted calculations. $product_tax_classes = $wpdb->get_col( "SELECT DISTINCT tax_class FROM {$wpdb->wc_product_meta_lookup};" ); if ( empty( $product_tax_classes ) ) { return ''; } $or_queries = []; // We need to adjust the filter for each possible tax class and combine the queries into one. foreach ( $product_tax_classes as $tax_class ) { $adjusted_price_filter = $this->adjust_price_filter_for_tax_class( $price_filter, $tax_class ); $or_queries[] = $wpdb->prepare( '( wc_product_meta_lookup.tax_class = %s AND wc_product_meta_lookup.`' . esc_sql( $column ) . '` ' . esc_sql( $operator ) . ' %f )', $tax_class, $adjusted_price_filter ); } // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared return $wpdb->prepare( ' AND ( wc_product_meta_lookup.tax_status = "taxable" AND ( 0=1 OR ' . implode( ' OR ', $or_queries ) . ') OR ( wc_product_meta_lookup.tax_status != "taxable" AND wc_product_meta_lookup.`' . esc_sql( $column ) . '` ' . esc_sql( $operator ) . ' %f ) ) ', $price_filter ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared } /** * If price filters need adjustment to work with displayed taxes, this returns true. * * This logic is used when prices are stored in the database differently to how they are being displayed, with regards * to taxes. * * @return boolean */ protected function adjust_price_filters_for_displayed_taxes() { $display = get_option( 'woocommerce_tax_display_shop' ); $database = wc_prices_include_tax() ? 'incl' : 'excl'; return $display !== $database; } /** * Converts price filter from subunits to decimal. * * @param string|int $price_filter Raw price filter in subunit format. * @return float Price filter in decimal format. */ protected function prepare_price_filter( $price_filter ) { return floatval( $price_filter / ( 10 ** wc_get_price_decimals() ) ); } /** * Adjusts a price filter based on a tax class and whether or not the amount includes or excludes taxes. * * This calculation logic is based on `wc_get_price_excluding_tax` and `wc_get_price_including_tax` in core. * * @param float $price_filter Price filter amount as entered. * @param string $tax_class Tax class for adjustment. * @return float */ protected function adjust_price_filter_for_tax_class( $price_filter, $tax_class ) { $tax_display = get_option( 'woocommerce_tax_display_shop' ); $tax_rates = WC_Tax::get_rates( $tax_class ); $base_tax_rates = WC_Tax::get_base_tax_rates( $tax_class ); // If prices are shown incl. tax, we want to remove the taxes from the filter amount to match prices stored excl. tax. if ( 'incl' === $tax_display ) { $taxes = apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ? WC_Tax::calc_tax( $price_filter, $base_tax_rates, true ) : WC_Tax::calc_tax( $price_filter, $tax_rates, true ); return $price_filter - array_sum( $taxes ); } // If prices are shown excl. tax, add taxes to match the prices stored in the DB. $taxes = WC_Tax::calc_tax( $price_filter, $tax_rates, false ); return $price_filter + array_sum( $taxes ); } /** * Join wc_product_meta_lookup to posts if not already joined. * * @param string $sql SQL join. * @return string */ protected function append_product_sorting_table_join( $sql ) { global $wpdb; if ( ! strstr( $sql, 'wc_product_meta_lookup' ) ) { $sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id "; } return $sql; } } woocommerce-blocks/src/StoreApi/Utilities/OutOfStockException.php 0000644 00000000470 15133551764 0021271 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Utilities; /** * OutOfStockException class. * * @internal This API is used internally by Blocks, this exception is thrown when an item in a draft order is out * of stock completely. */ class OutOfStockException extends StockAvailabilityException { } woocommerce-blocks/src/StoreApi/Utilities/InvalidStockLevelsInCartException.php 0000644 00000003134 15133551764 0024077 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Utilities; use WP_Error; /** * InvalidStockLevelsInCartException class. * * @internal This API is used internally by Blocks, this exception is thrown if any items are out of stock * after each product on a draft order has been stock checked. */ class InvalidStockLevelsInCartException extends \Exception { /** * Sanitized error code. * * @var string */ public $error_code; /** * Additional error data. * * @var array */ public $additional_data = []; /** * All errors to display to the user. * * @var WP_Error */ public $error; /** * Setup exception. * * @param string $error_code Machine-readable error code, e.g `woocommerce_invalid_product_id`. * @param WP_Error $error The WP_Error object containing all errors relating to stock availability. * @param array $additional_data Extra data (key value pairs) to expose in the error response. */ public function __construct( $error_code, $error, $additional_data = [] ) { $this->error_code = $error_code; $this->error = $error; $this->additional_data = array_filter( (array) $additional_data ); parent::__construct( '', 409 ); } /** * Returns the error code. * * @return string */ public function getErrorCode() { return $this->error_code; } /** * Returns the list of messages. * * @return WP_Error */ public function getError() { return $this->error; } /** * Returns additional error data. * * @return array */ public function getAdditionalData() { return $this->additional_data; } } woocommerce-blocks/src/StoreApi/Utilities/StockAvailabilityException.php 0000644 00000003136 15133551764 0022651 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Utilities; /** * StockAvailabilityException class. * * @internal This API is used internally by Blocks, this exception is thrown when more than one of a product that * can only be purchased individually is in a cart. */ class StockAvailabilityException extends \Exception { /** * Sanitized error code. * * @var string */ public $error_code; /** * The name of the product that can only be purchased individually. * * @var string */ public $product_name; /** * Additional error data. * * @var array */ public $additional_data = []; /** * Setup exception. * * @param string $error_code Machine-readable error code, e.g `woocommerce_invalid_product_id`. * @param string $product_name The name of the product that can only be purchased individually. * @param array $additional_data Extra data (key value pairs) to expose in the error response. */ public function __construct( $error_code, $product_name, $additional_data = [] ) { $this->error_code = $error_code; $this->product_name = $product_name; $this->additional_data = array_filter( (array) $additional_data ); parent::__construct(); } /** * Returns the error code. * * @return string */ public function getErrorCode() { return $this->error_code; } /** * Returns additional error data. * * @return array */ public function getAdditionalData() { return $this->additional_data; } /** * Returns the product name. * * @return string */ public function getProductName() { return $this->product_name; } } woocommerce-blocks/src/StoreApi/Utilities/OrderController.php 0000644 00000044447 15133551764 0020505 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Utilities; use \Exception; use Automattic\WooCommerce\Blocks\StoreApi\Routes\RouteException; /** * OrderController class. * Helper class which creates and syncs orders with the cart. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class OrderController { /** * Create order and set props based on global settings. * * @throws RouteException Exception if invalid data is detected. * * @return \WC_Order A new order object. */ public function create_order_from_cart() { if ( wc()->cart->is_empty() ) { throw new RouteException( 'woocommerce_rest_cart_empty', __( 'Cannot create order from empty cart.', 'woocommerce' ), 400 ); } add_filter( 'woocommerce_default_order_status', array( $this, 'default_order_status' ) ); $order = new \WC_Order(); $order->set_status( 'checkout-draft' ); $order->set_created_via( 'store-api' ); $this->update_order_from_cart( $order ); remove_filter( 'woocommerce_default_order_status', array( $this, 'default_order_status' ) ); return $order; } /** * Update an order using data from the current cart. * * @param \WC_Order $order The order object to update. */ public function update_order_from_cart( \WC_Order $order ) { // Ensure cart is current. wc()->cart->calculate_shipping(); wc()->cart->calculate_totals(); // Update the current order to match the current cart. $this->update_line_items_from_cart( $order ); $this->update_addresses_from_cart( $order ); $order->set_currency( get_woocommerce_currency() ); $order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) ); $order->set_customer_id( get_current_user_id() ); $order->set_customer_ip_address( \WC_Geolocation::get_ip_address() ); $order->set_customer_user_agent( wc_get_user_agent() ); $order->update_meta_data( 'is_vat_exempt', wc()->cart->get_customer()->get_is_vat_exempt() ? 'yes' : 'no' ); $order->calculate_totals(); } /** * Copies order data to customer object (not the session), so values persist for future checkouts. * * @param \WC_Order $order Order object. */ public function sync_customer_data_with_order( \WC_Order $order ) { if ( $order->get_customer_id() ) { $customer = new \WC_Customer( $order->get_customer_id() ); $customer->set_props( [ 'billing_first_name' => $order->get_billing_first_name(), 'billing_last_name' => $order->get_billing_last_name(), 'billing_company' => $order->get_billing_company(), 'billing_address_1' => $order->get_billing_address_1(), 'billing_address_2' => $order->get_billing_address_2(), 'billing_city' => $order->get_billing_city(), 'billing_state' => $order->get_billing_state(), 'billing_postcode' => $order->get_billing_postcode(), 'billing_country' => $order->get_billing_country(), 'billing_email' => $order->get_billing_email(), 'billing_phone' => $order->get_billing_phone(), 'shipping_first_name' => $order->get_shipping_first_name(), 'shipping_last_name' => $order->get_shipping_last_name(), 'shipping_company' => $order->get_shipping_company(), 'shipping_address_1' => $order->get_shipping_address_1(), 'shipping_address_2' => $order->get_shipping_address_2(), 'shipping_city' => $order->get_shipping_city(), 'shipping_state' => $order->get_shipping_state(), 'shipping_postcode' => $order->get_shipping_postcode(), 'shipping_country' => $order->get_shipping_country(), ] ); $shipping_phone_value = is_callable( [ $order, 'get_shipping_phone' ] ) ? $order->get_shipping_phone() : $order->get_meta( '_shipping_phone', true ); if ( is_callable( [ $customer, 'set_shipping_phone' ] ) ) { $customer->set_shipping_phone( $shipping_phone_value ); } else { $customer->update_meta_data( 'shipping_phone', $shipping_phone_value ); } $customer->save(); }; } /** * Final validation ran before payment is taken. * * By this point we have an order populated with customer data and items. * * @throws RouteException Exception if invalid data is detected. * @param \WC_Order $order Order object. */ public function validate_order_before_payment( \WC_Order $order ) { $needs_shipping = wc()->cart->needs_shipping(); $chosen_shipping_methods = wc()->session->get( 'chosen_shipping_methods' ); $this->validate_coupons( $order ); $this->validate_email( $order ); $this->validate_selected_shipping_methods( $needs_shipping, $chosen_shipping_methods ); $this->validate_addresses( $order ); } /** * Convert a coupon code to a coupon object. * * @param string $coupon_code Coupon code. * @return \WC_Coupon Coupon object. */ protected function get_coupon( $coupon_code ) { return new \WC_Coupon( $coupon_code ); } /** * Validate coupons applied to the order and remove those that are not valid. * * @throws RouteException Exception if invalid data is detected. * @param \WC_Order $order Order object. */ protected function validate_coupons( \WC_Order $order ) { $coupon_codes = $order->get_coupon_codes(); $coupons = array_filter( array_map( [ $this, 'get_coupon' ], $coupon_codes ) ); $validators = [ 'validate_coupon_email_restriction', 'validate_coupon_usage_limit' ]; $coupon_errors = []; foreach ( $coupons as $coupon ) { try { array_walk( $validators, function( $validator, $index, $params ) { call_user_func_array( [ $this, $validator ], $params ); }, [ $coupon, $order ] ); } catch ( Exception $error ) { $coupon_errors[ $coupon->get_code() ] = $error->getMessage(); } } if ( $coupon_errors ) { // Remove all coupons that were not valid. foreach ( $coupon_errors as $coupon_code => $message ) { wc()->cart->remove_coupon( $coupon_code ); } // Recalculate totals. wc()->cart->calculate_totals(); // Re-sync order with cart. $this->update_order_from_cart( $order ); // Return exception so customer can review before payment. throw new RouteException( 'woocommerce_rest_cart_coupon_errors', sprintf( /* translators: %s Coupon codes. */ __( 'Invalid coupons were removed from the cart: "%s"', 'woocommerce' ), implode( '", "', array_keys( $coupon_errors ) ) ), 409, [ 'removed_coupons' => $coupon_errors, ] ); } } /** * Validates the customer email. This is a required field. * * @throws RouteException Exception if invalid data is detected. * @param \WC_Order $order Order object. */ protected function validate_email( \WC_Order $order ) { $email = $order->get_billing_email(); if ( empty( $email ) ) { throw new RouteException( 'woocommerce_rest_missing_email_address', __( 'A valid email address is required', 'woocommerce' ), 400 ); } if ( ! is_email( $email ) ) { throw new RouteException( 'woocommerce_rest_invalid_email_address', sprintf( /* translators: %s provided email. */ __( 'The provided email address (%s) is not valid—please provide a valid email address', 'woocommerce' ), esc_html( $email ) ), 400 ); } } /** * Validates customer address data based on the locale to ensure required fields are set. * * @throws RouteException Exception if invalid data is detected. * @param \WC_Order $order Order object. */ protected function validate_addresses( \WC_Order $order ) { $errors = new \WP_Error(); $needs_shipping = wc()->cart->needs_shipping(); $billing_address = $order->get_address( 'billing' ); $shipping_address = $order->get_address( 'shipping' ); if ( $needs_shipping && ! $this->validate_allowed_country( $shipping_address['country'], (array) wc()->countries->get_shipping_countries() ) ) { throw new RouteException( 'woocommerce_rest_invalid_address_country', sprintf( /* translators: %s country code. */ __( 'Sorry, we do not ship orders to the provided country (%s)', 'woocommerce' ), $shipping_address['country'] ), 400, [ 'allowed_countries' => array_keys( wc()->countries->get_shipping_countries() ), ] ); } if ( ! $this->validate_allowed_country( $billing_address['country'], (array) wc()->countries->get_allowed_countries() ) ) { throw new RouteException( 'woocommerce_rest_invalid_address_country', sprintf( /* translators: %s country code. */ __( 'Sorry, we do not allow orders from the provided country (%s)', 'woocommerce' ), $billing_address['country'] ), 400, [ 'allowed_countries' => array_keys( wc()->countries->get_allowed_countries() ), ] ); } if ( $needs_shipping ) { $this->validate_address_fields( $shipping_address, 'shipping', $errors ); } $this->validate_address_fields( $billing_address, 'billing', $errors ); if ( ! $errors->has_errors() ) { return; } $errors_by_code = []; $error_codes = $errors->get_error_codes(); foreach ( $error_codes as $code ) { $errors_by_code[ $code ] = $errors->get_error_messages( $code ); } // Surface errors from first code. foreach ( $errors_by_code as $code => $error_messages ) { throw new RouteException( 'woocommerce_rest_invalid_address', sprintf( /* translators: %s Address type. */ __( 'There was a problem with the provided %s:', 'woocommerce' ) . ' ' . implode( ', ', $error_messages ), 'shipping' === $code ? __( 'shipping address', 'woocommerce' ) : __( 'billing address', 'woocommerce' ) ), 400, [ 'errors' => $errors_by_code, ] ); } } /** * Check all required address fields are set and return errors if not. * * @param string $country Country code. * @param array $allowed_countries List of valid country codes. * @return boolean True if valid. */ protected function validate_allowed_country( $country, array $allowed_countries ) { return array_key_exists( $country, $allowed_countries ); } /** * Check all required address fields are set and return errors if not. * * @param array $address Address array. * @param string $address_type billing or shipping address, used in error messages. * @param \WP_Error $errors Error object. */ protected function validate_address_fields( $address, $address_type, \WP_Error $errors ) { $all_locales = wc()->countries->get_country_locale(); $current_locale = isset( $all_locales[ $address['country'] ] ) ? $all_locales[ $address['country'] ] : []; /** * We are not using wc()->counties->get_default_address_fields() here because that is filtered. Instead, this array * is based on assets/js/base/components/cart-checkout/address-form/default-address-fields.js */ $address_fields = [ 'first_name' => [ 'label' => __( 'First name', 'woocommerce' ), 'required' => true, ], 'last_name' => [ 'label' => __( 'Last name', 'woocommerce' ), 'required' => true, ], 'company' => [ 'label' => __( 'Company', 'woocommerce' ), 'required' => false, ], 'address_1' => [ 'label' => __( 'Address', 'woocommerce' ), 'required' => true, ], 'address_2' => [ 'label' => __( 'Apartment, suite, etc.', 'woocommerce' ), 'required' => false, ], 'country' => [ 'label' => __( 'Country/Region', 'woocommerce' ), 'required' => true, ], 'city' => [ 'label' => __( 'City', 'woocommerce' ), 'required' => true, ], 'state' => [ 'label' => __( 'State/County', 'woocommerce' ), 'required' => true, ], 'postcode' => [ 'label' => __( 'Postal code', 'woocommerce' ), 'required' => true, ], ]; if ( $current_locale ) { foreach ( $current_locale as $key => $field ) { if ( isset( $address_fields[ $key ] ) ) { $address_fields[ $key ]['label'] = isset( $field['label'] ) ? $field['label'] : $address_fields[ $key ]['label']; $address_fields[ $key ]['required'] = isset( $field['required'] ) ? $field['required'] : $address_fields[ $key ]['required']; } } } foreach ( $address_fields as $address_field_key => $address_field ) { if ( empty( $address[ $address_field_key ] ) && $address_field['required'] ) { /* translators: %s Field label. */ $errors->add( $address_type, sprintf( __( '%s is required', 'woocommerce' ), $address_field['label'] ), $address_field_key ); } } } /** * Check email restrictions of a coupon against the order. * * @throws Exception Exception if invalid data is detected. * @param \WC_Coupon $coupon Coupon object applied to the cart. * @param \WC_Order $order Order object. */ protected function validate_coupon_email_restriction( \WC_Coupon $coupon, \WC_Order $order ) { $restrictions = $coupon->get_email_restrictions(); if ( ! empty( $restrictions ) && $order->get_billing_email() && ! wc()->cart->is_coupon_emails_allowed( [ $order->get_billing_email() ], $restrictions ) ) { throw new Exception( $coupon->get_coupon_error( \WC_Coupon::E_WC_COUPON_NOT_YOURS_REMOVED ) ); } } /** * Check usage restrictions of a coupon against the order. * * @throws Exception Exception if invalid data is detected. * @param \WC_Coupon $coupon Coupon object applied to the cart. * @param \WC_Order $order Order object. */ protected function validate_coupon_usage_limit( \WC_Coupon $coupon, \WC_Order $order ) { $coupon_usage_limit = $coupon->get_usage_limit_per_user(); if ( $coupon_usage_limit > 0 ) { $data_store = $coupon->get_data_store(); $usage_count = $order->get_customer_id() ? $data_store->get_usage_by_user_id( $coupon, $order->get_customer_id() ) : $data_store->get_usage_by_email( $coupon, $order->get_billing_email() ); if ( $usage_count >= $coupon_usage_limit ) { throw new Exception( $coupon->get_coupon_error( \WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED ) ); } } } /** * Check there is a shipping method if it requires shipping. * * @throws RouteException Exception if invalid data is detected. * @param boolean $needs_shipping Current order needs shipping. * @param array $chosen_shipping_methods Array of shipping methods. */ public function validate_selected_shipping_methods( $needs_shipping, $chosen_shipping_methods = array() ) { if ( ! $needs_shipping || ! is_array( $chosen_shipping_methods ) ) { return; } foreach ( $chosen_shipping_methods as $chosen_shipping_method ) { if ( false === $chosen_shipping_method ) { throw new RouteException( 'woocommerce_rest_invalid_shipping_option', __( 'Sorry, this order requires a shipping option.', 'woocommerce' ), 400, [] ); } } } /** * Changes default order status to draft for orders created via this API. * * @return string */ public function default_order_status() { return 'checkout-draft'; } /** * Create order line items. * * @param \WC_Order $order The order object to update. */ protected function update_line_items_from_cart( \WC_Order $order ) { $cart_controller = new CartController(); $cart = $cart_controller->get_cart_instance(); $cart_hashes = $cart_controller->get_cart_hashes(); if ( $order->get_cart_hash() !== $cart_hashes['line_items'] ) { $order->set_cart_hash( $cart_hashes['line_items'] ); $order->remove_order_items( 'line_item' ); wc()->checkout->create_order_line_items( $order, $cart ); } if ( $order->get_meta_data( '_shipping_hash' ) !== $cart_hashes['shipping'] ) { $order->update_meta_data( '_shipping_hash', $cart_hashes['shipping'] ); $order->remove_order_items( 'shipping' ); wc()->checkout->create_order_shipping_lines( $order, wc()->session->get( 'chosen_shipping_methods' ), wc()->shipping()->get_packages() ); } if ( $order->get_meta_data( '_coupons_hash' ) !== $cart_hashes['coupons'] ) { $order->remove_order_items( 'coupon' ); $order->update_meta_data( '_coupons_hash', $cart_hashes['coupons'] ); wc()->checkout->create_order_coupon_lines( $order, $cart ); } if ( $order->get_meta_data( '_fees_hash' ) !== $cart_hashes['fees'] ) { $order->update_meta_data( '_fees_hash', $cart_hashes['fees'] ); $order->remove_order_items( 'fee' ); wc()->checkout->create_order_fee_lines( $order, $cart ); } if ( $order->get_meta_data( '_taxes_hash' ) !== $cart_hashes['taxes'] ) { $order->update_meta_data( '_taxes_hash', $cart_hashes['taxes'] ); $order->remove_order_items( 'tax' ); wc()->checkout->create_order_tax_lines( $order, $cart ); } } /** * Update address data from cart and/or customer session data. * * @param \WC_Order $order The order object to update. */ protected function update_addresses_from_cart( \WC_Order $order ) { $order->set_props( [ 'billing_first_name' => wc()->customer->get_billing_first_name(), 'billing_last_name' => wc()->customer->get_billing_last_name(), 'billing_company' => wc()->customer->get_billing_company(), 'billing_address_1' => wc()->customer->get_billing_address_1(), 'billing_address_2' => wc()->customer->get_billing_address_2(), 'billing_city' => wc()->customer->get_billing_city(), 'billing_state' => wc()->customer->get_billing_state(), 'billing_postcode' => wc()->customer->get_billing_postcode(), 'billing_country' => wc()->customer->get_billing_country(), 'billing_email' => wc()->customer->get_billing_email(), 'billing_phone' => wc()->customer->get_billing_phone(), 'shipping_first_name' => wc()->customer->get_shipping_first_name(), 'shipping_last_name' => wc()->customer->get_shipping_last_name(), 'shipping_company' => wc()->customer->get_shipping_company(), 'shipping_address_1' => wc()->customer->get_shipping_address_1(), 'shipping_address_2' => wc()->customer->get_shipping_address_2(), 'shipping_city' => wc()->customer->get_shipping_city(), 'shipping_state' => wc()->customer->get_shipping_state(), 'shipping_postcode' => wc()->customer->get_shipping_postcode(), 'shipping_country' => wc()->customer->get_shipping_country(), ] ); $shipping_phone_value = is_callable( [ wc()->customer, 'get_shipping_phone' ] ) ? wc()->customer->get_shipping_phone() : wc()->customer->get_meta( 'shipping_phone', true ); if ( is_callable( [ $order, 'set_shipping_phone' ] ) ) { $order->set_shipping_phone( $shipping_phone_value ); } else { $order->update_meta_data( '_shipping_phone', $shipping_phone_value ); } } } woocommerce-blocks/src/StoreApi/Utilities/NoticeHandler.php 0000644 00000002430 15133551764 0020067 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Utilities; use Automattic\WooCommerce\Blocks\StoreApi\Routes\RouteException; /** * NoticeHandler class. * Helper class to convert notices to exceptions. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class NoticeHandler { /** * Convert queued error notices into an exception. * * For example, Payment methods may add error notices during validate_fields call to prevent checkout. * Since we're not rendering notices at all, we need to convert them to exceptions. * * This method will find the first error message and thrown an exception instead. Discards notices once complete. * * @throws RouteException If an error notice is detected, Exception is thrown. * * @param string $error_code Error code for the thrown exceptions. */ public static function convert_notices_to_exceptions( $error_code = 'unknown_server_error' ) { if ( 0 === wc_notice_count( 'error' ) ) { return; } $error_notices = wc_get_notices( 'error' ); // Prevent notices from being output later on. wc_clear_notices(); foreach ( $error_notices as $error_notice ) { throw new RouteException( $error_code, wp_strip_all_tags( $error_notice['notice'] ), 400 ); } } } woocommerce-blocks/src/StoreApi/Utilities/CartController.php 0000644 00000114631 15133551764 0020314 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Utilities; use Automattic\WooCommerce\Blocks\StoreApi\Routes\RouteException; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\NoticeHandler; use Automattic\WooCommerce\Blocks\Utils\ArrayUtils; use Automattic\WooCommerce\Checkout\Helpers\ReserveStock; use WP_Error; /** * Woo Cart Controller class. * Helper class to bridge the gap between the cart API and Woo core. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. * @since 2.5.0 */ class CartController { /** * Makes the cart and sessions available to a route by loading them from core. */ public function load_cart() { if ( ! did_action( 'woocommerce_load_cart_from_session' ) && function_exists( 'wc_load_cart' ) ) { include_once WC_ABSPATH . 'includes/wc-cart-functions.php'; include_once WC_ABSPATH . 'includes/wc-notice-functions.php'; wc_load_cart(); } } /** * Based on the core cart class but returns errors rather than rendering notices directly. * * @todo Overriding the core add_to_cart method was necessary because core outputs notices when an item is added to * the cart. For us this would cause notices to build up and output on the store, out of context. Core would need * refactoring to split notices out from other cart actions. * * @throws RouteException Exception if invalid data is detected. * * @param array $request Add to cart request params. * @return string|Error */ public function add_to_cart( $request ) { $cart = $this->get_cart_instance(); $request = wp_parse_args( $request, [ 'id' => 0, 'quantity' => 1, 'variation' => [], 'cart_item_data' => [], ] ); $request = $this->filter_request_data( $this->parse_variation_data( $request ) ); $product = $this->get_product_for_cart( $request ); $cart_id = $cart->generate_cart_id( $this->get_product_id( $product ), $this->get_variation_id( $product ), $request['variation'], $request['cart_item_data'] ); $this->validate_add_to_cart( $product, $request ); $existing_cart_id = $cart->find_product_in_cart( $cart_id ); if ( $existing_cart_id ) { if ( $product->is_sold_individually() ) { throw new RouteException( 'woocommerce_rest_cart_product_sold_individually', sprintf( /* translators: %s: product name */ __( 'You cannot add another "%s" to your cart.', 'woocommerce' ), $product->get_name() ), 400 ); } $cart->set_quantity( $existing_cart_id, $request['quantity'] + $cart->cart_contents[ $existing_cart_id ]['quantity'], true ); return $existing_cart_id; } $cart->cart_contents[ $cart_id ] = apply_filters( 'woocommerce_add_cart_item', array_merge( $request['cart_item_data'], array( 'key' => $cart_id, 'product_id' => $this->get_product_id( $product ), 'variation_id' => $this->get_variation_id( $product ), 'variation' => $request['variation'], 'quantity' => $request['quantity'], 'data' => $product, 'data_hash' => wc_get_cart_item_data_hash( $product ), ) ), $cart_id ); $cart->cart_contents = apply_filters( 'woocommerce_cart_contents_changed', $cart->cart_contents ); do_action( 'woocommerce_add_to_cart', $cart_id, $this->get_product_id( $product ), $request['quantity'], $this->get_variation_id( $product ), $request['variation'], $request['cart_item_data'] ); return $cart_id; } /** * Based on core `set_quantity` method, but validates if an item is sold individually first. * * @throws RouteException Exception if invalid data is detected. * * @param string $item_id Cart item id. * @param integer $quantity Cart quantity. */ public function set_cart_item_quantity( $item_id, $quantity = 1 ) { $cart_item = $this->get_cart_item( $item_id ); if ( empty( $cart_item ) ) { throw new RouteException( 'woocommerce_rest_cart_invalid_key', __( 'Cart item does not exist.', 'woocommerce' ), 404 ); } $product = $cart_item['data']; if ( ! $product instanceof \WC_Product ) { throw new RouteException( 'woocommerce_rest_cart_invalid_product', __( 'Cart item is invalid.', 'woocommerce' ), 404 ); } if ( $product->is_sold_individually() && $quantity > 1 ) { throw new RouteException( 'woocommerce_rest_cart_product_sold_individually', sprintf( /* translators: %s: product name */ __( 'You cannot add another "%s" to your cart.', 'woocommerce' ), $product->get_name() ), 400 ); } $cart = $this->get_cart_instance(); $cart->set_quantity( $item_id, $quantity ); } /** * Validate all items in the cart and check for errors. * * @throws RouteException Exception if invalid data is detected. * * @param \WC_Product $product Product object associated with the cart item. * @param array $request Add to cart request params. */ public function validate_add_to_cart( \WC_Product $product, $request ) { if ( ! $product->is_purchasable() ) { $this->throw_default_product_exception( $product ); } if ( ! $product->is_in_stock() ) { throw new RouteException( 'woocommerce_rest_cart_product_no_stock', sprintf( /* translators: %s: product name */ __( 'You cannot add "%s" to the cart because the product is out of stock.', 'woocommerce' ), $product->get_name() ), 400 ); } if ( $product->managing_stock() && ! $product->backorders_allowed() ) { $qty_remaining = $this->get_remaining_stock_for_product( $product ); $qty_in_cart = $this->get_product_quantity_in_cart( $product ); if ( $qty_remaining < $qty_in_cart + $request['quantity'] ) { throw new RouteException( 'woocommerce_rest_cart_product_no_stock', sprintf( /* translators: 1: product name 2: quantity in stock */ __( 'You cannot add that amount of "%1$s" to the cart because there is not enough stock (%2$s remaining).', 'woocommerce' ), $product->get_name(), wc_format_stock_quantity_for_display( $qty_remaining, $product ) ), 400 ); } } /** * Hook: woocommerce_add_to_cart_validation (legacy). * * Allow 3rd parties to validate if an item can be added to the cart. This is a legacy hook from Woo core. * This filter will be deprecated because it encourages usage of wc_add_notice. For the API we need to capture * notices and convert to exceptions instead. */ $passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $this->get_product_id( $product ), $request['quantity'], $this->get_variation_id( $product ), $request['variation'] ); if ( ! $passed_validation ) { // Validation did not pass - see if an error notice was thrown. NoticeHandler::convert_notices_to_exceptions( 'woocommerce_rest_add_to_cart_error' ); // If no notice was thrown, throw the default notice instead. $this->throw_default_product_exception( $product ); } /** * Fire action to validate add to cart. Functions hooking into this should throw an \Exception to prevent * add to cart from occuring. * * @param \WC_Product $product Product object being added to the cart. * @param array $request Add to cart request params including id, quantity, and variation attributes. */ do_action( 'wooocommerce_store_api_validate_add_to_cart', $product, $request ); } /** * Generates the error message for out of stock products and adds product names to it. * * @param string $singular The message to use when only one product is in the list. * @param string $plural The message to use when more than one product is in the list. * @param array $items The list of cart items whose names should be inserted into the message. * @returns string The translated and correctly pluralised message. */ private function add_product_names_to_message( $singular, $plural, $items ) { $product_names = wc_list_pluck( $items, 'getProductName' ); $message = ( count( $items ) > 1 ) ? $plural : $singular; return sprintf( $message, ArrayUtils::natural_language_join( $product_names, true ) ); } /** * Takes a string describing the type of stock extension, whether there is a single product or multiple products * causing this exception and returns an appropriate error message. * * @param string $exception_type The type of exception encountered. * @param string $singular_or_plural Whether to get the error message for a single product or multiple. * * @return string */ private function get_error_message_for_stock_exception_type( $exception_type, $singular_or_plural ) { $stock_error_messages = [ 'out_of_stock' => [ /* translators: %s: product name. */ 'singular' => __( '%s is out of stock and cannot be purchased. Please remove it from your cart.', 'woocommerce' ), /* translators: %s: product names. */ 'plural' => __( '%s are out of stock and cannot be purchased. Please remove them from your cart.', 'woocommerce' ), ], 'not_purchasable' => [ /* translators: %s: product name. */ 'singular' => __( '%s cannot be purchased. Please remove it from your cart.', 'woocommerce' ), /* translators: %s: product names. */ 'plural' => __( '%s cannot be purchased. Please remove them from your cart.', 'woocommerce' ), ], 'too_many_in_cart' => [ /* translators: %s: product names. */ 'singular' => __( 'There are too many %s in the cart. Only 1 can be purchased. Please reduce the quantity in your cart.', 'woocommerce' ), /* translators: %s: product names. */ 'plural' => __( 'There are too many %s in the cart. Only 1 of each can be purchased. Please reduce the quantities in your cart.', 'woocommerce' ), ], 'partial_out_of_stock' => [ /* translators: %s: product names. */ 'singular' => __( 'There is not enough %s in stock. Please reduce the quantity in your cart.', 'woocommerce' ), /* translators: %s: product names. */ 'plural' => __( 'There are not enough %s in stock. Please reduce the quantities in your cart.', 'woocommerce' ), ], ]; if ( isset( $stock_error_messages[ $exception_type ] ) && isset( $stock_error_messages[ $exception_type ][ $singular_or_plural ] ) ) { return $stock_error_messages[ $exception_type ][ $singular_or_plural ]; } return __( 'There was an error with an item in your cart.', 'woocommerce' ); } /** * Validate all items in the cart and check for errors. * * @throws InvalidStockLevelsInCartException Exception if invalid data is detected due to insufficient stock levels. */ public function validate_cart_items() { $cart = $this->get_cart_instance(); $cart_items = $this->get_cart_items(); $out_of_stock_products = []; $too_many_in_cart_products = []; $partial_out_of_stock_products = []; $not_purchasable_products = []; foreach ( $cart_items as $cart_item_key => $cart_item ) { try { $this->validate_cart_item( $cart_item ); } catch ( TooManyInCartException $error ) { $too_many_in_cart_products[] = $error; } catch ( NotPurchasableException $error ) { $not_purchasable_products[] = $error; } catch ( PartialOutOfStockException $error ) { $partial_out_of_stock_products[] = $error; } catch ( OutOfStockException $error ) { $out_of_stock_products[] = $error; } } $error = new WP_Error(); if ( count( $out_of_stock_products ) > 0 ) { $singular_error = $this->get_error_message_for_stock_exception_type( 'out_of_stock', 'singular' ); $plural_error = $this->get_error_message_for_stock_exception_type( 'out_of_stock', 'plural' ); $error->add( 409, $this->add_product_names_to_message( $singular_error, $plural_error, $out_of_stock_products ) ); } if ( count( $not_purchasable_products ) > 0 ) { $singular_error = $this->get_error_message_for_stock_exception_type( 'not_purchasable', 'singular' ); $plural_error = $this->get_error_message_for_stock_exception_type( 'not_purchasable', 'plural' ); $error->add( 409, $this->add_product_names_to_message( $singular_error, $plural_error, $not_purchasable_products ) ); } if ( count( $too_many_in_cart_products ) > 0 ) { $singular_error = $this->get_error_message_for_stock_exception_type( 'too_many_in_cart', 'singular' ); $plural_error = $this->get_error_message_for_stock_exception_type( 'too_many_in_cart', 'plural' ); $error->add( 409, $this->add_product_names_to_message( $singular_error, $plural_error, $too_many_in_cart_products ) ); } if ( count( $partial_out_of_stock_products ) > 0 ) { $singular_error = $this->get_error_message_for_stock_exception_type( 'partial_out_of_stock', 'singular' ); $plural_error = $this->get_error_message_for_stock_exception_type( 'partial_out_of_stock', 'plural' ); $error->add( 409, $this->add_product_names_to_message( $singular_error, $plural_error, $partial_out_of_stock_products ) ); } if ( $error->has_errors() ) { throw new InvalidStockLevelsInCartException( 'woocommerce_stock_availability_error', $error ); } // Before running the woocommerce_check_cart_items hook, unhook validation from the core cart. remove_action( 'woocommerce_check_cart_items', array( $cart, 'check_cart_items' ), 1 ); remove_action( 'woocommerce_check_cart_items', array( $cart, 'check_cart_coupons' ), 1 ); /** * Hook: woocommerce_check_cart_items * * Allow 3rd parties to validate cart items. This is a legacy hook from Woo core. * This filter will be deprecated because it encourages usage of wc_add_notice. For the API we need to capture * notices and convert to exceptions instead. */ do_action( 'woocommerce_check_cart_items' ); NoticeHandler::convert_notices_to_exceptions( 'woocommerce_rest_cart_item_error' ); } /** * This method will take arrays of exceptions relating to stock, and will convert them to a WP_Error object. * * @param TooManyInCartException[] $too_many_in_cart_products Array of TooManyInCartExceptions. * @param NotPurchasableException[] $not_purchasable_products Array of NotPurchasableExceptions. * @param PartialOutOfStockException[] $partial_out_of_stock_products Array of PartialOutOfStockExceptions. * @param OutOfStockException[] $out_of_stock_products Array of OutOfStockExceptions. * * @return WP_Error[] The WP_Error object returned. Will have errors if any exceptions were in the args. It will be empty if they do not. */ private function stock_exceptions_to_wp_errors( $too_many_in_cart_products, $not_purchasable_products, $partial_out_of_stock_products, $out_of_stock_products ) { $too_many_in_cart_error = new WP_Error(); $out_of_stock_error = new WP_Error(); $partial_out_of_stock_error = new WP_Error(); $not_purchasable_error = new WP_Error(); if ( count( $out_of_stock_products ) > 0 ) { $singular_error = $this->get_error_message_for_stock_exception_type( 'out_of_stock', 'singular' ); $plural_error = $this->get_error_message_for_stock_exception_type( 'out_of_stock', 'plural' ); $out_of_stock_error->add( 'woocommerce-blocks-product-out-of-stock', $this->add_product_names_to_message( $singular_error, $plural_error, $out_of_stock_products ) ); } if ( count( $not_purchasable_products ) > 0 ) { $singular_error = $this->get_error_message_for_stock_exception_type( 'not_purchasable', 'singular' ); $plural_error = $this->get_error_message_for_stock_exception_type( 'not_purchasable', 'plural' ); $not_purchasable_error->add( 'woocommerce-blocks-product-not-purchasable', $this->add_product_names_to_message( $singular_error, $plural_error, $not_purchasable_products ) ); } if ( count( $too_many_in_cart_products ) > 0 ) { $singular_error = $this->get_error_message_for_stock_exception_type( 'too_many_in_cart', 'singular' ); $plural_error = $this->get_error_message_for_stock_exception_type( 'too_many_in_cart', 'plural' ); $too_many_in_cart_error->add( 'woocommerce-blocks-too-many-of-product-in-cart', $this->add_product_names_to_message( $singular_error, $plural_error, $too_many_in_cart_products ) ); } if ( count( $partial_out_of_stock_products ) > 0 ) { $singular_error = $this->get_error_message_for_stock_exception_type( 'partial_out_of_stock', 'singular' ); $plural_error = $this->get_error_message_for_stock_exception_type( 'partial_out_of_stock', 'plural' ); $partial_out_of_stock_error->add( 'woocommerce-blocks-product-partially-out-of-stock', $this->add_product_names_to_message( $singular_error, $plural_error, $partial_out_of_stock_products ) ); } return array_filter( [ $too_many_in_cart_error, $partial_out_of_stock_error, $out_of_stock_error, $not_purchasable_error, ], function( $error ) { return $error->has_errors(); } ); } /** * Validates an existing cart item and returns any errors. * * @throws TooManyInCartException Exception if more than one product that can only be purchased individually is in * the cart. * @throws PartialOutOfStockException Exception if an item has a quantity greater than what is available in stock. * @throws OutOfStockException Exception thrown when an item is entirely out of stock. * @throws NotPurchasableException Exception thrown when an item is not purchasable. * @param array $cart_item Cart item array. */ public function validate_cart_item( $cart_item ) { $product = $cart_item['data']; if ( ! $product instanceof \WC_Product ) { return; } if ( ! $product->is_purchasable() ) { throw new NotPurchasableException( 'woocommerce_rest_cart_product_not_purchasable', $product->get_name() ); } if ( $product->is_sold_individually() && $cart_item['quantity'] > 1 ) { throw new TooManyInCartException( 'woocommerce_rest_cart_product_sold_individually', $product->get_name() ); } if ( ! $product->is_in_stock() ) { throw new OutOfStockException( 'woocommerce_rest_cart_product_no_stock', $product->get_name() ); } if ( $product->managing_stock() && ! $product->backorders_allowed() ) { $qty_remaining = $this->get_remaining_stock_for_product( $product ); $qty_in_cart = $this->get_product_quantity_in_cart( $product ); if ( $qty_remaining < $qty_in_cart ) { throw new PartialOutOfStockException( 'woocommerce_rest_cart_product_partially_no_stock', $product->get_name() ); } } /** * Fire action to validate add to cart. Functions hooking into this should throw an \Exception to prevent * add to cart from occurring. * * @param \WC_Product $product Product object being added to the cart. * @param array $cart_item Cart item array. */ do_action( 'wooocommerce_store_api_validate_cart_item', $product, $cart_item ); } /** * Validate all coupons in the cart and check for errors. * * @throws RouteException Exception if invalid data is detected. */ public function validate_cart_coupons() { $cart_coupons = $this->get_cart_coupons(); foreach ( $cart_coupons as $code ) { $coupon = new \WC_Coupon( $code ); $this->validate_cart_coupon( $coupon ); } } /** * Validate all items in the cart and get a list of errors. * * @return WP_Error[] An array of WP_Errors describing the cart's error state. */ public function get_cart_item_errors() { $errors = []; $cart_items = $this->get_cart_items(); $too_many_in_cart_exceptions = []; $not_purchasable_exceptions = []; $partial_out_of_stock_exceptions = []; $out_of_stock_exceptions = []; foreach ( $cart_items as $cart_item_key => $cart_item ) { try { $this->validate_cart_item( $cart_item ); } catch ( RouteException $error ) { $errors[] = new WP_Error( $error->getErrorCode(), $error->getMessage() ); } catch ( TooManyInCartException $error ) { $too_many_in_cart_exceptions[] = $error; } catch ( NotPurchasableException $error ) { $not_purchasable_exceptions[] = $error; } catch ( PartialOutOfStockException $error ) { $partial_out_of_stock_exceptions[] = $error; } catch ( OutOfStockException $error ) { $out_of_stock_exceptions[] = $error; } } if ( count( $errors ) > 0 ) { return $errors; } return $this->stock_exceptions_to_wp_errors( $too_many_in_cart_exceptions, $not_purchasable_exceptions, $partial_out_of_stock_exceptions, $out_of_stock_exceptions ); } /** * Validate all items in the cart and get a list of errors. * * @throws RouteException Exception if invalid data is detected. */ public function get_cart_coupon_errors() { $errors = []; $cart_coupons = $this->get_cart_coupons(); foreach ( $cart_coupons as $code ) { try { $coupon = new \WC_Coupon( $code ); $this->validate_cart_coupon( $coupon ); } catch ( RouteException $error ) { $errors[] = new \WP_Error( $error->getErrorCode(), $error->getMessage() ); } } return $errors; } /** * Get main instance of cart class. * * @throws RouteException When cart cannot be loaded. * @return \WC_Cart */ public function get_cart_instance() { $cart = wc()->cart; if ( ! $cart || ! $cart instanceof \WC_Cart ) { throw new RouteException( 'woocommerce_rest_cart_error', __( 'Unable to retrieve cart.', 'woocommerce' ), 500 ); } return $cart; } /** * Return a cart item from the woo core cart class. * * @param string $item_id Cart item id. * @return array */ public function get_cart_item( $item_id ) { $cart = $this->get_cart_instance(); return isset( $cart->cart_contents[ $item_id ] ) ? $cart->cart_contents[ $item_id ] : []; } /** * Returns all cart items. * * @param callable $callback Optional callback to apply to the array filter. * @return array */ public function get_cart_items( $callback = null ) { $cart = $this->get_cart_instance(); return $callback ? array_filter( $cart->get_cart(), $callback ) : array_filter( $cart->get_cart() ); } /** * Get hashes for items in the current cart. Useful for tracking changes. * * @return array */ public function get_cart_hashes() { $cart = $this->get_cart_instance(); return [ 'line_items' => $cart->get_cart_hash(), 'shipping' => md5( wp_json_encode( $cart->shipping_methods ) ), 'fees' => md5( wp_json_encode( $cart->get_fees() ) ), 'coupons' => md5( wp_json_encode( $cart->get_applied_coupons() ) ), 'taxes' => md5( wp_json_encode( $cart->get_taxes() ) ), ]; } /** * Empty cart contents. */ public function empty_cart() { $cart = $this->get_cart_instance(); $cart->empty_cart(); } /** * See if cart has applied coupon by code. * * @param string $coupon_code Cart coupon code. * @return bool */ public function has_coupon( $coupon_code ) { $cart = $this->get_cart_instance(); return $cart->has_discount( $coupon_code ); } /** * Returns all applied coupons. * * @param callable $callback Optional callback to apply to the array filter. * @return array */ public function get_cart_coupons( $callback = null ) { $cart = $this->get_cart_instance(); return $callback ? array_filter( $cart->get_applied_coupons(), $callback ) : array_filter( $cart->get_applied_coupons() ); } /** * Get shipping packages from the cart with calculated shipping rates. * * @todo this can be refactored once https://github.com/woocommerce/woocommerce/pull/26101 lands. * * @param bool $calculate_rates Should rates for the packages also be returned. * @return array */ public function get_shipping_packages( $calculate_rates = true ) { $cart = $this->get_cart_instance(); // See if we need to calculate anything. if ( ! $cart->needs_shipping() ) { return []; } $packages = $cart->get_shipping_packages(); // Add extra package data to array. if ( count( $packages ) ) { $packages = array_map( function( $key, $package, $index ) { $package['package_id'] = isset( $package['package_id'] ) ? $package['package_id'] : $key; $package['package_name'] = isset( $package['package_name'] ) ? $package['package_name'] : $this->get_package_name( $package, $index ); return $package; }, array_keys( $packages ), $packages, range( 1, count( $packages ) ) ); } return $calculate_rates ? wc()->shipping()->calculate_shipping( $packages ) : $packages; } /** * Creates a name for a package. * * @param array $package Shipping package from WooCommerce. * @param int $index Package number. * @return string */ protected function get_package_name( $package, $index ) { return apply_filters( 'woocommerce_shipping_package_name', $index > 1 ? sprintf( /* translators: %d: shipping package number */ _x( 'Shipping method %d', 'shipping packages', 'woocommerce' ), $index ) : _x( 'Shipping method', 'shipping packages', 'woocommerce' ), $package['package_id'], $package ); } /** * Selects a shipping rate. * * @param int|string $package_id ID of the package to choose a rate for. * @param string $rate_id ID of the rate being chosen. */ public function select_shipping_rate( $package_id, $rate_id ) { $cart = $this->get_cart_instance(); $session_data = wc()->session->get( 'chosen_shipping_methods' ) ? wc()->session->get( 'chosen_shipping_methods' ) : []; $session_data[ $package_id ] = $rate_id; wc()->session->set( 'chosen_shipping_methods', $session_data ); } /** * Based on the core cart class but returns errors rather than rendering notices directly. * * @todo Overriding the core apply_coupon method was necessary because core outputs notices when a coupon gets * applied. For us this would cause notices to build up and output on the store, out of context. Core would need * refactoring to split notices out from other cart actions. * * @throws RouteException Exception if invalid data is detected. * * @param string $coupon_code Coupon code. */ public function apply_coupon( $coupon_code ) { $cart = $this->get_cart_instance(); $applied_coupons = $this->get_cart_coupons(); $coupon = new \WC_Coupon( $coupon_code ); if ( $coupon->get_code() !== $coupon_code ) { throw new RouteException( 'woocommerce_rest_cart_coupon_error', sprintf( /* translators: %s coupon code */ __( '"%s" is an invalid coupon code.', 'woocommerce' ), esc_html( $coupon_code ) ), 400 ); } if ( $this->has_coupon( $coupon_code ) ) { throw new RouteException( 'woocommerce_rest_cart_coupon_error', sprintf( /* translators: %s coupon code */ __( 'Coupon code "%s" has already been applied.', 'woocommerce' ), esc_html( $coupon_code ) ), 400 ); } if ( ! $coupon->is_valid() ) { throw new RouteException( 'woocommerce_rest_cart_coupon_error', wp_strip_all_tags( $coupon->get_error_message() ), 400 ); } // Prevents new coupons being added if individual use coupons are already in the cart. $individual_use_coupons = $this->get_cart_coupons( function( $code ) { $coupon = new \WC_Coupon( $code ); return $coupon->get_individual_use(); } ); foreach ( $individual_use_coupons as $code ) { $individual_use_coupon = new \WC_Coupon( $code ); if ( false === apply_filters( 'woocommerce_apply_with_individual_use_coupon', false, $coupon, $individual_use_coupon, $applied_coupons ) ) { throw new RouteException( 'woocommerce_rest_cart_coupon_error', sprintf( /* translators: %s: coupon code */ __( '"%s" has already been applied and cannot be used in conjunction with other coupons.', 'woocommerce' ), $code ), 400 ); } } if ( $coupon->get_individual_use() ) { $coupons_to_remove = array_diff( $applied_coupons, apply_filters( 'woocommerce_apply_individual_use_coupon', array(), $coupon, $applied_coupons ) ); foreach ( $coupons_to_remove as $code ) { $cart->remove_coupon( $code ); } $applied_coupons = array_diff( $applied_coupons, $coupons_to_remove ); } $applied_coupons[] = $coupon_code; $cart->set_applied_coupons( $applied_coupons ); do_action( 'woocommerce_applied_coupon', $coupon_code ); } /** * Validates an existing cart coupon and returns any errors. * * @throws RouteException Exception if invalid data is detected. * * @param \WC_Coupon $coupon Coupon object applied to the cart. */ protected function validate_cart_coupon( \WC_Coupon $coupon ) { if ( ! $coupon->is_valid() ) { $cart = $this->get_cart_instance(); $cart->remove_coupon( $coupon->get_code() ); $cart->calculate_totals(); throw new RouteException( 'woocommerce_rest_cart_coupon_error', sprintf( /* translators: %1$s coupon code, %2$s reason. */ __( 'The "%1$s" coupon has been removed from your cart: %2$s', 'woocommerce' ), $coupon->get_code(), wp_strip_all_tags( $coupon->get_error_message() ) ), 409 ); } } /** * Gets the qty of a product across line items. * * @param \WC_Product $product Product object. * @return int */ protected function get_product_quantity_in_cart( $product ) { $cart = $this->get_cart_instance(); $product_quantities = $cart->get_cart_item_quantities(); $product_id = $product->get_stock_managed_by_id(); return isset( $product_quantities[ $product_id ] ) ? $product_quantities[ $product_id ] : 0; } /** * Gets remaining stock for a product. * * @param \WC_Product $product Product object. * @return int */ protected function get_remaining_stock_for_product( $product ) { $reserve_stock = new ReserveStock(); $draft_order = wc()->session->get( 'store_api_draft_order', 0 ); $qty_reserved = $reserve_stock->get_reserved_stock( $product, $draft_order ); return $product->get_stock_quantity() - $qty_reserved; } /** * Get a product object to be added to the cart. * * @throws RouteException Exception if invalid data is detected. * * @param array $request Add to cart request params. * @return \WC_Product|Error Returns a product object if purchasable. */ protected function get_product_for_cart( $request ) { $product = wc_get_product( $request['id'] ); if ( ! $product || 'trash' === $product->get_status() ) { throw new RouteException( 'woocommerce_rest_cart_invalid_product', __( 'This product cannot be added to the cart.', 'woocommerce' ), 400 ); } return $product; } /** * For a given product, get the product ID. * * @param \WC_Product $product Product object associated with the cart item. * @return int */ protected function get_product_id( \WC_Product $product ) { return $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id(); } /** * For a given product, get the variation ID. * * @param \WC_Product $product Product object associated with the cart item. * @return int */ protected function get_variation_id( \WC_Product $product ) { return $product->is_type( 'variation' ) ? $product->get_id() : 0; } /** * Default exception thrown when an item cannot be added to the cart. * * @throws RouteException Exception with code woocommerce_rest_cart_product_is_not_purchasable. * * @param \WC_Product $product Product object associated with the cart item. */ protected function throw_default_product_exception( \WC_Product $product ) { throw new RouteException( 'woocommerce_rest_cart_product_is_not_purchasable', sprintf( /* translators: %s: product name */ __( '"%s" is not available for purchase.', 'woocommerce' ), $product->get_name() ), 400 ); } /** * Filter data for add to cart requests. * * @param array $request Add to cart request params. * @return array Updated request array. */ protected function filter_request_data( $request ) { $product_id = $request['id']; $variation_id = 0; $product = wc_get_product( $product_id ); if ( $product->is_type( 'variation' ) ) { $product_id = $product->get_parent_id(); $variation_id = $product->get_id(); } $request['cart_item_data'] = (array) apply_filters( 'woocommerce_add_cart_item_data', $request['cart_item_data'], $product_id, $variation_id, $request['quantity'] ); if ( $product->is_sold_individually() ) { $request['quantity'] = apply_filters( 'woocommerce_add_to_cart_sold_individually_quantity', 1, $request['quantity'], $product_id, $variation_id, $request['cart_item_data'] ); } return $request; } /** * If variations are set, validate and format the values ready to add to the cart. * * @throws RouteException Exception if invalid data is detected. * * @param array $request Add to cart request params. * @return array Updated request array. */ protected function parse_variation_data( $request ) { $product = $this->get_product_for_cart( $request ); // Remove variation request if not needed. if ( ! $product->is_type( array( 'variation', 'variable' ) ) ) { $request['variation'] = []; return $request; } // Flatten data and format posted values. $variable_product_attributes = $this->get_variable_product_attributes( $product ); $request['variation'] = $this->sanitize_variation_data( wp_list_pluck( $request['variation'], 'value', 'attribute' ), $variable_product_attributes ); // If we have a parent product, find the variation ID. if ( $product->is_type( 'variable' ) ) { $request['id'] = $this->get_variation_id_from_variation_data( $request, $product ); } // Now we have a variation ID, get the valid set of attributes for this variation. They will have an attribute_ prefix since they are from meta. $expected_attributes = wc_get_product_variation_attributes( $request['id'] ); $missing_attributes = []; foreach ( $variable_product_attributes as $attribute ) { if ( ! $attribute['is_variation'] ) { continue; } $prefixed_attribute_name = 'attribute_' . sanitize_title( $attribute['name'] ); $expected_value = isset( $expected_attributes[ $prefixed_attribute_name ] ) ? $expected_attributes[ $prefixed_attribute_name ] : ''; $attribute_label = wc_attribute_label( $attribute['name'] ); if ( isset( $request['variation'][ wc_variation_attribute_name( $attribute['name'] ) ] ) ) { $given_value = $request['variation'][ wc_variation_attribute_name( $attribute['name'] ) ]; if ( $expected_value === $given_value ) { continue; } // If valid values are empty, this is an 'any' variation so get all possible values. if ( '' === $expected_value && in_array( $given_value, $attribute->get_slugs(), true ) ) { continue; } throw new RouteException( 'woocommerce_rest_invalid_variation_data', /* translators: %1$s: Attribute name, %2$s: Allowed values. */ sprintf( __( 'Invalid value posted for %1$s. Allowed values: %2$s', 'woocommerce' ), $attribute_label, implode( ', ', $attribute->get_slugs() ) ), 400 ); } // If no attribute was posted, only error if the variation has an 'any' attribute which requires a value. if ( '' === $expected_value ) { $missing_attributes[] = $attribute_label; } } if ( ! empty( $missing_attributes ) ) { throw new RouteException( 'woocommerce_rest_missing_variation_data', /* translators: %s: Attribute name. */ __( 'Missing variation data for variable product.', 'woocommerce' ) . ' ' . sprintf( _n( '%s is a required field', '%s are required fields', count( $missing_attributes ), 'woocommerce' ), wc_format_list_of_items( $missing_attributes ) ), 400 ); } return $request; } /** * Try to match request data to a variation ID and return the ID. * * @throws RouteException Exception if variation cannot be found. * * @param array $request Add to cart request params. * @param \WC_Product $product Product being added to the cart. * @return int Matching variation ID. */ protected function get_variation_id_from_variation_data( $request, $product ) { $data_store = \WC_Data_Store::load( 'product' ); $match_attributes = $request['variation']; $variation_id = $data_store->find_matching_product_variation( $product, $match_attributes ); if ( empty( $variation_id ) ) { throw new RouteException( 'woocommerce_rest_variation_id_from_variation_data', __( 'No matching variation found.', 'woocommerce' ), 400 ); } return $variation_id; } /** * Format and sanitize variation data posted to the API. * * Labels are converted to names (e.g. Size to pa_size), and values are cleaned. * * @throws RouteException Exception if variation cannot be found. * * @param array $variation_data Key value pairs of attributes and values. * @param array $variable_product_attributes Product attributes we're expecting. * @return array */ protected function sanitize_variation_data( $variation_data, $variable_product_attributes ) { $return = []; foreach ( $variable_product_attributes as $attribute ) { if ( ! $attribute['is_variation'] ) { continue; } $attribute_label = wc_attribute_label( $attribute['name'] ); $variation_attribute_name = wc_variation_attribute_name( $attribute['name'] ); // Attribute labels e.g. Size. if ( isset( $variation_data[ $attribute_label ] ) ) { $return[ $variation_attribute_name ] = $attribute['is_taxonomy'] ? sanitize_title( $variation_data[ $attribute_label ] ) : html_entity_decode( wc_clean( $variation_data[ $attribute_label ] ), ENT_QUOTES, get_bloginfo( 'charset' ) ); continue; } // Attribute slugs e.g. pa_size. if ( isset( $variation_data[ $attribute['name'] ] ) ) { $return[ $variation_attribute_name ] = $attribute['is_taxonomy'] ? sanitize_title( $variation_data[ $attribute['name'] ] ) : html_entity_decode( wc_clean( $variation_data[ $attribute['name'] ] ), ENT_QUOTES, get_bloginfo( 'charset' ) ); } } return $return; } /** * Get product attributes from the variable product (which may be the parent if the product object is a variation). * * @throws RouteException Exception if product is invalid. * * @param \WC_Product $product Product being added to the cart. * @return array */ protected function get_variable_product_attributes( $product ) { if ( $product->is_type( 'variation' ) ) { $product = wc_get_product( $product->get_parent_id() ); } if ( ! $product || 'trash' === $product->get_status() ) { throw new RouteException( 'woocommerce_rest_cart_invalid_parent_product', __( 'This product cannot be added to the cart.', 'woocommerce' ), 400 ); } return $product->get_attributes(); } } woocommerce-blocks/src/StoreApi/Utilities/TooManyInCartException.php 0000644 00000000532 15133551764 0021717 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Utilities; /** * TooManyInCartException class. * * @internal This API is used internally by Blocks, this exception is thrown when more than one of a product that * can only be purchased individually is in a cart. */ class TooManyInCartException extends StockAvailabilityException { } woocommerce-blocks/src/StoreApi/Routes/Products.php 0000644 00000031674 15133551764 0016475 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\Pagination; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\ProductQuery; /** * Products class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class Products extends AbstractRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => $this->get_collection_params(), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a collection of posts and add the post title filter option to \WP_Query. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $response = new \WP_REST_Response(); $product_query = new ProductQuery(); // Only get objects during GET requests. if ( \WP_REST_Server::READABLE === $request->get_method() ) { $query_results = $product_query->get_objects( $request ); $response_objects = []; foreach ( $query_results['objects'] as $object ) { $data = rest_ensure_response( $this->schema->get_item_response( $object ) ); $response_objects[] = $this->prepare_response_for_collection( $data ); } $response->set_data( $response_objects ); } else { $query_results = $product_query->get_results( $request ); } $response = ( new Pagination() )->add_headers( $response, $request, $query_results['total'], $query_results['pages'] ); $response->header( 'Last-Modified', $product_query->get_last_modified() ); return $response; } /** * Prepare links for the request. * * @param \WC_Product $item Product object. * @param \WP_REST_Request $request Request object. * @return array */ protected function prepare_links( $item, $request ) { $links = array( 'self' => array( 'href' => rest_url( $this->get_namespace() . $this->get_path() . '/' . $item->get_id() ), ), 'collection' => array( 'href' => rest_url( $this->get_namespace() . $this->get_path() ), ), ); if ( $item->get_parent_id() ) { $links['up'] = array( 'href' => rest_url( $this->get_namespace() . $this->get_path() . '/' . $item->get_parent_id() ), ); } return $links; } /** * Get the query params for collections of products. * * @return array */ public function get_collection_params() { $params = []; $params['context'] = $this->get_context_param(); $params['context']['default'] = 'view'; $params['page'] = array( 'description' => __( 'Current page of the collection.', 'woocommerce' ), 'type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', 'minimum' => 1, ); $params['per_page'] = array( 'description' => __( 'Maximum number of items to be returned in result set. Defaults to no limit if left blank.', 'woocommerce' ), 'type' => 'integer', 'default' => 10, 'minimum' => 0, 'maximum' => 100, 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); $params['search'] = array( 'description' => __( 'Limit results to those matching a string.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => 'rest_validate_request_arg', ); $params['after'] = array( 'description' => __( 'Limit response to resources created after a given ISO8601 compliant date.', 'woocommerce' ), 'type' => 'string', 'format' => 'date-time', 'validate_callback' => 'rest_validate_request_arg', ); $params['before'] = array( 'description' => __( 'Limit response to resources created before a given ISO8601 compliant date.', 'woocommerce' ), 'type' => 'string', 'format' => 'date-time', 'validate_callback' => 'rest_validate_request_arg', ); $params['date_column'] = array( 'description' => __( 'When limiting response using after/before, which date column to compare against.', 'woocommerce' ), 'type' => 'string', 'default' => 'date', 'enum' => array( 'date', 'date_gmt', 'modified', 'modified_gmt', ), 'validate_callback' => 'rest_validate_request_arg', ); $params['exclude'] = array( 'description' => __( 'Ensure result set excludes specific IDs.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => [], 'sanitize_callback' => 'wp_parse_id_list', ); $params['include'] = array( 'description' => __( 'Limit result set to specific ids.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => [], 'sanitize_callback' => 'wp_parse_id_list', ); $params['offset'] = array( 'description' => __( 'Offset the result set by a specific number of items.', 'woocommerce' ), 'type' => 'integer', 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); $params['order'] = array( 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ), 'type' => 'string', 'default' => 'desc', 'enum' => array( 'asc', 'desc' ), 'validate_callback' => 'rest_validate_request_arg', ); $params['orderby'] = array( 'description' => __( 'Sort collection by object attribute.', 'woocommerce' ), 'type' => 'string', 'default' => 'date', 'enum' => array( 'date', 'modified', 'id', 'include', 'title', 'slug', 'price', 'popularity', 'rating', 'menu_order', 'comment_count', ), 'validate_callback' => 'rest_validate_request_arg', ); $params['parent'] = array( 'description' => __( 'Limit result set to those of particular parent IDs.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => [], 'sanitize_callback' => 'wp_parse_id_list', ); $params['parent_exclude'] = array( 'description' => __( 'Limit result set to all items except those of a particular parent ID.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'sanitize_callback' => 'wp_parse_id_list', 'default' => [], ); $params['type'] = array( 'description' => __( 'Limit result set to products assigned a specific type.', 'woocommerce' ), 'type' => 'string', 'enum' => array_merge( array_keys( wc_get_product_types() ), [ 'variation' ] ), 'sanitize_callback' => 'sanitize_key', 'validate_callback' => 'rest_validate_request_arg', ); $params['sku'] = array( 'description' => __( 'Limit result set to products with specific SKU(s). Use commas to separate.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => 'rest_validate_request_arg', ); $params['featured'] = array( 'description' => __( 'Limit result set to featured products.', 'woocommerce' ), 'type' => 'boolean', 'sanitize_callback' => 'wc_string_to_bool', 'validate_callback' => 'rest_validate_request_arg', ); $params['category'] = array( 'description' => __( 'Limit result set to products assigned a specific category ID.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'wp_parse_id_list', 'validate_callback' => 'rest_validate_request_arg', ); $params['category_operator'] = array( 'description' => __( 'Operator to compare product category terms.', 'woocommerce' ), 'type' => 'string', 'enum' => [ 'in', 'not in', 'and' ], 'default' => 'in', 'sanitize_callback' => 'sanitize_key', 'validate_callback' => 'rest_validate_request_arg', ); $params['tag'] = array( 'description' => __( 'Limit result set to products assigned a specific tag ID.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'wp_parse_id_list', 'validate_callback' => 'rest_validate_request_arg', ); $params['tag_operator'] = array( 'description' => __( 'Operator to compare product tags.', 'woocommerce' ), 'type' => 'string', 'enum' => [ 'in', 'not in', 'and' ], 'default' => 'in', 'sanitize_callback' => 'sanitize_key', 'validate_callback' => 'rest_validate_request_arg', ); $params['on_sale'] = array( 'description' => __( 'Limit result set to products on sale.', 'woocommerce' ), 'type' => 'boolean', 'sanitize_callback' => 'wc_string_to_bool', 'validate_callback' => 'rest_validate_request_arg', ); $params['min_price'] = array( 'description' => __( 'Limit result set to products based on a minimum price, provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => 'rest_validate_request_arg', ); $params['max_price'] = array( 'description' => __( 'Limit result set to products based on a maximum price, provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => 'rest_validate_request_arg', ); $params['stock_status'] = array( 'description' => __( 'Limit result set to products with specified stock status.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'string', 'enum' => array_keys( wc_get_product_stock_status_options() ), 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => 'rest_validate_request_arg', ), 'default' => [], ); $params['attributes'] = array( 'description' => __( 'Limit result set to products with selected global attributes.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'object', 'properties' => array( 'attribute' => array( 'description' => __( 'Attribute taxonomy name.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'wc_sanitize_taxonomy_name', ), 'term_id' => array( 'description' => __( 'List of attribute term IDs.', 'woocommerce' ), 'type' => 'array', 'items' => [ 'type' => 'integer', ], 'sanitize_callback' => 'wp_parse_id_list', ), 'slug' => array( 'description' => __( 'List of attribute slug(s). If a term ID is provided, this will be ignored.', 'woocommerce' ), 'type' => 'array', 'items' => [ 'type' => 'string', ], 'sanitize_callback' => 'wp_parse_slug_list', ), 'operator' => array( 'description' => __( 'Operator to compare product attribute terms.', 'woocommerce' ), 'type' => 'string', 'enum' => [ 'in', 'not in', 'and' ], ), ), ), 'default' => [], ); $params['attribute_relation'] = array( 'description' => __( 'The logical relationship between attributes when filtering across multiple at once.', 'woocommerce' ), 'type' => 'string', 'enum' => [ 'in', 'and' ], 'default' => 'and', 'sanitize_callback' => 'sanitize_key', 'validate_callback' => 'rest_validate_request_arg', ); $params['catalog_visibility'] = array( 'description' => __( 'Determines if hidden or visible catalog products are shown.', 'woocommerce' ), 'type' => 'string', 'enum' => array( 'any', 'visible', 'catalog', 'search', 'hidden' ), 'sanitize_callback' => 'sanitize_key', 'validate_callback' => 'rest_validate_request_arg', ); $params['rating'] = array( 'description' => __( 'Limit result set to products with a certain average rating.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', 'enum' => range( 1, 5 ), ), 'default' => [], 'sanitize_callback' => 'wp_parse_id_list', ); return $params; } } woocommerce-blocks/src/StoreApi/Routes/Batch.php 0000644 00000006257 15133551764 0015712 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; use WP_Error; use WP_REST_Request; use WP_REST_Response; /** * Batch Route class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class Batch extends AbstractRoute implements RouteInterface { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/batch'; } /** * Constructor. */ public function __construct() {} /** * Get arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return array( 'callback' => [ $this, 'get_response' ], 'methods' => 'POST', 'permission_callback' => '__return_true', 'args' => array( 'validation' => array( 'type' => 'string', 'enum' => array( 'require-all-validate', 'normal' ), 'default' => 'normal', ), 'requests' => array( 'required' => true, 'type' => 'array', 'maxItems' => 25, 'items' => array( 'type' => 'object', 'properties' => array( 'method' => array( 'type' => 'string', 'enum' => array( 'POST', 'PUT', 'PATCH', 'DELETE' ), 'default' => 'POST', ), 'path' => array( 'type' => 'string', 'required' => true, ), 'body' => array( 'type' => 'object', 'properties' => array(), 'additionalProperties' => true, ), 'headers' => array( 'type' => 'object', 'properties' => array(), 'additionalProperties' => array( 'type' => array( 'string', 'array' ), 'items' => array( 'type' => 'string', ), ), ), ), ), ), ), ); } /** * Get the route response. * * @see WP_REST_Server::serve_batch_request_v1 * https://developer.wordpress.org/reference/classes/wp_rest_server/serve_batch_request_v1/ * * @throws RouteException On error. * * @param WP_REST_Request $request Request object. * @return WP_REST_Response */ public function get_response( WP_REST_Request $request ) { try { foreach ( $request['requests'] as $args ) { if ( ! stristr( $args['path'], 'wc/store' ) ) { throw new RouteException( 'woocommerce_rest_invalid_path', __( 'Invalid path provided.', 'woocommerce' ), 400 ); } } $response = rest_get_server()->serve_batch_request_v1( $request ); } catch ( RouteException $error ) { $response = $this->get_route_error_response( $error->getErrorCode(), $error->getMessage(), $error->getCode(), $error->getAdditionalData() ); } catch ( \Exception $error ) { $response = $this->get_route_error_response( 'unknown_server_error', $error->getMessage(), 500 ); } if ( is_wp_error( $response ) ) { $response = $this->error_to_response( $response ); } $response->header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) ); $response->header( 'X-WC-Store-API-Nonce-Timestamp', time() ); $response->header( 'X-WC-Store-API-User', get_current_user_id() ); return $response; } } woocommerce-blocks/src/StoreApi/Routes/ProductAttributeTerms.php 0000644 00000003134 15133551764 0021177 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * ProductAttributeTerms class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class ProductAttributeTerms extends AbstractTermsRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/attributes/(?P<attribute_id>[\d]+)/terms'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ 'args' => array( 'attribute_id' => array( 'description' => __( 'Unique identifier for the attribute.', 'woocommerce' ), 'type' => 'integer', ), ), [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => $this->get_collection_params(), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a collection of attribute terms. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $attribute = wc_get_attribute( $request['attribute_id'] ); if ( ! $attribute || ! taxonomy_exists( $attribute->slug ) ) { throw new RouteException( 'woocommerce_rest_taxonomy_invalid', __( 'Attribute does not exist.', 'woocommerce' ), 404 ); } return $this->get_terms_response( $attribute->slug, $request ); } } woocommerce-blocks/src/StoreApi/Routes/CartItems.php 0000644 00000007052 15133551764 0016556 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * CartItems class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class CartItems extends AbstractCartRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/items'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'context' => $this->get_context_param( [ 'default' => 'view' ] ), ], ], [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => array( $this, 'get_response' ), 'permission_callback' => '__return_true', 'args' => $this->schema->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ), ], [ 'methods' => \WP_REST_Server::DELETABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Get a collection of cart items. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $cart_items = $this->cart_controller->get_cart_items(); $items = []; foreach ( $cart_items as $cart_item ) { $data = $this->prepare_item_for_response( $cart_item, $request ); $items[] = $this->prepare_response_for_collection( $data ); } $response = rest_ensure_response( $items ); return $response; } /** * Creates one item from the collection. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { // Do not allow key to be specified during creation. if ( ! empty( $request['key'] ) ) { throw new RouteException( 'woocommerce_rest_cart_item_exists', __( 'Cannot create an existing cart item.', 'woocommerce' ), 400 ); } $result = $this->cart_controller->add_to_cart( [ 'id' => $request['id'], 'quantity' => $request['quantity'], 'variation' => $request['variation'], ] ); $response = rest_ensure_response( $this->prepare_item_for_response( $this->cart_controller->get_cart_item( $result ), $request ) ); $response->set_status( 201 ); return $response; } /** * Deletes all items in the cart. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_delete_response( \WP_REST_Request $request ) { $this->cart_controller->empty_cart(); return new \WP_REST_Response( [], 200 ); } /** * Prepare links for the request. * * @param array $cart_item Object to prepare. * @param \WP_REST_Request $request Request object. * @return array */ protected function prepare_links( $cart_item, $request ) { $base = $this->get_namespace() . $this->get_path(); $links = array( 'self' => array( 'href' => rest_url( trailingslashit( $base ) . $cart_item['key'] ), ), 'collection' => array( 'href' => rest_url( $base ), ), ); return $links; } } woocommerce-blocks/src/StoreApi/Routes/ProductAttributesById.php 0000644 00000003244 15133551764 0021121 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * ProductAttributesById class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class ProductAttributesById extends AbstractRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/attributes/(?P<id>[\d]+)'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ 'args' => array( 'id' => array( 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'integer', ), ), [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view', ) ), ), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a single item. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $object = wc_get_attribute( (int) $request['id'] ); if ( ! $object || 0 === $object->id ) { throw new RouteException( 'woocommerce_rest_attribute_invalid_id', __( 'Invalid attribute ID.', 'woocommerce' ), 404 ); } $data = $this->prepare_item_for_response( $object, $request ); $response = rest_ensure_response( $data ); return $response; } } woocommerce-blocks/src/StoreApi/Routes/CartItemsByKey.php 0000644 00000007454 15133551764 0017530 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * CartItemsByKey class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class CartItemsByKey extends AbstractCartRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/items/(?P<key>[\w-]{32})'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ 'args' => [ 'key' => [ 'description' => __( 'Unique identifier for the item within the cart.', 'woocommerce' ), 'type' => 'string', ], ], [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'context' => $this->get_context_param( [ 'default' => 'view' ] ), ], ], [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => array( $this, 'get_response' ), 'permission_callback' => '__return_true', 'args' => $this->schema->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ), ], [ 'methods' => \WP_REST_Server::DELETABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Get a single cart items. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $cart_item = $this->cart_controller->get_cart_item( $request['key'] ); if ( empty( $cart_item ) ) { throw new RouteException( 'woocommerce_rest_cart_invalid_key', __( 'Cart item does not exist.', 'woocommerce' ), 404 ); } $data = $this->prepare_item_for_response( $cart_item, $request ); $response = rest_ensure_response( $data ); return $response; } /** * Update a single cart item. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_update_response( \WP_REST_Request $request ) { $cart = $this->cart_controller->get_cart_instance(); if ( isset( $request['quantity'] ) ) { $this->cart_controller->set_cart_item_quantity( $request['key'], $request['quantity'] ); } return rest_ensure_response( $this->prepare_item_for_response( $this->cart_controller->get_cart_item( $request['key'] ), $request ) ); } /** * Delete a single cart item. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_delete_response( \WP_REST_Request $request ) { $cart = $this->cart_controller->get_cart_instance(); $cart_item = $this->cart_controller->get_cart_item( $request['key'] ); if ( empty( $cart_item ) ) { throw new RouteException( 'woocommerce_rest_cart_invalid_key', __( 'Cart item does not exist.', 'woocommerce' ), 404 ); } $cart->remove_cart_item( $request['key'] ); return new \WP_REST_Response( null, 204 ); } /** * Prepare links for the request. * * @param array $cart_item Object to prepare. * @param \WP_REST_Request $request Request object. * @return array */ protected function prepare_links( $cart_item, $request ) { $base = $this->get_namespace() . $this->get_path(); $links = array( 'self' => array( 'href' => rest_url( trailingslashit( $base ) . $cart_item['key'] ), ), 'collection' => array( 'href' => rest_url( $base ), ), ); return $links; } } woocommerce-blocks/src/StoreApi/Routes/ProductReviews.php 0000644 00000015020 15133551764 0017642 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; use WP_Comment_Query; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\Pagination; /** * ProductReviews class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class ProductReviews extends AbstractRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/reviews'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => $this->get_collection_params(), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a collection of reviews. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $prepared_args = array( 'type' => 'review', 'status' => 'approve', 'no_found_rows' => false, 'offset' => $request['offset'], 'order' => $request['order'], 'number' => $request['per_page'], 'post__in' => $request['product_id'], ); /** * Map category id to list of product ids. */ if ( ! empty( $request['category_id'] ) ) { $category_ids = $request['category_id']; $child_ids = []; foreach ( $category_ids as $category_id ) { $child_ids = array_merge( $child_ids, get_term_children( $category_id, 'product_cat' ) ); } $category_ids = array_unique( array_merge( $category_ids, $child_ids ) ); $product_ids = get_objects_in_term( $category_ids, 'product_cat' ); $prepared_args['post__in'] = isset( $prepared_args['post__in'] ) ? array_merge( $prepared_args['post__in'], $product_ids ) : $product_ids; } if ( 'rating' === $request['orderby'] ) { $prepared_args['meta_query'] = array( // phpcs:ignore 'relation' => 'OR', array( 'key' => 'rating', 'compare' => 'EXISTS', ), array( 'key' => 'rating', 'compare' => 'NOT EXISTS', ), ); } $prepared_args['orderby'] = $this->normalize_query_param( $request['orderby'] ); if ( empty( $request['offset'] ) ) { $prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 ); } $query = new WP_Comment_Query(); $query_result = $query->query( $prepared_args ); $response_objects = array(); foreach ( $query_result as $review ) { $data = $this->prepare_item_for_response( $review, $request ); $response_objects[] = $this->prepare_response_for_collection( $data ); } $total_reviews = (int) $query->found_comments; $max_pages = (int) $query->max_num_pages; if ( $total_reviews < 1 ) { // Out-of-bounds, run the query again without LIMIT for total count. unset( $prepared_args['number'], $prepared_args['offset'] ); $query = new WP_Comment_Query(); $prepared_args['count'] = true; $total_reviews = $query->query( $prepared_args ); $max_pages = $request['per_page'] ? ceil( $total_reviews / $request['per_page'] ) : 1; } $response = rest_ensure_response( $response_objects ); $response = ( new Pagination() )->add_headers( $response, $request, $total_reviews, $max_pages ); return $response; } /** * Prepends internal property prefix to query parameters to match our response fields. * * @param string $query_param Query parameter. * @return string */ protected function normalize_query_param( $query_param ) { $prefix = 'comment_'; switch ( $query_param ) { case 'id': $normalized = $prefix . 'ID'; break; case 'product': $normalized = $prefix . 'post_ID'; break; case 'rating': $normalized = 'meta_value_num'; break; default: $normalized = $prefix . $query_param; break; } return $normalized; } /** * Get the query params for collections of products. * * @return array */ public function get_collection_params() { $params = array(); $params['context'] = $this->get_context_param(); $params['context']['default'] = 'view'; $params['page'] = array( 'description' => __( 'Current page of the collection.', 'woocommerce' ), 'type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', 'minimum' => 1, ); $params['per_page'] = array( 'description' => __( 'Maximum number of items to be returned in result set. Defaults to no limit if left blank.', 'woocommerce' ), 'type' => 'integer', 'default' => 10, 'minimum' => 0, 'maximum' => 100, 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); $params['offset'] = array( 'description' => __( 'Offset the result set by a specific number of items.', 'woocommerce' ), 'type' => 'integer', 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); $params['order'] = array( 'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ), 'type' => 'string', 'default' => 'desc', 'enum' => array( 'asc', 'desc' ), 'validate_callback' => 'rest_validate_request_arg', ); $params['orderby'] = array( 'description' => __( 'Sort collection by object attribute.', 'woocommerce' ), 'type' => 'string', 'default' => 'date', 'enum' => array( 'date', 'date_gmt', 'id', 'rating', 'product', ), 'validate_callback' => 'rest_validate_request_arg', ); $params['category_id'] = array( 'description' => __( 'Limit result set to reviews from specific category IDs.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'wp_parse_id_list', 'validate_callback' => 'rest_validate_request_arg', ); $params['product_id'] = array( 'description' => __( 'Limit result set to reviews from specific product IDs.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'wp_parse_id_list', 'validate_callback' => 'rest_validate_request_arg', ); return $params; } } woocommerce-blocks/src/StoreApi/Routes/CartCouponsByCode.php 0000644 00000004621 15133551764 0020210 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * CartCouponsByCode class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class CartCouponsByCode extends AbstractCartRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/coupons/(?P<code>[\w-]+)'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ 'args' => [ 'code' => [ 'description' => __( 'Unique identifier for the coupon within the cart.', 'woocommerce' ), 'type' => 'string', ], ], [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'context' => $this->get_context_param( [ 'default' => 'view' ] ), ], ], [ 'methods' => \WP_REST_Server::DELETABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Get a single cart coupon. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { if ( ! $this->cart_controller->has_coupon( $request['code'] ) ) { throw new RouteException( 'woocommerce_rest_cart_coupon_invalid_code', __( 'Coupon does not exist in the cart.', 'woocommerce' ), 404 ); } return $this->prepare_item_for_response( $request['code'], $request ); } /** * Delete a single cart coupon. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_delete_response( \WP_REST_Request $request ) { if ( ! $this->cart_controller->has_coupon( $request['code'] ) ) { throw new RouteException( 'woocommerce_rest_cart_coupon_invalid_code', __( 'Coupon does not exist in the cart.', 'woocommerce' ), 404 ); } $cart = $this->cart_controller->get_cart_instance(); $cart->remove_coupon( $request['code'] ); $cart->calculate_totals(); return new \WP_REST_Response( null, 204 ); } } woocommerce-blocks/src/StoreApi/Routes/ProductCategories.php 0000644 00000002166 15133551764 0020312 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * ProductCategories class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class ProductCategories extends AbstractTermsRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/categories'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => $this->get_collection_params(), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a collection of terms. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { return $this->get_terms_response( 'product_cat', $request ); } } woocommerce-blocks/src/StoreApi/Routes/CartCoupons.php 0000644 00000007221 15133551764 0017121 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * CartCoupons class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class CartCoupons extends AbstractCartRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/coupons'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'context' => $this->get_context_param( [ 'default' => 'view' ] ), ], ], [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => $this->schema->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ), ], [ 'methods' => \WP_REST_Server::DELETABLE, 'permission_callback' => '__return_true', 'callback' => [ $this, 'get_response' ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Get a collection of cart coupons. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $cart_coupons = $this->cart_controller->get_cart_coupons(); $items = []; foreach ( $cart_coupons as $coupon_code ) { $response = rest_ensure_response( $this->schema->get_item_response( $coupon_code ) ); $response->add_links( $this->prepare_links( $coupon_code, $request ) ); $response = $this->prepare_response_for_collection( $response ); $items[] = $response; } $response = rest_ensure_response( $items ); return $response; } /** * Add a coupon to the cart and return the result. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { if ( ! wc_coupons_enabled() ) { throw new RouteException( 'woocommerce_rest_cart_coupon_disabled', __( 'Coupons are disabled.', 'woocommerce' ), 404 ); } try { $this->cart_controller->apply_coupon( $request['code'] ); } catch ( \WC_REST_Exception $e ) { throw new RouteException( $e->getErrorCode(), $e->getMessage(), $e->getCode() ); } $response = $this->prepare_item_for_response( $request['code'], $request ); $response->set_status( 201 ); return $response; } /** * Deletes all coupons in the cart. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_delete_response( \WP_REST_Request $request ) { $cart = $this->cart_controller->get_cart_instance(); $cart->remove_coupons(); $cart->calculate_totals(); return new \WP_REST_Response( [], 200 ); } /** * Prepare links for the request. * * @param string $coupon_code Coupon code. * @param \WP_REST_Request $request Request object. * @return array */ protected function prepare_links( $coupon_code, $request ) { $base = $this->get_namespace() . $this->get_path(); $links = array( 'self' => array( 'href' => rest_url( trailingslashit( $base ) . $coupon_code ), ), 'collection' => array( 'href' => rest_url( $base ), ), ); return $links; } } woocommerce-blocks/src/StoreApi/Routes/CartApplyCoupon.php 0000644 00000003503 15133551764 0017743 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * CartApplyCoupon class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class CartApplyCoupon extends AbstractCartRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/apply-coupon'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'code' => [ 'description' => __( 'Unique identifier for the coupon within the cart.', 'woocommerce' ), 'type' => 'string', ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Handle the request and return a valid response for this endpoint. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { if ( ! wc_coupons_enabled() ) { throw new RouteException( 'woocommerce_rest_cart_coupon_disabled', __( 'Coupons are disabled.', 'woocommerce' ), 404 ); } $cart = $this->cart_controller->get_cart_instance(); $coupon_code = wc_format_coupon_code( wp_unslash( $request['code'] ) ); try { $this->cart_controller->apply_coupon( $coupon_code ); } catch ( \WC_REST_Exception $e ) { throw new RouteException( $e->getErrorCode(), $e->getMessage(), $e->getCode() ); } return rest_ensure_response( $this->schema->get_item_response( $cart ) ); } } woocommerce-blocks/src/StoreApi/Routes/ProductCollectionData.php 0000644 00000014124 15133551764 0021107 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\ProductQueryFilters; /** * ProductCollectionData route. * Get aggregate data from a collection of products. * * Supports the same parameters as /products, but returns a different response. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class ProductCollectionData extends AbstractRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/collection-data'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => $this->get_collection_params(), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a collection of posts and add the post title filter option to \WP_Query. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $data = [ 'min_price' => null, 'max_price' => null, 'attribute_counts' => null, 'stock_status_counts' => null, 'rating_counts' => null, ]; $filters = new ProductQueryFilters(); if ( ! empty( $request['calculate_price_range'] ) ) { $filter_request = clone $request; $filter_request->set_param( 'min_price', null ); $filter_request->set_param( 'max_price', null ); $price_results = $filters->get_filtered_price( $filter_request ); $data['min_price'] = $price_results->min_price; $data['max_price'] = $price_results->max_price; } if ( ! empty( $request['calculate_stock_status_counts'] ) ) { $filter_request = clone $request; $counts = $filters->get_stock_status_counts( $filter_request ); $data['stock_status_counts'] = []; foreach ( $counts as $key => $value ) { $data['stock_status_counts'][] = (object) [ 'status' => $key, 'count' => $value, ]; } } if ( ! empty( $request['calculate_attribute_counts'] ) ) { $taxonomy__or_queries = []; $taxonomy__and_queries = []; foreach ( $request['calculate_attribute_counts'] as $attributes_to_count ) { if ( ! empty( $attributes_to_count['taxonomy'] ) ) { if ( empty( $attributes_to_count['query_type'] ) || 'or' === $attributes_to_count['query_type'] ) { $taxonomy__or_queries[] = $attributes_to_count['taxonomy']; } else { $taxonomy__and_queries[] = $attributes_to_count['taxonomy']; } } } $data['attribute_counts'] = []; // Or type queries need special handling because the attribute, if set, needs removing from the query first otherwise counts would not be correct. if ( $taxonomy__or_queries ) { foreach ( $taxonomy__or_queries as $taxonomy ) { $filter_request = clone $request; $filter_attributes = $filter_request->get_param( 'attributes' ); if ( ! empty( $filter_attributes ) ) { $filter_attributes = array_filter( $filter_attributes, function( $query ) use ( $taxonomy ) { return $query['attribute'] !== $taxonomy; } ); } $filter_request->set_param( 'attributes', $filter_attributes ); $counts = $filters->get_attribute_counts( $filter_request, [ $taxonomy ] ); foreach ( $counts as $key => $value ) { $data['attribute_counts'][] = (object) [ 'term' => $key, 'count' => $value, ]; } } } if ( $taxonomy__and_queries ) { $counts = $filters->get_attribute_counts( $request, $taxonomy__and_queries ); foreach ( $counts as $key => $value ) { $data['attribute_counts'][] = (object) [ 'term' => $key, 'count' => $value, ]; } } } if ( ! empty( $request['calculate_rating_counts'] ) ) { $filter_request = clone $request; $counts = $filters->get_rating_counts( $filter_request ); $data['rating_counts'] = []; foreach ( $counts as $key => $value ) { $data['rating_counts'][] = (object) [ 'rating' => $key, 'count' => $value, ]; } } return rest_ensure_response( $this->schema->get_item_response( $data ) ); } /** * Get the query params for collections of products. * * @return array */ public function get_collection_params() { $params = ( new Products( $this->schema ) )->get_collection_params(); $params['calculate_price_range'] = [ 'description' => __( 'If true, calculates the minimum and maximum product prices for the collection.', 'woocommerce' ), 'type' => 'boolean', 'default' => false, ]; $params['calculate_stock_status_counts'] = [ 'description' => __( 'If true, calculates stock counts for products in the collection.', 'woocommerce' ), 'type' => 'boolean', 'default' => false, ]; $params['calculate_attribute_counts'] = [ 'description' => __( 'If requested, calculates attribute term counts for products in the collection.', 'woocommerce' ), 'type' => 'array', 'items' => [ 'type' => 'object', 'properties' => [ 'taxonomy' => [ 'description' => __( 'Taxonomy name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'query_type' => [ 'description' => __( 'Query type being performed which may affect counts. Valid values include "and" and "or".', 'woocommerce' ), 'type' => 'string', 'enum' => [ 'and', 'or' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], 'default' => [], ]; $params['calculate_rating_counts'] = [ 'description' => __( 'If true, calculates rating counts for products in the collection.', 'woocommerce' ), 'type' => 'boolean', 'default' => false, ]; return $params; } } woocommerce-blocks/src/StoreApi/Routes/CartRemoveItem.php 0000644 00000003321 15133551764 0017544 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * CartRemoveItem class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class CartRemoveItem extends AbstractCartRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/remove-item'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'key' => [ 'description' => __( 'Unique identifier (key) for the cart item.', 'woocommerce' ), 'type' => 'string', ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Handle the request and return a valid response for this endpoint. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { $cart = $this->cart_controller->get_cart_instance(); $cart_item = $this->cart_controller->get_cart_item( $request['key'] ); if ( empty( $cart_item ) ) { throw new RouteException( 'woocommerce_rest_cart_invalid_key', __( 'Cart item no longer exists or is invalid.', 'woocommerce' ), 409 ); } $cart->remove_cart_item( $request['key'] ); $this->maybe_release_stock(); return rest_ensure_response( $this->schema->get_item_response( $cart ) ); } } woocommerce-blocks/src/StoreApi/Routes/Checkout.php 0000644 00000052136 15133551764 0016433 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\InvalidStockLevelsInCartException; use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Domain\Services\CreateAccount; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\AbstractSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartSchema; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\CartController; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\OrderController; use Automattic\WooCommerce\Checkout\Helpers\ReserveStock; use Automattic\WooCommerce\Checkout\Helpers\ReserveStockException; use Automattic\WooCommerce\Blocks\Payments\PaymentResult; use Automattic\WooCommerce\Blocks\Payments\PaymentContext; /** * Checkout class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class Checkout extends AbstractCartRoute { /** * Holds the current order being processed. * * @var \WC_Order */ private $order = null; /** * Order controller class instance. * * @var OrderController */ protected $order_controller; /** * Constructor accepts two types of schema; one for the item being returned, and one for the cart as a whole. These * may be the same depending on the route. * * @param CartSchema $cart_schema Schema class for the cart. * @param AbstractSchema $item_schema Schema class for this route's items if it differs from the cart schema. * @param CartController $cart_controller Cart controller class. * @param OrderController $order_controller Order controller class. */ public function __construct( CartSchema $cart_schema, AbstractSchema $item_schema = null, CartController $cart_controller, OrderController $order_controller ) { $this->schema = is_null( $item_schema ) ? $cart_schema : $item_schema; $this->cart_schema = $cart_schema; $this->cart_controller = $cart_controller; $this->order_controller = $order_controller; } /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/checkout'; } /** * Checks if a nonce is required for the route. * * @param \WP_REST_Request $request Request. * @return bool */ protected function requires_nonce( \WP_REST_Request $request ) { return true; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'context' => $this->get_context_param( [ 'default' => 'view' ] ), ], ], [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => array_merge( [ 'payment_data' => [ 'description' => __( 'Data to pass through to the payment method when processing payment.', 'woocommerce' ), 'type' => 'array', 'items' => [ 'type' => 'object', 'properties' => [ 'key' => [ 'type' => 'string', ], 'value' => [ 'type' => [ 'string', 'boolean' ], ], ], ], ], ], $this->schema->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ) ), ], [ 'methods' => \WP_REST_Server::EDITABLE, 'callback' => array( $this, 'get_response' ), 'permission_callback' => '__return_true', 'args' => $this->schema->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Prepare a single item for response. Handles setting the status based on the payment result. * * @param mixed $item Item to format to schema. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response $response Response data. */ public function prepare_item_for_response( $item, \WP_REST_Request $request ) { $response = parent::prepare_item_for_response( $item, $request ); $status_codes = [ 'success' => 200, 'pending' => 202, 'failure' => 400, 'error' => 500, ]; if ( isset( $item->payment_result ) && $item->payment_result instanceof PaymentResult ) { $response->set_status( $status_codes[ $item->payment_result->status ] ?? 200 ); } return $response; } /** * Convert the cart into a new draft order, or update an existing draft order, and return an updated cart response. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $this->create_or_update_draft_order(); return $this->prepare_item_for_response( (object) [ 'order' => $this->order, 'payment_result' => new PaymentResult(), ], $request ); } /** * Update the current order. * * @internal Customer data is updated first so OrderController::update_addresses_from_cart uses up to date data. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_update_response( \WP_REST_Request $request ) { $this->update_customer_from_request( $request ); $this->create_or_update_draft_order(); $this->update_order_from_request( $request ); return $this->prepare_item_for_response( (object) [ 'order' => $this->order, 'payment_result' => new PaymentResult(), ], $request ); } /** * Update and process an order. * * 1. Obtain Draft Order * 2. Process Request * 3. Process Customer * 4. Validate Order * 5. Process Payment * * @throws RouteException On error. * @throws InvalidStockLevelsInCartException On error. * * @param \WP_REST_Request $request Request object. * * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { /** * Validate items etc are allowed in the order before the order is processed. This will fix violations and tell * the customer. */ $this->cart_controller->validate_cart_items(); $this->cart_controller->validate_cart_coupons(); /** * Obtain Draft Order and process request data. * * Note: Customer data is persisted from the request first so that OrderController::update_addresses_from_cart * uses the up to date customer address. */ $this->update_customer_from_request( $request ); $this->create_or_update_draft_order(); $this->update_order_from_request( $request ); /** * Process customer data. * * Update order with customer details, and sign up a user account as necessary. */ $this->process_customer( $request ); /** * Validate order. * * This logic ensures the order is valid before payment is attempted. */ $this->order_controller->validate_order_before_payment( $this->order ); /** * WooCommerce Blocks Checkout Order Processed (experimental). * * This hook informs extensions that $order has completed processing and is ready for payment. * * This is similar to existing core hook woocommerce_checkout_order_processed. We're using a new action: * - To keep the interface focused (only pass $order, not passing request data). * - This also explicitly indicates these orders are from checkout block/StoreAPI. * * @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3238 * @internal This Hook is experimental and may change or be removed. * * @param \WC_Order $order Order object. */ do_action( '__experimental_woocommerce_blocks_checkout_order_processed', $this->order ); /** * Process the payment and return the results. */ $payment_result = new PaymentResult(); if ( $this->order->needs_payment() ) { $this->process_payment( $request, $payment_result ); } else { $this->process_without_payment( $request, $payment_result ); } return $this->prepare_item_for_response( (object) [ 'order' => wc_get_order( $this->order ), 'payment_result' => $payment_result, ], $request ); } /** * Get route response when something went wrong. * * @param string $error_code String based error code. * @param string $error_message User facing error message. * @param int $http_status_code HTTP status. Defaults to 500. * @param array $additional_data Extra data (key value pairs) to expose in the error response. * @return \WP_Error WP Error object. */ protected function get_route_error_response( $error_code, $error_message, $http_status_code = 500, $additional_data = [] ) { $error_from_message = new \WP_Error( $error_code, $error_message ); // 409 is when there was a conflict, so we return the cart so the client can resolve it. if ( 409 === $http_status_code ) { return $this->add_data_to_error_object( $error_from_message, $additional_data, $http_status_code, true ); } return $this->add_data_to_error_object( $error_from_message, $additional_data, $http_status_code ); } /** * Get route response when something went wrong. * * @param \WP_Error $error_object User facing error message. * @param int $http_status_code HTTP status. Defaults to 500. * @param array $additional_data Extra data (key value pairs) to expose in the error response. * @return \WP_Error WP Error object. */ protected function get_route_error_response_from_object( $error_object, $http_status_code = 500, $additional_data = [] ) { // 409 is when there was a conflict, so we return the cart so the client can resolve it. if ( 409 === $http_status_code ) { return $this->add_data_to_error_object( $error_object, $additional_data, $http_status_code, true ); } return $this->add_data_to_error_object( $error_object, $additional_data, $http_status_code ); } /** * Adds additional data to the \WP_Error object. * * @param \WP_Error $error The error object to add the cart to. * @param array $data The data to add to the error object. * @param int $http_status_code The HTTP status code this error should return. * @param bool $include_cart Whether the cart should be included in the error data. * @returns \WP_Error The \WP_Error with the cart added. */ private function add_data_to_error_object( $error, $data, $http_status_code, bool $include_cart = false ) { $data = array_merge( $data, [ 'status' => $http_status_code ] ); if ( $include_cart ) { $data = array_merge( $data, [ 'cart' => wc()->api->get_endpoint_data( '/wc/store/cart' ) ] ); } $error->add_data( $data ); return $error; } /** * Gets draft order data from the customer session. * * @return array */ private function get_draft_order_id() { return wc()->session->get( 'store_api_draft_order', 0 ); } /** * Updates draft order data in the customer session. * * @param integer $order_id Draft order ID. */ private function set_draft_order_id( $order_id ) { wc()->session->set( 'store_api_draft_order', $order_id ); } /** * Whether the passed argument is a draft order or an order that is * pending/failed and the cart hasn't changed. * * @param \WC_Order $order_object Order object to check. * @return boolean Whether the order is valid as a draft order. */ private function is_valid_draft_order( $order_object ) { if ( ! $order_object instanceof \WC_Order ) { return false; } // Draft orders are okay. if ( $order_object->has_status( 'checkout-draft' ) ) { return true; } // Pending and failed orders can be retried if the cart hasn't changed. if ( $order_object->needs_payment() && $order_object->has_cart_hash( wc()->cart->get_cart_hash() ) ) { return true; } return false; } /** * Create or update a draft order based on the cart. * * @throws RouteException On error. */ private function create_or_update_draft_order() { $this->order = $this->get_draft_order_id() ? wc_get_order( $this->get_draft_order_id() ) : null; if ( ! $this->is_valid_draft_order( $this->order ) ) { $this->order = $this->order_controller->create_order_from_cart(); } else { $this->order_controller->update_order_from_cart( $this->order ); } /** * WooCommerce Blocks Checkout Update Order Meta (experimental). * * This hook gives extensions the chance to add or update meta data on the $order. * * This is similar to existing core hook woocommerce_checkout_update_order_meta. * We're using a new action: * - To keep the interface focused (only pass $order, not passing request data). * - This also explicitly indicates these orders are from checkout block/StoreAPI. * * @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3686 * @internal This Hook is experimental and may change or be removed. * * @param \WC_Order $order Order object. */ do_action( '__experimental_woocommerce_blocks_checkout_update_order_meta', $this->order ); // Confirm order is valid before proceeding further. if ( ! $this->order instanceof \WC_Order ) { throw new RouteException( 'woocommerce_rest_checkout_missing_order', __( 'Unable to create order', 'woocommerce' ), 500 ); } // Store order ID to session. $this->set_draft_order_id( $this->order->get_id() ); // Try to reserve stock for 10 mins, if available. try { $reserve_stock = new ReserveStock(); $reserve_stock->reserve_stock_for_order( $this->order, 10 ); } catch ( ReserveStockException $e ) { $error_data = $e->getErrorData(); throw new RouteException( $e->getErrorCode(), $e->getMessage(), $e->getCode() ); } } /** * Updates the current customer session using data from the request (e.g. address data). * * Address session data is synced to the order itself later on by OrderController::update_order_from_cart() * * @param \WP_REST_Request $request Full details about the request. */ private function update_customer_from_request( \WP_REST_Request $request ) { $customer = wc()->customer; if ( isset( $request['billing_address'] ) ) { foreach ( $request['billing_address'] as $key => $value ) { if ( is_callable( [ $customer, "set_billing_$key" ] ) ) { $customer->{"set_billing_$key"}( $value ); } } } if ( isset( $request['shipping_address'] ) ) { foreach ( $request['shipping_address'] as $key => $value ) { if ( is_callable( [ $customer, "set_shipping_$key" ] ) ) { $customer->{"set_shipping_$key"}( $value ); } elseif ( 'phone' === $key ) { $customer->update_meta_data( 'shipping_phone', $value ); } } } $customer->save(); } /** * Update the current order using the posted values from the request. * * @param \WP_REST_Request $request Full details about the request. */ private function update_order_from_request( \WP_REST_Request $request ) { $this->order->set_customer_note( $request['customer_note'] ?? '' ); $this->order->set_payment_method( $this->get_request_payment_method( $request ) ); /** * WooCommerce Blocks Checkout Update Order From Request (experimental). * * This hook gives extensions the chance to update orders based on the data in the request. This can be used in * conjunction with the ExtendRestAPI class to post custom data and then process it. * * @internal This Hook is experimental and may change or be removed. * * @param \WC_Order $order Order object. * @param \WP_REST_Request $request Full details about the request. */ do_action( '__experimental_woocommerce_blocks_checkout_update_order_from_request', $this->order, $request ); $this->order->save(); } /** * For orders which do not require payment, just update status. * * @param \WP_REST_Request $request Request object. * @param PaymentResult $payment_result Payment result object. */ private function process_without_payment( \WP_REST_Request $request, PaymentResult $payment_result ) { // Transition the order to pending, and then completed. This ensures transactional emails fire for pending_to_complete events. $this->order->update_status( 'pending' ); $this->order->payment_complete(); // Mark the payment as successful. $payment_result->set_status( 'success' ); $payment_result->set_redirect_url( $this->order->get_checkout_order_received_url() ); } /** * Fires an action hook instructing active payment gateways to process the payment for an order and provide a result. * * @throws RouteException On error. * * @param \WP_REST_Request $request Request object. * @param PaymentResult $payment_result Payment result object. */ private function process_payment( \WP_REST_Request $request, PaymentResult $payment_result ) { try { // Transition the order to pending before making payment. $this->order->update_status( 'pending' ); // Prepare the payment context object to pass through payment hooks. $context = new PaymentContext(); $context->set_payment_method( $this->get_request_payment_method_id( $request ) ); $context->set_payment_data( $this->get_request_payment_data( $request ) ); $context->set_order( $this->order ); /** * Process payment with context. * * @hook woocommerce_rest_checkout_process_payment_with_context * * @throws \Exception If there is an error taking payment, an \Exception object can be thrown with an error message. * * @param PaymentContext $context Holds context for the payment, including order ID and payment method. * @param PaymentResult $payment_result Result object for the transaction. */ do_action_ref_array( 'woocommerce_rest_checkout_process_payment_with_context', [ $context, &$payment_result ] ); if ( ! $payment_result instanceof PaymentResult ) { throw new RouteException( 'woocommerce_rest_checkout_invalid_payment_result', __( 'Invalid payment result received from payment method.', 'woocommerce' ), 500 ); } } catch ( \Exception $e ) { throw new RouteException( 'woocommerce_rest_checkout_process_payment_error', $e->getMessage(), 400 ); } } /** * Gets the chosen payment method ID from the request. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return string */ private function get_request_payment_method_id( \WP_REST_Request $request ) { $payment_method_id = wc_clean( wp_unslash( $request['payment_method'] ?? '' ) ); if ( empty( $payment_method_id ) ) { throw new RouteException( 'woocommerce_rest_checkout_missing_payment_method', __( 'No payment method provided.', 'woocommerce' ), 400 ); } return $payment_method_id; } /** * Gets the chosen payment method from the request. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WC_Payment_Gateway */ private function get_request_payment_method( \WP_REST_Request $request ) { $payment_method_id = $this->get_request_payment_method_id( $request ); $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); if ( ! isset( $available_gateways[ $payment_method_id ] ) ) { throw new RouteException( 'woocommerce_rest_checkout_payment_method_disabled', __( 'This payment gateway is not available.', 'woocommerce' ), 400 ); } return $available_gateways[ $payment_method_id ]; } /** * Gets and formats payment request data. * * @param \WP_REST_Request $request Request object. * @return array */ private function get_request_payment_data( \WP_REST_Request $request ) { static $payment_data = []; if ( ! empty( $payment_data ) ) { return $payment_data; } if ( ! empty( $request['payment_data'] ) ) { foreach ( $request['payment_data'] as $data ) { $payment_data[ sanitize_key( $data['key'] ) ] = wc_clean( $data['value'] ); } } return $payment_data; } /** * Order processing relating to customer account. * * Creates a customer account as needed (based on request & store settings) and updates the order with the new customer ID. * Updates the order with user details (e.g. address). * * @throws RouteException API error object with error details. * @param \WP_REST_Request $request Request object. */ private function process_customer( \WP_REST_Request $request ) { try { $create_account = Package::container()->get( CreateAccount::class ); $create_account->from_order_request( $request ); $this->order->set_customer_id( get_current_user_id() ); $this->order->save(); } catch ( \Exception $error ) { switch ( $error->getMessage() ) { case 'registration-error-invalid-email': throw new RouteException( 'registration-error-invalid-email', __( 'Please provide a valid email address.', 'woocommerce' ), 400 ); case 'registration-error-email-exists': throw new RouteException( 'registration-error-email-exists', __( 'An account is already registered with your email address. Please log in before proceeding.', 'woocommerce' ), 400 ); } } // Persist customer address data to account. $this->order_controller->sync_customer_data_with_order( $this->order ); } } woocommerce-blocks/src/StoreApi/Routes/CartUpdateItem.php 0000644 00000003262 15133551764 0017535 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * CartUpdateItem class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class CartUpdateItem extends AbstractCartRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/update-item'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'key' => [ 'description' => __( 'Unique identifier (key) for the cart item to update.', 'woocommerce' ), 'type' => 'string', ], 'quantity' => [ 'description' => __( 'New quantity of the item in the cart.', 'woocommerce' ), 'type' => 'integer', ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Handle the request and return a valid response for this endpoint. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { $cart = $this->cart_controller->get_cart_instance(); if ( isset( $request['quantity'] ) ) { $this->cart_controller->set_cart_item_quantity( $request['key'], $request['quantity'] ); } return rest_ensure_response( $this->schema->get_item_response( $cart ) ); } } woocommerce-blocks/src/StoreApi/Routes/CartAddItem.php 0000644 00000005604 15133551764 0017005 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * CartAddItem class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class CartAddItem extends AbstractCartRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/add-item'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'id' => [ 'description' => __( 'The cart item product or variation ID.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'arg_options' => [ 'sanitize_callback' => 'absint', ], ], 'quantity' => [ 'description' => __( 'Quantity of this item in the cart.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'arg_options' => [ 'sanitize_callback' => 'wc_stock_amount', ], ], 'variation' => [ 'description' => __( 'Chosen attributes (for variations).', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => [ 'attribute' => [ 'description' => __( 'Variation attribute name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'value' => [ 'description' => __( 'Variation attribute value.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], ], ], ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Handle the request and return a valid response for this endpoint. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { // Do not allow key to be specified during creation. if ( ! empty( $request['key'] ) ) { throw new RouteException( 'woocommerce_rest_cart_item_exists', __( 'Cannot create an existing cart item.', 'woocommerce' ), 400 ); } $cart = $this->cart_controller->get_cart_instance(); $result = $this->cart_controller->add_to_cart( [ 'id' => $request['id'], 'quantity' => $request['quantity'], 'variation' => $request['variation'], ] ); $response = rest_ensure_response( $this->schema->get_item_response( $cart ) ); $response->set_status( 201 ); return $response; } } woocommerce-blocks/src/StoreApi/Routes/ProductCategoriesById.php 0000644 00000003213 15133551764 0021054 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * ProductCategoriesById class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class ProductCategoriesById extends AbstractRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/categories/(?P<id>[\d]+)'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ 'args' => array( 'id' => array( 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'integer', ), ), [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view', ) ), ), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a single item. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $object = get_term( (int) $request['id'], 'product_cat' ); if ( ! $object || 0 === $object->id ) { throw new RouteException( 'woocommerce_rest_category_invalid_id', __( 'Invalid category ID.', 'woocommerce' ), 404 ); } $data = $this->prepare_item_for_response( $object, $request ); return rest_ensure_response( $data ); } } woocommerce-blocks/src/StoreApi/Routes/AbstractCartRoute.php 0000644 00000013270 15133551764 0020256 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\CartController; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\AbstractSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartSchema; /** * Abstract Cart Route * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ abstract class AbstractCartRoute extends AbstractRoute { /** * Schema class for this route's response. * * @var AbstractSchema|CartSchema */ protected $schema; /** * Schema class for the cart. * * @var CartSchema */ protected $cart_schema; /** * Cart controller class instance. * * @var CartController */ protected $cart_controller; /** * Constructor accepts two types of schema; one for the item being returned, and one for the cart as a whole. These * may be the same depending on the route. * * @param CartSchema $cart_schema Schema class for the cart. * @param AbstractSchema $item_schema Schema class for this route's items if it differs from the cart schema. * @param CartController $cart_controller Cart controller class. */ public function __construct( CartSchema $cart_schema, AbstractSchema $item_schema = null, CartController $cart_controller ) { $this->schema = is_null( $item_schema ) ? $cart_schema : $item_schema; $this->cart_schema = $cart_schema; $this->cart_controller = $cart_controller; } /** * Get the route response based on the type of request. * * @param \WP_REST_Request $request Request object. * @return \WP_Error|\WP_REST_Response */ public function get_response( \WP_REST_Request $request ) { $this->cart_controller->load_cart(); $this->calculate_totals(); if ( $this->requires_nonce( $request ) ) { $nonce_check = $this->check_nonce( $request ); if ( is_wp_error( $nonce_check ) ) { return $this->add_nonce_headers( $this->error_to_response( $nonce_check ) ); } } try { $response = parent::get_response( $request ); } catch ( RouteException $error ) { $response = $this->get_route_error_response( $error->getErrorCode(), $error->getMessage(), $error->getCode(), $error->getAdditionalData() ); } catch ( \Exception $error ) { $response = $this->get_route_error_response( 'unknown_server_error', $error->getMessage(), 500 ); } if ( is_wp_error( $response ) ) { $response = $this->error_to_response( $response ); } return $this->add_nonce_headers( $response ); } /** * Add nonce headers to a response object. * * @param \WP_REST_Response $response The response object. * @return \WP_REST_Response */ protected function add_nonce_headers( \WP_REST_Response $response ) { $response->header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) ); $response->header( 'X-WC-Store-API-Nonce-Timestamp', time() ); $response->header( 'X-WC-Store-API-User', get_current_user_id() ); return $response; } /** * Checks if a nonce is required for the route. * * @param \WP_REST_Request $request Request. * @return bool */ protected function requires_nonce( \WP_REST_Request $request ) { return 'GET' !== $request->get_method(); } /** * Ensures the cart totals are calculated before an API response is generated. */ protected function calculate_totals() { wc()->cart->get_cart(); wc()->cart->calculate_fees(); wc()->cart->calculate_shipping(); wc()->cart->calculate_totals(); } /** * If there is a draft order, releases stock. * * @return void */ protected function maybe_release_stock() { $draft_order = wc()->session->get( 'store_api_draft_order', 0 ); if ( ! $draft_order ) { return; } wc_release_stock_for_order( $draft_order ); } /** * For non-GET endpoints, require and validate a nonce to prevent CSRF attacks. * * Nonces will mismatch if the logged in session cookie is different! If using a client to test, set this cookie * to match the logged in cookie in your browser. * * @param \WP_REST_Request $request Request object. * @return \WP_Error|boolean */ protected function check_nonce( \WP_REST_Request $request ) { $nonce = $request->get_header( 'X-WC-Store-API-Nonce' ); if ( apply_filters( 'woocommerce_store_api_disable_nonce_check', false ) ) { return true; } if ( null === $nonce ) { return $this->get_route_error_response( 'woocommerce_rest_missing_nonce', __( 'Missing the X-WC-Store-API-Nonce header. This endpoint requires a valid nonce.', 'woocommerce' ), 401 ); } if ( ! wp_verify_nonce( $nonce, 'wc_store_api' ) ) { return $this->get_route_error_response( 'woocommerce_rest_invalid_nonce', __( 'X-WC-Store-API-Nonce is invalid.', 'woocommerce' ), 403 ); } return true; } /** * Get route response when something went wrong. * * @param string $error_code String based error code. * @param string $error_message User facing error message. * @param int $http_status_code HTTP status. Defaults to 500. * @param array $additional_data Extra data (key value pairs) to expose in the error response. * @return \WP_Error WP Error object. */ protected function get_route_error_response( $error_code, $error_message, $http_status_code = 500, $additional_data = [] ) { switch ( $http_status_code ) { case 409: // If there was a conflict, return the cart so the client can resolve it. $cart = $this->cart_controller->get_cart_instance(); return new \WP_Error( $error_code, $error_message, array_merge( $additional_data, [ 'status' => $http_status_code, 'cart' => $this->cart_schema->get_item_response( $cart ), ] ) ); } return new \WP_Error( $error_code, $error_message, [ 'status' => $http_status_code ] ); } } woocommerce-blocks/src/StoreApi/Routes/ProductAttributes.php 0000644 00000002556 15133551764 0020356 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * ProductAttributes class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class ProductAttributes extends AbstractRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/attributes'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => $this->get_collection_params(), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a collection of attributes. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $ids = wc_get_attribute_taxonomy_ids(); $return = []; foreach ( $ids as $id ) { $object = wc_get_attribute( $id ); $data = $this->prepare_item_for_response( $object, $request ); $return[] = $this->prepare_response_for_collection( $data ); } return rest_ensure_response( $return ); } } woocommerce-blocks/src/StoreApi/Routes/CartRemoveCoupon.php 0000644 00000004341 15133551764 0020114 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * CartRemoveCoupon class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class CartRemoveCoupon extends AbstractCartRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/remove-coupon'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'code' => [ 'description' => __( 'Unique identifier for the coupon within the cart.', 'woocommerce' ), 'type' => 'string', ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Handle the request and return a valid response for this endpoint. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { if ( ! wc_coupons_enabled() ) { throw new RouteException( 'woocommerce_rest_cart_coupon_disabled', __( 'Coupons are disabled.', 'woocommerce' ), 404 ); } $cart = $this->cart_controller->get_cart_instance(); $coupon_code = wc_format_coupon_code( $request['code'] ); $coupon = new \WC_Coupon( $coupon_code ); if ( $coupon->get_code() !== $coupon_code || ! $coupon->is_valid() ) { throw new RouteException( 'woocommerce_rest_cart_coupon_error', __( 'Invalid coupon code.', 'woocommerce' ), 400 ); } if ( ! $this->cart_controller->has_coupon( $coupon_code ) ) { throw new RouteException( 'woocommerce_rest_cart_coupon_invalid_code', __( 'Coupon cannot be removed because it is not already applied to the cart.', 'woocommerce' ), 409 ); } $cart = $this->cart_controller->get_cart_instance(); $cart->remove_coupon( $coupon_code ); $cart->calculate_totals(); return rest_ensure_response( $this->schema->get_item_response( $cart ) ); } } woocommerce-blocks/src/StoreApi/Routes/RouteInterface.php 0000644 00000001055 15133551764 0017577 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * RouteInterface. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ interface RouteInterface { /** * Get the namespace for this route. * * @return string */ public function get_namespace(); /** * Get the path of this REST route. * * @return string */ public function get_path(); /** * Get arguments for this REST route. * * @return array An array of endpoints. */ public function get_args(); } woocommerce-blocks/src/StoreApi/Routes/ProductTags.php 0000644 00000002144 15133551764 0017117 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * ProductTags class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class ProductTags extends AbstractTermsRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/tags'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => $this->get_collection_params(), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a collection of terms. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { return $this->get_terms_response( 'product_tag', $request ); } } woocommerce-blocks/src/StoreApi/Routes/AbstractRoute.php 0000644 00000021101 15133551764 0017434 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\AbstractSchema; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\InvalidStockLevelsInCartException; use WP_Error; /** * AbstractRoute class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ abstract class AbstractRoute implements RouteInterface { /** * Schema class instance. * * @var AbstractSchema */ protected $schema; /** * Constructor. * * @param AbstractSchema $schema Schema class for this route. */ public function __construct( AbstractSchema $schema ) { $this->schema = $schema; } /** * Get the namespace for this route. * * @return string */ public function get_namespace() { return 'wc/store'; } /** * Get item schema properties. * * @return array */ public function get_item_schema() { return $this->schema->get_item_schema(); } /** * Get the route response based on the type of request. * * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ public function get_response( \WP_REST_Request $request ) { $response = null; try { switch ( $request->get_method() ) { case 'POST': $response = $this->get_route_post_response( $request ); break; case 'PUT': case 'PATCH': $response = $this->get_route_update_response( $request ); break; case 'DELETE': $response = $this->get_route_delete_response( $request ); break; default: $response = $this->get_route_response( $request ); break; } } catch ( RouteException $error ) { $response = $this->get_route_error_response( $error->getErrorCode(), $error->getMessage(), $error->getCode(), $error->getAdditionalData() ); } catch ( InvalidStockLevelsInCartException $error ) { $response = $this->get_route_error_response_from_object( $error->getError(), $error->getCode(), $error->getAdditionalData() ); } catch ( \Exception $error ) { $response = $this->get_route_error_response( 'unknown_server_error', $error->getMessage(), 500 ); } if ( is_wp_error( $response ) ) { $response = $this->error_to_response( $response ); } return $response; } /** * Converts an error to a response object. Based on \WP_REST_Server. * * @param WP_Error $error WP_Error instance. * @return WP_REST_Response List of associative arrays with code and message keys. */ protected function error_to_response( $error ) { $error_data = $error->get_error_data(); $status = isset( $error_data, $error_data['status'] ) ? $error_data['status'] : 500; $errors = []; foreach ( (array) $error->errors as $code => $messages ) { foreach ( (array) $messages as $message ) { $errors[] = array( 'code' => $code, 'message' => $message, 'data' => $error->get_error_data( $code ), ); } } $data = array_shift( $errors ); if ( count( $errors ) ) { $data['additional_errors'] = $errors; } return new \WP_REST_Response( $data, $status ); } /** * Get route response for GET requests. * * When implemented, should return a \WP_REST_Response. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. */ protected function get_route_response( \WP_REST_Request $request ) { throw new RouteException( 'woocommerce_rest_invalid_endpoint', __( 'Method not implemented', 'woocommerce' ), 404 ); } /** * Get route response for POST requests. * * When implemented, should return a \WP_REST_Response. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. */ protected function get_route_post_response( \WP_REST_Request $request ) { throw new RouteException( 'woocommerce_rest_invalid_endpoint', __( 'Method not implemented', 'woocommerce' ), 404 ); } /** * Get route response for PUT requests. * * When implemented, should return a \WP_REST_Response. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. */ protected function get_route_update_response( \WP_REST_Request $request ) { throw new RouteException( 'woocommerce_rest_invalid_endpoint', __( 'Method not implemented', 'woocommerce' ), 404 ); } /** * Get route response for DELETE requests. * * When implemented, should return a \WP_REST_Response. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. */ protected function get_route_delete_response( \WP_REST_Request $request ) { throw new RouteException( 'woocommerce_rest_invalid_endpoint', __( 'Method not implemented', 'woocommerce' ), 404 ); } /** * Get route response when something went wrong. * * @param string $error_code String based error code. * @param string $error_message User facing error message. * @param int $http_status_code HTTP status. Defaults to 500. * @param array $additional_data Extra data (key value pairs) to expose in the error response. * @return \WP_Error WP Error object. */ protected function get_route_error_response( $error_code, $error_message, $http_status_code = 500, $additional_data = [] ) { return new \WP_Error( $error_code, $error_message, array_merge( $additional_data, [ 'status' => $http_status_code ] ) ); } /** * Get route response when something went wrong and the supplied error is a WP_Error. This currently only happens * when an item in the cart is out of stock, partially out of stock, can only be bought individually, or when the * item is not purchasable. * * @param WP_Error $error_object The WP_Error object containing the error. * @param int $http_status_code HTTP status. Defaults to 500. * @param array $additional_data Extra data (key value pairs) to expose in the error response. * @return WP_Error WP Error object. */ protected function get_route_error_response_from_object( $error_object, $http_status_code = 500, $additional_data = [] ) { $error_object->add_data( array_merge( $additional_data, [ 'status' => $http_status_code ] ) ); return $error_object; } /** * Prepare a single item for response. * * @param mixed $item Item to format to schema. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response $response Response data. */ public function prepare_item_for_response( $item, \WP_REST_Request $request ) { $response = rest_ensure_response( $this->schema->get_item_response( $item ) ); $response->add_links( $this->prepare_links( $item, $request ) ); return $response; } /** * Retrieves the context param. * * Ensures consistent descriptions between endpoints, and populates enum from schema. * * @param array $args Optional. Additional arguments for context parameter. Default empty array. * @return array Context parameter details. */ protected function get_context_param( $args = array() ) { $param_details = array( 'description' => __( 'Scope under which the request is made; determines fields present in response.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_key', 'validate_callback' => 'rest_validate_request_arg', ); $schema = $this->get_item_schema(); if ( empty( $schema['properties'] ) ) { return array_merge( $param_details, $args ); } $contexts = array(); foreach ( $schema['properties'] as $attributes ) { if ( ! empty( $attributes['context'] ) ) { $contexts = array_merge( $contexts, $attributes['context'] ); } } if ( ! empty( $contexts ) ) { $param_details['enum'] = array_unique( $contexts ); rsort( $param_details['enum'] ); } return array_merge( $param_details, $args ); } /** * Prepares a response for insertion into a collection. * * @param \WP_REST_Response $response Response object. * @return array|mixed Response data, ready for insertion into collection data. */ protected function prepare_response_for_collection( \WP_REST_Response $response ) { $data = (array) $response->get_data(); $server = rest_get_server(); $links = $server::get_compact_response_links( $response ); if ( ! empty( $links ) ) { $data['_links'] = $links; } return $data; } /** * Prepare links for the request. * * @param mixed $item Item to prepare. * @param \WP_REST_Request $request Request object. * @return array */ protected function prepare_links( $item, $request ) { return []; } /** * Retrieves the query params for the collections. * * @return array Query parameters for the collection. */ public function get_collection_params() { return array( 'context' => $this->get_context_param(), ); } } woocommerce-blocks/src/StoreApi/Routes/RouteException.php 0000644 00000002575 15133551764 0017645 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * ReserveStockRouteExceptionException class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class RouteException extends \Exception { /** * Sanitized error code. * * @var string */ public $error_code; /** * Additional error data. * * @var array */ public $additional_data = []; /** * Setup exception. * * @param string $error_code Machine-readable error code, e.g `woocommerce_invalid_product_id`. * @param string $message User-friendly translated error message, e.g. 'Product ID is invalid'. * @param int $http_status_code Proper HTTP status code to respond with, e.g. 400. * @param array $additional_data Extra data (key value pairs) to expose in the error response. */ public function __construct( $error_code, $message, $http_status_code = 400, $additional_data = [] ) { $this->error_code = $error_code; $this->additional_data = array_filter( (array) $additional_data ); parent::__construct( $message, $http_status_code ); } /** * Returns the error code. * * @return string */ public function getErrorCode() { return $this->error_code; } /** * Returns additional error data. * * @return array */ public function getAdditionalData() { return $this->additional_data; } } woocommerce-blocks/src/StoreApi/Routes/Cart.php 0000644 00000002326 15133551764 0015553 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * Cart class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class Cart extends AbstractCartRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'context' => $this->get_context_param( [ 'default' => 'view' ] ), ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Handle the request and return a valid response for this endpoint. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { return rest_ensure_response( $this->schema->get_item_response( $this->cart_controller->get_cart_instance() ) ); } } woocommerce-blocks/src/StoreApi/Routes/CartSelectShippingRate.php 0000644 00000004543 15133551764 0021234 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * CartSelectShippingRate class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class CartSelectShippingRate extends AbstractCartRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/select-shipping-rate'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'package_id' => array( 'description' => __( 'The ID of the package being shipped.', 'woocommerce' ), 'type' => [ 'integer', 'string' ], 'required' => true, ), 'rate_id' => [ 'description' => __( 'The chosen rate ID for the package.', 'woocommerce' ), 'type' => 'string', 'required' => true, ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Handle the request and return a valid response for this endpoint. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { if ( ! wc_shipping_enabled() ) { throw new RouteException( 'woocommerce_rest_shipping_disabled', __( 'Shipping is disabled.', 'woocommerce' ), 404 ); } if ( ! isset( $request['package_id'] ) ) { throw new RouteException( 'woocommerce_rest_cart_missing_package_id', __( 'Invalid Package ID.', 'woocommerce' ), 400 ); } $cart = $this->cart_controller->get_cart_instance(); $package_id = wc_clean( wp_unslash( $request['package_id'] ) ); $rate_id = wc_clean( wp_unslash( $request['rate_id'] ) ); try { $this->cart_controller->select_shipping_rate( $package_id, $rate_id ); } catch ( \WC_Rest_Exception $e ) { throw new RouteException( $e->getErrorCode(), $e->getMessage(), $e->getCode() ); } $cart->calculate_shipping(); $cart->calculate_totals(); return rest_ensure_response( $this->schema->get_item_response( $cart ) ); } } woocommerce-blocks/src/StoreApi/Routes/AbstractTermsRoute.php 0000644 00000011352 15133551764 0020456 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\Pagination; use WP_Term_Query; /** * AbstractTermsRoute class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ abstract class AbstractTermsRoute extends AbstractRoute { /** * Get the query params for collections of attributes. * * @return array */ public function get_collection_params() { $params = array(); $params['context'] = $this->get_context_param(); $params['context']['default'] = 'view'; $params['page'] = array( 'description' => __( 'Current page of the collection.', 'woocommerce' ), 'type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', 'minimum' => 1, ); $params['per_page'] = array( 'description' => __( 'Maximum number of items to be returned in result set. Defaults to no limit if left blank.', 'woocommerce' ), 'type' => 'integer', 'minimum' => 0, 'maximum' => 100, 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); $params['search'] = array( 'description' => __( 'Limit results to those matching a string.', 'woocommerce' ), 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => 'rest_validate_request_arg', ); $params['exclude'] = array( 'description' => __( 'Ensure result set excludes specific IDs.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), 'sanitize_callback' => 'wp_parse_id_list', ); $params['include'] = array( 'description' => __( 'Limit result set to specific ids.', 'woocommerce' ), 'type' => 'array', 'items' => array( 'type' => 'integer', ), 'default' => array(), 'sanitize_callback' => 'wp_parse_id_list', ); $params['order'] = array( 'description' => __( 'Sort ascending or descending.', 'woocommerce' ), 'type' => 'string', 'default' => 'asc', 'enum' => array( 'asc', 'desc' ), 'validate_callback' => 'rest_validate_request_arg', ); $params['orderby'] = array( 'description' => __( 'Sort by term property.', 'woocommerce' ), 'type' => 'string', 'default' => 'name', 'enum' => array( 'name', 'slug', 'count', ), 'validate_callback' => 'rest_validate_request_arg', ); $params['hide_empty'] = array( 'description' => __( 'If true, empty terms will not be returned.', 'woocommerce' ), 'type' => 'boolean', 'default' => true, ); return $params; } /** * Get terms matching passed in args. * * @param string $taxonomy Taxonomy to get terms from. * @param \WP_REST_Request $request Request object. * * @return \WP_REST_Response */ protected function get_terms_response( $taxonomy, $request ) { $page = (int) $request['page']; $per_page = $request['per_page'] ? (int) $request['per_page'] : 0; $prepared_args = array( 'taxonomy' => $taxonomy, 'exclude' => $request['exclude'], 'include' => $request['include'], 'order' => $request['order'], 'orderby' => $request['orderby'], 'hide_empty' => (bool) $request['hide_empty'], 'number' => $per_page, 'offset' => $per_page > 0 ? ( $page - 1 ) * $per_page : 0, 'search' => $request['search'], ); $term_query = new WP_Term_Query(); $objects = $term_query->query( $prepared_args ); $return = []; foreach ( $objects as $object ) { $data = $this->prepare_item_for_response( $object, $request ); $return[] = $this->prepare_response_for_collection( $data ); } $response = rest_ensure_response( $return ); // See if pagination is needed before calculating. if ( $per_page > 0 && ( count( $objects ) === $per_page || $page > 1 ) ) { $term_count = $this->get_term_count( $taxonomy, $prepared_args ); $response = ( new Pagination() )->add_headers( $response, $request, $term_count, ceil( $term_count / $per_page ) ); } return $response; } /** * Get count of terms for current query. * * @param string $taxonomy Taxonomy to get terms from. * @param array $args Array of args to pass to wp_count_terms. * @return int */ protected function get_term_count( $taxonomy, $args ) { $count_args = $args; unset( $count_args['number'], $count_args['offset'] ); return (int) wp_count_terms( $taxonomy, $count_args ); } } woocommerce-blocks/src/StoreApi/Routes/CartExtensions.php 0000644 00000003221 15133551764 0017626 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * CartExtensions class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class CartExtensions extends AbstractCartRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/extensions'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'namespace' => [ 'description' => __( 'Extension\'s name - this will be used to ensure the data in the request is routed appropriately.', 'woocommerce' ), 'type' => 'string', ], 'data' => [ 'description' => __( 'Additional data to pass to the extension', 'woocommerce' ), 'type' => 'object', ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Handle the request and return a valid response for this endpoint. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { try { return $this->schema->get_item_response( $request ); } catch ( \WC_REST_Exception $e ) { throw new RouteException( $e->getErrorCode(), $e->getMessage(), $e->getCode() ); } } } woocommerce-blocks/src/StoreApi/Routes/ProductsById.php 0000644 00000003116 15133551764 0017233 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; /** * ProductsById class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class ProductsById extends AbstractRoute { /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/products/(?P<id>[\d]+)'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ 'args' => array( 'id' => array( 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'integer', ), ), [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view', ) ), ), ], 'schema' => [ $this->schema, 'get_public_item_schema' ], ]; } /** * Get a single item. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_response( \WP_REST_Request $request ) { $object = wc_get_product( (int) $request['id'] ); if ( ! $object || 0 === $object->get_id() ) { throw new RouteException( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), 404 ); } return rest_ensure_response( $this->schema->get_item_response( $object ) ); } } woocommerce-blocks/src/StoreApi/Routes/CartUpdateCustomer.php 0000644 00000020143 15133551764 0020435 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Routes; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\BillingAddressSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ShippingAddressSchema; /** * CartUpdateCustomer class. * * Updates the customer billing and shipping address and returns an updated cart--things such as taxes may be recalculated. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class CartUpdateCustomer extends AbstractCartRoute { /** * Get the namespace for this route. * * @return string */ public function get_namespace() { return 'wc/store'; } /** * Get the path of this REST route. * * @return string */ public function get_path() { return '/cart/update-customer'; } /** * Get method arguments for this REST route. * * @return array An array of endpoints. */ public function get_args() { return [ [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'get_response' ], 'permission_callback' => '__return_true', 'args' => [ 'billing_address' => [ 'description' => __( 'Billing address.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'properties' => $this->schema->billing_address_schema->get_properties(), 'sanitize_callback' => [ $this->schema->billing_address_schema, 'sanitize_callback' ], 'validate_callback' => [ $this->schema->billing_address_schema, 'validate_callback' ], ], 'shipping_address' => [ 'description' => __( 'Shipping address.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'properties' => $this->schema->shipping_address_schema->get_properties(), 'sanitize_callback' => [ $this->schema->shipping_address_schema, 'sanitize_callback' ], 'validate_callback' => [ $this->schema->shipping_address_schema, 'validate_callback' ], ], ], ], 'schema' => [ $this->schema, 'get_public_item_schema' ], 'allow_batch' => [ 'v1' => true ], ]; } /** * Handle the request and return a valid response for this endpoint. * * @throws RouteException On error. * @param \WP_REST_Request $request Request object. * @return \WP_REST_Response */ protected function get_route_post_response( \WP_REST_Request $request ) { $cart = $this->cart_controller->get_cart_instance(); $billing = isset( $request['billing_address'] ) ? $request['billing_address'] : []; $shipping = isset( $request['shipping_address'] ) ? $request['shipping_address'] : []; // If the cart does not need shipping, shipping address is forced to match billing address unless defined. if ( ! $cart->needs_shipping() && ! isset( $request['shipping_address'] ) ) { $shipping = isset( $request['billing_address'] ) ? $request['billing_address'] : [ 'first_name' => wc()->customer->get_billing_first_name(), 'last_name' => wc()->customer->get_billing_last_name(), 'company' => wc()->customer->get_billing_company(), 'address_1' => wc()->customer->get_billing_address_1(), 'address_2' => wc()->customer->get_billing_address_2(), 'city' => wc()->customer->get_billing_city(), 'state' => wc()->customer->get_billing_state(), 'postcode' => wc()->customer->get_billing_postcode(), 'country' => wc()->customer->get_billing_country(), 'phone' => wc()->customer->get_billing_phone(), ]; } wc()->customer->set_props( array( 'billing_first_name' => isset( $billing['first_name'] ) ? $billing['first_name'] : null, 'billing_last_name' => isset( $billing['last_name'] ) ? $billing['last_name'] : null, 'billing_company' => isset( $billing['company'] ) ? $billing['company'] : null, 'billing_address_1' => isset( $billing['address_1'] ) ? $billing['address_1'] : null, 'billing_address_2' => isset( $billing['address_2'] ) ? $billing['address_2'] : null, 'billing_city' => isset( $billing['city'] ) ? $billing['city'] : null, 'billing_state' => isset( $billing['state'] ) ? $billing['state'] : null, 'billing_postcode' => isset( $billing['postcode'] ) ? $billing['postcode'] : null, 'billing_country' => isset( $billing['country'] ) ? $billing['country'] : null, 'billing_phone' => isset( $billing['phone'] ) ? $billing['phone'] : null, 'billing_email' => isset( $request['billing_address'], $request['billing_address']['email'] ) ? $request['billing_address']['email'] : null, 'shipping_first_name' => isset( $shipping['first_name'] ) ? $shipping['first_name'] : null, 'shipping_last_name' => isset( $shipping['last_name'] ) ? $shipping['last_name'] : null, 'shipping_company' => isset( $shipping['company'] ) ? $shipping['company'] : null, 'shipping_address_1' => isset( $shipping['address_1'] ) ? $shipping['address_1'] : null, 'shipping_address_2' => isset( $shipping['address_2'] ) ? $shipping['address_2'] : null, 'shipping_city' => isset( $shipping['city'] ) ? $shipping['city'] : null, 'shipping_state' => isset( $shipping['state'] ) ? $shipping['state'] : null, 'shipping_postcode' => isset( $shipping['postcode'] ) ? $shipping['postcode'] : null, 'shipping_country' => isset( $shipping['country'] ) ? $shipping['country'] : null, ) ); $shipping_phone_value = isset( $shipping['phone'] ) ? $shipping['phone'] : null; // @todo Remove custom shipping_phone handling (requires WC 5.6+) if ( is_callable( [ wc()->customer, 'set_shipping_phone' ] ) ) { wc()->customer->set_shipping_phone( $shipping_phone_value ); } else { wc()->customer->update_meta_data( 'shipping_phone', $shipping_phone_value ); } wc()->customer->save(); $this->calculate_totals(); $this->maybe_update_order(); return rest_ensure_response( $this->schema->get_item_response( $cart ) ); } /** * If there is a draft order, update customer data there also. * * @return void */ protected function maybe_update_order() { $draft_order_id = wc()->session->get( 'store_api_draft_order', 0 ); $draft_order = $draft_order_id ? wc_get_order( $draft_order_id ) : false; if ( ! $draft_order ) { return; } $draft_order->set_props( [ 'billing_first_name' => wc()->customer->get_billing_first_name(), 'billing_last_name' => wc()->customer->get_billing_last_name(), 'billing_company' => wc()->customer->get_billing_company(), 'billing_address_1' => wc()->customer->get_billing_address_1(), 'billing_address_2' => wc()->customer->get_billing_address_2(), 'billing_city' => wc()->customer->get_billing_city(), 'billing_state' => wc()->customer->get_billing_state(), 'billing_postcode' => wc()->customer->get_billing_postcode(), 'billing_country' => wc()->customer->get_billing_country(), 'billing_email' => wc()->customer->get_billing_email(), 'billing_phone' => wc()->customer->get_billing_phone(), 'shipping_first_name' => wc()->customer->get_shipping_first_name(), 'shipping_last_name' => wc()->customer->get_shipping_last_name(), 'shipping_company' => wc()->customer->get_shipping_company(), 'shipping_address_1' => wc()->customer->get_shipping_address_1(), 'shipping_address_2' => wc()->customer->get_shipping_address_2(), 'shipping_city' => wc()->customer->get_shipping_city(), 'shipping_state' => wc()->customer->get_shipping_state(), 'shipping_postcode' => wc()->customer->get_shipping_postcode(), 'shipping_country' => wc()->customer->get_shipping_country(), ] ); $shipping_phone_value = is_callable( [ wc()->customer, 'get_shipping_phone' ] ) ? wc()->customer->get_shipping_phone() : wc()->customer->get_meta( 'shipping_phone', true ); if ( is_callable( [ $draft_order, 'set_shipping_phone' ] ) ) { $draft_order->set_shipping_phone( $shipping_phone_value ); } else { $draft_order->update_meta_data( '_shipping_phone', $shipping_phone_value ); } $draft_order->save(); } } woocommerce-blocks/src/StoreApi/Formatters/FormatterInterface.php 0000644 00000000742 15133551764 0021313 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Formatters; /** * FormatterInterface. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ interface FormatterInterface { /** * Format a given value and return the result. * * @param mixed $value Value to format. * @param array $options Options that influence the formatting. * @return mixed */ public function format( $value, array $options = [] ); } woocommerce-blocks/src/StoreApi/Formatters/DefaultFormatter.php 0000644 00000001016 15133551764 0020772 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Formatters; /** * Default Formatter. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class DefaultFormatter implements FormatterInterface { /** * Format a given value and return the result. * * @param mixed $value Value to format. * @param array $options Options that influence the formatting. * @return mixed */ public function format( $value, array $options = [] ) { return $value; } } woocommerce-blocks/src/StoreApi/Formatters/CurrencyFormatter.php 0000644 00000002671 15133551764 0021210 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Formatters; /** * Currency Formatter. * * Formats an array of monetary values by inserting currency data. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class CurrencyFormatter implements FormatterInterface { /** * Format a given value and return the result. * * @param array $value Value to format. * @param array $options Options that influence the formatting. * @return array */ public function format( $value, array $options = [] ) { $position = get_option( 'woocommerce_currency_pos' ); $symbol = html_entity_decode( get_woocommerce_currency_symbol() ); $prefix = ''; $suffix = ''; switch ( $position ) { case 'left_space': $prefix = $symbol . ' '; break; case 'left': $prefix = $symbol; break; case 'right_space': $suffix = ' ' . $symbol; break; case 'right': $suffix = $symbol; break; } return array_merge( (array) $value, [ 'currency_code' => get_woocommerce_currency(), 'currency_symbol' => $symbol, 'currency_minor_unit' => wc_get_price_decimals(), 'currency_decimal_separator' => wc_get_price_decimal_separator(), 'currency_thousand_separator' => wc_get_price_thousand_separator(), 'currency_prefix' => $prefix, 'currency_suffix' => $suffix, ] ); } } woocommerce-blocks/src/StoreApi/Formatters/HtmlFormatter.php 0000644 00000001611 15133551764 0020313 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Formatters; /** * Html Formatter. * * Formats HTML in API responses. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class HtmlFormatter implements FormatterInterface { /** * Format a given value and return the result. * * The wptexturize, convert_chars, and trim functions are also used in the `the_title` filter. * The function wp_kses_post removes disallowed HTML tags. * * @param string|array $value Value to format. * @param array $options Options that influence the formatting. * @return string */ public function format( $value, array $options = [] ) { if ( is_array( $value ) ) { return array_map( [ $this, 'format' ], $value ); } return is_scalar( $value ) ? wp_kses_post( trim( convert_chars( wptexturize( $value ) ) ) ) : $value; } } woocommerce-blocks/src/StoreApi/Formatters/MoneyFormatter.php 0000644 00000001565 15133551764 0020506 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Formatters; /** * Money Formatter. * * Formats monetary values using store settings. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class MoneyFormatter implements FormatterInterface { /** * Format a given value and return the result. * * @param mixed $value Value to format. * @param array $options Options that influence the formatting. * @return mixed */ public function format( $value, array $options = [] ) { $options = wp_parse_args( $options, [ 'decimals' => wc_get_price_decimals(), 'rounding_mode' => PHP_ROUND_HALF_UP, ] ); return (string) intval( round( ( (float) wc_format_decimal( $value ) ) * ( 10 ** absint( $options['decimals'] ) ), 0, absint( $options['rounding_mode'] ) ) ); } } woocommerce-blocks/src/StoreApi/RoutesController.php 0000644 00000011044 15133551764 0016723 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi; use Routes\AbstractRoute; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\CartController; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\OrderController; /** * RoutesController class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class RoutesController { /** * Stores schemas. * * @var SchemaController */ protected $schemas; /** * Stores routes. * * @var AbstractRoute[] */ protected $routes = []; /** * Constructor. * * @param SchemaController $schemas Schema controller class passed to each route. */ public function __construct( SchemaController $schemas ) { $this->schemas = $schemas; $this->initialize(); } /** * Get a route class instance. * * @throws Exception If the schema does not exist. * * @param string $name Name of schema. * @return AbstractRoute */ public function get( $name ) { if ( ! isset( $this->routes[ $name ] ) ) { throw new Exception( $name . ' route does not exist' ); } return $this->routes[ $name ]; } /** * Register defined list of routes with WordPress. */ public function register_routes() { foreach ( $this->routes as $route ) { register_rest_route( $route->get_namespace(), $route->get_path(), $route->get_args() ); } } /** * Load route class instances. */ protected function initialize() { global $wp_version; $cart_controller = new CartController(); $order_controller = new OrderController(); $this->routes = [ 'cart' => new Routes\Cart( $this->schemas->get( 'cart' ), null, $cart_controller ), 'cart-add-item' => new Routes\CartAddItem( $this->schemas->get( 'cart' ), null, $cart_controller ), 'cart-apply-coupon' => new Routes\CartApplyCoupon( $this->schemas->get( 'cart' ), null, $cart_controller ), 'cart-coupons' => new Routes\CartCoupons( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-coupon' ), $cart_controller ), 'cart-coupons-by-code' => new Routes\CartCouponsByCode( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-coupon' ), $cart_controller ), 'cart-extensions' => new Routes\CartExtensions( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-extensions' ), $cart_controller ), 'cart-items' => new Routes\CartItems( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-item' ), $cart_controller ), 'cart-items-by-key' => new Routes\CartItemsByKey( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-item' ), $cart_controller ), 'cart-remove-coupon' => new Routes\CartRemoveCoupon( $this->schemas->get( 'cart' ), null, $cart_controller ), 'cart-remove-item' => new Routes\CartRemoveItem( $this->schemas->get( 'cart' ), null, $cart_controller ), 'cart-select-shipping-rate' => new Routes\CartSelectShippingRate( $this->schemas->get( 'cart' ), null, $cart_controller ), 'cart-update-item' => new Routes\CartUpdateItem( $this->schemas->get( 'cart' ), null, $cart_controller ), 'cart-update-customer' => new Routes\CartUpdateCustomer( $this->schemas->get( 'cart' ), null, $cart_controller ), 'checkout' => new Routes\Checkout( $this->schemas->get( 'cart' ), $this->schemas->get( 'checkout' ), $cart_controller, $order_controller ), 'product-attributes' => new Routes\ProductAttributes( $this->schemas->get( 'product-attribute' ) ), 'product-attributes-by-id' => new Routes\ProductAttributesById( $this->schemas->get( 'product-attribute' ) ), 'product-attribute-terms' => new Routes\ProductAttributeTerms( $this->schemas->get( 'term' ) ), 'product-categories' => new Routes\ProductCategories( $this->schemas->get( 'product-category' ) ), 'product-categories-by-id' => new Routes\ProductCategoriesById( $this->schemas->get( 'product-category' ) ), 'product-collection-data' => new Routes\ProductCollectionData( $this->schemas->get( 'product-collection-data' ) ), 'product-reviews' => new Routes\ProductReviews( $this->schemas->get( 'product-review' ) ), 'product-tags' => new Routes\ProductTags( $this->schemas->get( 'term' ) ), 'products' => new Routes\Products( $this->schemas->get( 'product' ) ), 'products-by-id' => new Routes\ProductsById( $this->schemas->get( 'product' ) ), ]; // Batching requires WP 5.6. if ( version_compare( $wp_version, '5.6', '>=' ) ) { $this->routes['batch'] = new Routes\Batch(); } } } woocommerce-blocks/src/StoreApi/Schemas/CartExtensionsSchema.php 0000644 00000002441 15133551764 0021054 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; use AutomateWoo\Exception; use Automattic\WooCommerce\Blocks\StoreApi\Routes\RouteException; /** * Class CartExtensionsSchema * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class CartExtensionsSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'cart-extensions'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'cart-extensions'; /** * Cart extensions schema properties. * * @return array */ public function get_properties() { return []; } /** * Handle the request and return a valid response for this endpoint. * * @param \WP_REST_Request $request Request containing data for the extension callback. * @throws RouteException When callback is not callable or parameters are incorrect. * * @return array */ public function get_item_response( $request = null ) { try { $callback = $this->extend->get_update_callback( $request['namespace'] ); } catch ( RouteException $e ) { throw $e; } if ( is_callable( $callback ) ) { $callback( $request['data'] ); } return rest_ensure_response( wc()->api->get_endpoint_data( '/wc/store/cart' ) ); } } woocommerce-blocks/src/StoreApi/Schemas/BillingAddressSchema.php 0000644 00000007024 15133551764 0020773 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; use Automattic\WooCommerce\Blocks\RestApi\Routes; /** * BillingAddressSchema class. * * Provides a generic billing address schema for composition in other schemas. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class BillingAddressSchema extends AbstractAddressSchema { /** * The schema item name. * * @var string */ protected $title = 'billing_address'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'billing-address'; /** * Term properties. * * @return array */ public function get_properties() { $properties = parent::get_properties(); return array_merge( $properties, [ 'email' => [ 'description' => __( 'Email', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], ] ); } /** * Sanitize and format the given address object. * * @param array $address Value being sanitized. * @param \WP_REST_Request $request The Request. * @param string $param The param being sanitized. * @return array */ public function sanitize_callback( $address, $request, $param ) { $address = parent::sanitize_callback( $address, $request, $param ); $address['email'] = wc_clean( wp_unslash( $address['email'] ) ); return $address; } /** * Validate the given address object. * * @param array $address Value being sanitized. * @param \WP_REST_Request $request The Request. * @param string $param The param being sanitized. * @return true|\WP_Error */ public function validate_callback( $address, $request, $param ) { $errors = parent::validate_callback( $address, $request, $param ); $address = $this->sanitize_callback( $address, $request, $param ); $errors = is_wp_error( $errors ) ? $errors : new \WP_Error(); if ( ! empty( $address['email'] ) && ! is_email( $address['email'] ) ) { $errors->add( 'invalid_email', __( 'The provided email address is not valid', 'woocommerce' ) ); } return $errors->has_errors( $errors ) ? $errors : true; } /** * Convert a term object into an object suitable for the response. * * @param \WC_Order|\WC_Customer $address An object with billing address. * * @throws RouteException When the invalid object types are provided. * @return stdClass */ public function get_item_response( $address ) { if ( ( $address instanceof \WC_Customer || $address instanceof \WC_Order ) ) { return (object) $this->prepare_html_response( [ 'first_name' => $address->get_billing_first_name(), 'last_name' => $address->get_billing_last_name(), 'company' => $address->get_billing_company(), 'address_1' => $address->get_billing_address_1(), 'address_2' => $address->get_billing_address_2(), 'city' => $address->get_billing_city(), 'state' => $address->get_billing_state(), 'postcode' => $address->get_billing_postcode(), 'country' => $address->get_billing_country(), 'email' => $address->get_billing_email(), 'phone' => $address->get_billing_phone(), ] ); } throw new RouteException( 'invalid_object_type', sprintf( /* translators: Placeholders are class and method names */ __( '%1$s requires an instance of %2$s or %3$s for the address', 'woocommerce' ), 'BillingAddressSchema::get_item_response', 'WC_Customer', 'WC_Order' ), 500 ); } } woocommerce-blocks/src/StoreApi/Schemas/ProductReviewSchema.php 0000644 00000014567 15133551764 0020721 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi; /** * ProductReviewSchema class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class ProductReviewSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'product_review'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'product-review'; /** * Image attachment schema instance. * * @var ImageAttachmentSchema */ protected $image_attachment_schema; /** * Constructor. * * @param ExtendRestApi $extend Rest Extending instance. * @param ImageAttachmentSchema $image_attachment_schema Image attachment schema instance. */ public function __construct( ExtendRestApi $extend, ImageAttachmentSchema $image_attachment_schema ) { $this->image_attachment_schema = $image_attachment_schema; parent::__construct( $extend ); } /** * Product review schema properties. * * @return array */ public function get_properties() { $properties = [ 'id' => [ 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'date_created' => [ 'description' => __( "The date the review was created, in the site's timezone.", 'woocommerce' ), 'type' => 'string', 'format' => 'date-time', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'formatted_date_created' => [ 'description' => __( "The date the review was created, in the site's timezone in human-readable format.", 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'date_created_gmt' => [ 'description' => __( 'The date the review was created, as GMT.', 'woocommerce' ), 'type' => 'string', 'format' => 'date-time', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'product_id' => [ 'description' => __( 'Unique identifier for the product that the review belongs to.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'product_name' => [ 'description' => __( 'Name of the product that the review belongs to.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'product_permalink' => [ 'description' => __( 'Permalink of the product that the review belongs to.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'product_image' => [ 'description' => __( 'Image of the product that the review belongs to.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => $this->image_attachment_schema->get_properties(), ], 'reviewer' => [ 'description' => __( 'Reviewer name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'review' => [ 'description' => __( 'The content of the review.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'arg_options' => [ 'sanitize_callback' => 'wp_filter_post_kses', ], 'readonly' => true, ], 'rating' => [ 'description' => __( 'Review rating (0 to 5).', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'verified' => [ 'description' => __( 'Shows if the reviewer bought the product or not.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ]; if ( get_option( 'show_avatars' ) ) { $avatar_properties = array(); $avatar_sizes = rest_get_avatar_sizes(); foreach ( $avatar_sizes as $size ) { $avatar_properties[ $size ] = array( /* translators: %d: avatar image size in pixels */ 'description' => sprintf( __( 'Avatar URL with image size of %d pixels.', 'woocommerce' ), $size ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'embed', 'view', 'edit' ), ); } $properties['reviewer_avatar_urls'] = array( 'description' => __( 'Avatar URLs for the object reviewer.', 'woocommerce' ), 'type' => 'object', 'context' => array( 'view', 'edit' ), 'readonly' => true, 'properties' => $avatar_properties, ); } return $properties; } /** * Convert a WooCommerce product into an object suitable for the response. * * @param \WP_Comment $review Product review object. * @return array */ public function get_item_response( \WP_Comment $review ) { $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $rating = get_comment_meta( $review->comment_ID, 'rating', true ) === '' ? null : (int) get_comment_meta( $review->comment_ID, 'rating', true ); $data = [ 'id' => (int) $review->comment_ID, 'date_created' => wc_rest_prepare_date_response( $review->comment_date ), 'formatted_date_created' => get_comment_date( 'F j, Y', $review->comment_ID ), 'date_created_gmt' => wc_rest_prepare_date_response( $review->comment_date_gmt ), 'product_id' => (int) $review->comment_post_ID, 'product_name' => get_the_title( (int) $review->comment_post_ID ), 'product_permalink' => get_permalink( (int) $review->comment_post_ID ), 'product_image' => $this->image_attachment_schema->get_item_response( get_post_thumbnail_id( (int) $review->comment_post_ID ) ), 'reviewer' => $review->comment_author, 'review' => $review->comment_content, 'rating' => $rating, 'verified' => wc_review_is_from_verified_owner( $review->comment_ID ), 'reviewer_avatar_urls' => rest_get_avatar_urls( $review->comment_author_email ), ]; if ( 'view' === $context ) { $data['review'] = wpautop( $data['review'] ); } return $data; } } woocommerce-blocks/src/StoreApi/Schemas/TermSchema.php 0000644 00000004435 15133551764 0017017 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; /** * TermSchema class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. * @since 2.5.0 */ class TermSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'term'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'term'; /** * Term properties. * * @return array */ public function get_properties() { return [ 'id' => array( 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'name' => array( 'description' => __( 'Term name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'slug' => array( 'description' => __( 'String based identifier for the term.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'description' => array( 'description' => __( 'Term description.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'parent' => array( 'description' => __( 'Parent term ID, if applicable.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'count' => array( 'description' => __( 'Number of objects (posts of any type) assigned to the term.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ]; } /** * Convert a term object into an object suitable for the response. * * @param \WP_Term $term Term object. * @return array */ public function get_item_response( $term ) { return [ 'id' => (int) $term->term_id, 'name' => $this->prepare_html_response( $term->name ), 'slug' => $term->slug, 'description' => $this->prepare_html_response( $term->description ), 'parent' => (int) $term->parent, 'count' => (int) $term->count, ]; } } woocommerce-blocks/src/StoreApi/Schemas/AbstractSchema.php 0000644 00000027033 15133551764 0017652 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi; /** * AbstractSchema class. * * For REST Route Schemas * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. * @since 2.5.0 */ abstract class AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'Schema'; /** * Rest extend instance * * @var ExtendRestApi */ protected $extend; /** * Extending key that gets added to endpoint. * * @var string */ const EXTENDING_KEY = 'extensions'; /** * Constructor. * * @param ExtendRestApi $extend Rest Extending instance. */ public function __construct( ExtendRestApi $extend ) { $this->extend = $extend; } /** * Returns the full item schema. * * @return array */ public function get_item_schema() { return array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => $this->title, 'type' => 'object', 'properties' => $this->get_properties(), ); } /** * Recursive removal of arg_options. * * @param array $properties Schema properties. */ protected function remove_arg_options( $properties ) { return array_map( function( $property ) { if ( isset( $property['properties'] ) ) { $property['properties'] = $this->remove_arg_options( $property['properties'] ); } elseif ( isset( $property['items']['properties'] ) ) { $property['items']['properties'] = $this->remove_arg_options( $property['items']['properties'] ); } unset( $property['arg_options'] ); return $property; }, (array) $properties ); } /** * Returns the public schema. * * @return array */ public function get_public_item_schema() { $schema = $this->get_item_schema(); if ( isset( $schema['properties'] ) ) { $schema['properties'] = $this->remove_arg_options( $schema['properties'] ); } return $schema; } /** * Returns extended data for a specific endpoint. * * @param string $endpoint The endpoint identifer. * @param array ...$passed_args An array of arguments to be passed to callbacks. * @return object the data that will get added. */ protected function get_extended_data( $endpoint, ...$passed_args ) { return $this->extend->get_endpoint_data( $endpoint, $passed_args ); } /** * Gets an array of schema defaults recursively. * * @param array $properties Schema property data. * @return array Array of defaults, pulled from arg_options */ protected function get_recursive_schema_property_defaults( $properties ) { $defaults = []; foreach ( $properties as $property_key => $property_value ) { if ( isset( $property_value['arg_options']['default'] ) ) { $defaults[ $property_key ] = $property_value['arg_options']['default']; } elseif ( isset( $property_value['properties'] ) ) { $defaults[ $property_key ] = $this->get_recursive_schema_property_defaults( $property_value['properties'] ); } } return $defaults; } /** * Gets a function that validates recursively. * * @param array $properties Schema property data. * @return function Anonymous validation callback. */ protected function get_recursive_validate_callback( $properties ) { /** * Validate a request argument based on details registered to the route. * * @param mixed $values * @param \WP_REST_Request $request * @param string $param * @return true|\WP_Error */ return function ( $values, $request, $param ) use ( $properties ) { foreach ( $properties as $property_key => $property_value ) { $current_value = isset( $values[ $property_key ] ) ? $values[ $property_key ] : null; if ( isset( $property_value['arg_options']['validate_callback'] ) ) { $callback = $property_value['arg_options']['validate_callback']; $result = is_callable( $callback ) ? $callback( $current_value, $request, $param ) : false; } else { $result = rest_validate_value_from_schema( $current_value, $property_value, $param . ' > ' . $property_key ); } if ( ! $result || is_wp_error( $result ) ) { return $result; } if ( isset( $property_value['properties'] ) ) { $validate_callback = $this->get_recursive_validate_callback( $property_value['properties'] ); return $validate_callback( $current_value, $request, $param . ' > ' . $property_key ); } } return true; }; } /** * Gets a function that sanitizes recursively. * * @param array $properties Schema property data. * @return function Anonymous validation callback. */ protected function get_recursive_sanitize_callback( $properties ) { /** * Validate a request argument based on details registered to the route. * * @param mixed $values * @param \WP_REST_Request $request * @param string $param * @return true|\WP_Error */ return function ( $values, $request, $param ) use ( $properties ) { foreach ( $properties as $property_key => $property_value ) { $current_value = isset( $values[ $property_key ] ) ? $values[ $property_key ] : null; if ( isset( $property_value['arg_options']['sanitize_callback'] ) ) { $callback = $property_value['arg_options']['sanitize_callback']; $current_value = is_callable( $callback ) ? $callback( $current_value, $request, $param ) : $current_value; } else { $current_value = rest_sanitize_value_from_schema( $current_value, $property_value, $param . ' > ' . $property_key ); } if ( is_wp_error( $current_value ) ) { return $current_value; } if ( isset( $property_value['properties'] ) ) { $sanitize_callback = $this->get_recursive_sanitize_callback( $property_value['properties'] ); return $sanitize_callback( $current_value, $request, $param . ' > ' . $property_key ); } } return true; }; } /** * Returns extended schema for a specific endpoint. * * @param string $endpoint The endpoint identifer. * @param array ...$passed_args An array of arguments to be passed to callbacks. * @return array the data that will get added. */ protected function get_extended_schema( $endpoint, ...$passed_args ) { $extended_schema = $this->extend->get_endpoint_schema( $endpoint, $passed_args ); $defaults = $this->get_recursive_schema_property_defaults( $extended_schema ); return [ 'type' => 'object', 'context' => [ 'view', 'edit' ], 'arg_options' => [ 'default' => $defaults, 'validate_callback' => $this->get_recursive_validate_callback( $extended_schema ), 'sanitize_callback' => $this->get_recursive_sanitize_callback( $extended_schema ), ], 'properties' => $extended_schema, ]; } /** * Apply a schema get_item_response callback to an array of items and return the result. * * @param AbstractSchema $schema Schema class instance. * @param array $items Array of items. * @return array Array of values from the callback function. */ protected function get_item_responses_from_schema( AbstractSchema $schema, $items ) { $items = array_filter( $items ); if ( empty( $items ) ) { return []; } return array_values( array_map( [ $schema, 'get_item_response' ], $items ) ); } /** * Retrieves an array of endpoint arguments from the item schema for the controller. * * @uses rest_get_endpoint_args_for_schema() * @param string $method Optional. HTTP method of the request. * @return array Endpoint arguments. */ public function get_endpoint_args_for_item_schema( $method = \WP_REST_Server::CREATABLE ) { $schema = $this->get_item_schema(); $endpoint_args = rest_get_endpoint_args_for_schema( $schema, $method ); $endpoint_args = $this->remove_arg_options( $endpoint_args ); return $endpoint_args; } /** * Force all schema properties to be readonly. * * @param array $properties Schema. * @return array Updated schema. */ protected function force_schema_readonly( $properties ) { return array_map( function( $property ) { $property['readonly'] = true; if ( isset( $property['items']['properties'] ) ) { $property['items']['properties'] = $this->force_schema_readonly( $property['items']['properties'] ); } return $property; }, (array) $properties ); } /** * Returns consistent currency schema used across endpoints for prices. * * @return array */ protected function get_store_currency_properties() { return [ 'currency_code' => [ 'description' => __( 'Currency code (in ISO format) for returned prices.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'currency_symbol' => [ 'description' => __( 'Currency symbol for the currency which can be used to format returned prices.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'currency_minor_unit' => [ 'description' => __( 'Currency minor unit (number of digits after the decimal separator) for returned prices.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'currency_decimal_separator' => array( 'description' => __( 'Decimal separator for the currency which can be used to format returned prices.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'currency_thousand_separator' => array( 'description' => __( 'Thousand separator for the currency which can be used to format returned prices.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'currency_prefix' => array( 'description' => __( 'Price prefix for the currency which can be used to format returned prices.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'currency_suffix' => array( 'description' => __( 'Price prefix for the currency which can be used to format returned prices.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ]; } /** * Adds currency data to an array of monetary values. * * @param array $values Monetary amounts. * @return array Monetary amounts with currency data appended. */ protected function prepare_currency_response( $values ) { return $this->extend->get_formatter( 'currency' )->format( $values ); } /** * Convert monetary values from WooCommerce to string based integers, using * the smallest unit of a currency. * * @param string|float $amount Monetary amount with decimals. * @param int $decimals Number of decimals the amount is formatted with. * @param int $rounding_mode Defaults to the PHP_ROUND_HALF_UP constant. * @return string The new amount. */ protected function prepare_money_response( $amount, $decimals = 2, $rounding_mode = PHP_ROUND_HALF_UP ) { return $this->extend->get_formatter( 'money' )->format( $amount, [ 'decimals' => $decimals, 'rounding_mode' => $rounding_mode, ] ); } /** * Prepares HTML based content, such as post titles and content, for the API response. * * @param string|array $response Data to format. * @return string|array Formatted data. */ protected function prepare_html_response( $response ) { return $this->extend->get_formatter( 'html' )->format( $response ); } } woocommerce-blocks/src/StoreApi/Schemas/ShippingAddressSchema.php 0000644 00000004221 15133551764 0021170 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; use Automattic\WooCommerce\Blocks\RestApi\Routes; /** * ShippingAddressSchema class. * * Provides a generic shipping address schema for composition in other schemas. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. * @since 2.5.0 */ class ShippingAddressSchema extends AbstractAddressSchema { /** * The schema item name. * * @var string */ protected $title = 'shipping_address'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'shipping-address'; /** * Convert a term object into an object suitable for the response. * * @param \WC_Order|\WC_Customer $address An object with shipping address. * * @throws RouteException When the invalid object types are provided. * @return stdClass */ public function get_item_response( $address ) { if ( ( $address instanceof \WC_Customer || $address instanceof \WC_Order ) ) { if ( is_callable( [ $address, 'get_shipping_phone' ] ) ) { $shipping_phone = $address->get_shipping_phone(); } else { $shipping_phone = $address->get_meta( $address instanceof \WC_Customer ? 'shipping_phone' : '_shipping_phone', true ); } return (object) $this->prepare_html_response( [ 'first_name' => $address->get_shipping_first_name(), 'last_name' => $address->get_shipping_last_name(), 'company' => $address->get_shipping_company(), 'address_1' => $address->get_shipping_address_1(), 'address_2' => $address->get_shipping_address_2(), 'city' => $address->get_shipping_city(), 'state' => $address->get_shipping_state(), 'postcode' => $address->get_shipping_postcode(), 'country' => $address->get_shipping_country(), 'phone' => $shipping_phone, ] ); } throw new RouteException( 'invalid_object_type', sprintf( /* translators: Placeholders are class and method names */ __( '%1$s requires an instance of %2$s or %3$s for the address', 'woocommerce' ), 'ShippingAddressSchema::get_item_response', 'WC_Customer', 'WC_Order' ), 500 ); } } woocommerce-blocks/src/StoreApi/Schemas/OrderCouponSchema.php 0000644 00000004221 15133551764 0020340 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; /** * OrderCouponSchema class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class OrderCouponSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'order_coupon'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'order-coupon'; /** * Cart schema properties. * * @return array */ public function get_properties() { return [ 'code' => [ 'description' => __( 'The coupons unique code.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'totals' => [ 'description' => __( 'Total amounts provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( $this->get_store_currency_properties(), [ 'total_discount' => [ 'description' => __( 'Total discount applied by this coupon.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_discount_tax' => [ 'description' => __( 'Total tax removed due to discount applied by this coupon.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ] ), ], ]; } /** * Convert an order coupon to an object suitable for the response. * * @param \WC_Order_Item_Coupon $coupon Order coupon array. * @return array */ public function get_item_response( \WC_Order_Item_Coupon $coupon ) { return [ 'code' => $coupon->get_code(), 'totals' => (object) $this->prepare_currency_response( [ 'total_discount' => $this->prepare_money_response( $coupon->get_discount(), wc_get_price_decimals() ), 'total_discount_tax' => $this->prepare_money_response( $coupon->get_discount_tax(), wc_get_price_decimals(), PHP_ROUND_HALF_DOWN ), ] ), ]; } } woocommerce-blocks/src/StoreApi/Schemas/CartSchema.php 0000644 00000036701 15133551764 0017002 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\CartController; use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi; use WC_Tax; use WP_Error; /** * CartSchema class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. * @since 2.5.0 */ class CartSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'cart'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'cart'; /** * Item schema instance. * * @var CartItemSchema */ public $item_schema; /** * Coupon schema instance. * * @var CartCouponSchema */ public $coupon_schema; /** * Fee schema instance. * * @var CartFeeSchema */ public $fee_schema; /** * Shipping rates schema instance. * * @var CartShippingRateSchema */ public $shipping_rate_schema; /** * Shipping address schema instance. * * @var ShippingAddressSchema */ public $shipping_address_schema; /** * Billing address schema instance. * * @var BillingAddressSchema */ public $billing_address_schema; /** * Error schema instance. * * @var ErrorSchema */ public $error_schema; /** * Constructor. * * @param ExtendRestApi $extend Rest Extending instance. * @param CartItemSchema $item_schema Item schema instance. * @param CartCouponSchema $coupon_schema Coupon schema instance. * @param CartFeeSchema $fee_schema Fee schema instance. * @param CartShippingRateSchema $shipping_rate_schema Shipping rates schema instance. * @param ShippingAddressSchema $shipping_address_schema Shipping address schema instance. * @param BillingAddressSchema $billing_address_schema Billing address schema instance. * @param ErrorSchema $error_schema Error schema instance. */ public function __construct( ExtendRestApi $extend, CartItemSchema $item_schema, CartCouponSchema $coupon_schema, CartFeeSchema $fee_schema, CartShippingRateSchema $shipping_rate_schema, ShippingAddressSchema $shipping_address_schema, BillingAddressSchema $billing_address_schema, ErrorSchema $error_schema ) { $this->item_schema = $item_schema; $this->coupon_schema = $coupon_schema; $this->fee_schema = $fee_schema; $this->shipping_rate_schema = $shipping_rate_schema; $this->shipping_address_schema = $shipping_address_schema; $this->billing_address_schema = $billing_address_schema; $this->error_schema = $error_schema; parent::__construct( $extend ); } /** * Cart schema properties. * * @return array */ public function get_properties() { return [ 'coupons' => [ 'description' => __( 'List of applied cart coupons.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => $this->force_schema_readonly( $this->coupon_schema->get_properties() ), ], ], 'shipping_rates' => [ 'description' => __( 'List of available shipping rates for the cart.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => $this->force_schema_readonly( $this->shipping_rate_schema->get_properties() ), ], ], 'shipping_address' => [ 'description' => __( 'Current set shipping address for the customer.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => $this->force_schema_readonly( $this->shipping_address_schema->get_properties() ), ], 'billing_address' => [ 'description' => __( 'Current set billing address for the customer.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => $this->force_schema_readonly( $this->billing_address_schema->get_properties() ), ], 'items' => [ 'description' => __( 'List of cart items.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => $this->force_schema_readonly( $this->item_schema->get_properties() ), ], ], 'items_count' => [ 'description' => __( 'Number of items in the cart.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'items_weight' => [ 'description' => __( 'Total weight (in grams) of all products in the cart.', 'woocommerce' ), 'type' => 'number', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'needs_payment' => [ 'description' => __( 'True if the cart needs payment. False for carts with only free products and no shipping costs.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'needs_shipping' => [ 'description' => __( 'True if the cart needs shipping. False for carts with only digital goods or stores with no shipping methods set-up.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'has_calculated_shipping' => [ 'description' => __( 'True if the cart meets the criteria for showing shipping costs, and rates have been calculated and included in the totals.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'fees' => [ 'description' => __( 'List of cart fees.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => $this->force_schema_readonly( $this->fee_schema->get_properties() ), ], ], 'totals' => [ 'description' => __( 'Cart total amounts provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( $this->get_store_currency_properties(), [ 'total_items' => [ 'description' => __( 'Total price of items in the cart.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_items_tax' => [ 'description' => __( 'Total tax on items in the cart.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_fees' => [ 'description' => __( 'Total price of any applied fees.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_fees_tax' => [ 'description' => __( 'Total tax on fees.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_discount' => [ 'description' => __( 'Total discount from applied coupons.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_discount_tax' => [ 'description' => __( 'Total tax removed due to discount from applied coupons.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_shipping' => [ 'description' => __( 'Total price of shipping. If shipping has not been calculated, a null response will be sent.', 'woocommerce' ), 'type' => [ 'string', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_shipping_tax' => [ 'description' => __( 'Total tax on shipping. If shipping has not been calculated, a null response will be sent.', 'woocommerce' ), 'type' => [ 'string', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_price' => [ 'description' => __( 'Total price the customer will pay.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_tax' => [ 'description' => __( 'Total tax applied to items and shipping.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'tax_lines' => [ 'description' => __( 'Lines of taxes applied to items and shipping.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'name' => [ 'description' => __( 'The name of the tax.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'price' => [ 'description' => __( 'The amount of tax charged.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'rate' => [ 'description' => __( 'The rate at which tax is applied.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], ] ), ], 'errors' => [ 'description' => __( 'List of cart item errors, for example, items in the cart which are out of stock.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => $this->force_schema_readonly( $this->error_schema->get_properties() ), ], ], 'payment_requirements' => [ 'description' => __( 'List of required payment gateway features to process the order.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'generated_timestamp' => [ 'description' => __( 'The time at which this cart data was prepared', 'woocommerce' ), 'type' => 'number', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], self::EXTENDING_KEY => $this->get_extended_schema( self::IDENTIFIER ), ]; } /** * Convert a woo cart into an object suitable for the response. * * @param \WC_Cart $cart Cart class instance. * @return array */ public function get_item_response( $cart ) { $controller = new CartController(); // Get cart errors first so if recalculations are performed, it's reflected in the response. $cart_errors = $this->get_cart_errors( $cart ); // The core cart class will not include shipping in the cart totals if `show_shipping()` returns false. This can // happen if an address is required, or through the use of hooks. This tracks if shipping has actually been // calculated so we can avoid returning costs and rates prematurely. $has_calculated_shipping = $cart->show_shipping(); // Get shipping packages to return in the response from the cart. $shipping_packages = $has_calculated_shipping ? $controller->get_shipping_packages() : []; return [ 'coupons' => $this->get_item_responses_from_schema( $this->coupon_schema, $cart->get_applied_coupons() ), 'shipping_rates' => $this->get_item_responses_from_schema( $this->shipping_rate_schema, $shipping_packages ), 'shipping_address' => $this->shipping_address_schema->get_item_response( wc()->customer ), 'billing_address' => $this->billing_address_schema->get_item_response( wc()->customer ), 'items' => $this->get_item_responses_from_schema( $this->item_schema, $cart->get_cart() ), 'items_count' => $cart->get_cart_contents_count(), 'items_weight' => wc_get_weight( $cart->get_cart_contents_weight(), 'g' ), 'needs_payment' => $cart->needs_payment(), 'needs_shipping' => $cart->needs_shipping(), 'has_calculated_shipping' => $has_calculated_shipping, 'fees' => $this->get_item_responses_from_schema( $this->fee_schema, $cart->get_fees() ), 'totals' => (object) $this->prepare_currency_response( [ 'total_items' => $this->prepare_money_response( $cart->get_subtotal(), wc_get_price_decimals() ), 'total_items_tax' => $this->prepare_money_response( $cart->get_subtotal_tax(), wc_get_price_decimals() ), 'total_fees' => $this->prepare_money_response( $cart->get_fee_total(), wc_get_price_decimals() ), 'total_fees_tax' => $this->prepare_money_response( $cart->get_fee_tax(), wc_get_price_decimals() ), 'total_discount' => $this->prepare_money_response( $cart->get_discount_total(), wc_get_price_decimals() ), 'total_discount_tax' => $this->prepare_money_response( $cart->get_discount_tax(), wc_get_price_decimals() ), 'total_shipping' => $has_calculated_shipping ? $this->prepare_money_response( $cart->get_shipping_total(), wc_get_price_decimals() ) : null, 'total_shipping_tax' => $has_calculated_shipping ? $this->prepare_money_response( $cart->get_shipping_tax(), wc_get_price_decimals() ) : null, // Explicitly request context='edit'; default ('view') will render total as markup. 'total_price' => $this->prepare_money_response( $cart->get_total( 'edit' ), wc_get_price_decimals() ), 'total_tax' => $this->prepare_money_response( $cart->get_total_tax(), wc_get_price_decimals() ), 'tax_lines' => $this->get_tax_lines( $cart ), ] ), 'errors' => $cart_errors, 'payment_requirements' => $this->extend->get_payment_requirements(), 'generated_timestamp' => time(), self::EXTENDING_KEY => $this->get_extended_data( self::IDENTIFIER ), ]; } /** * Get tax lines from the cart and format to match schema. * * @param \WC_Cart $cart Cart class instance. * @return array */ protected function get_tax_lines( $cart ) { $cart_tax_totals = $cart->get_tax_totals(); $tax_lines = []; foreach ( $cart_tax_totals as $cart_tax_total ) { $tax_lines[] = array( 'name' => $cart_tax_total->label, 'price' => $this->prepare_money_response( $cart_tax_total->amount, wc_get_price_decimals() ), 'rate' => WC_Tax::get_rate_percent( $cart_tax_total->tax_rate_id ), ); } return $tax_lines; } /** * Get cart validation errors. * * @param \WC_Cart $cart Cart class instance. * @return array */ protected function get_cart_errors( $cart ) { $controller = new CartController(); $item_errors = array_filter( $controller->get_cart_item_errors(), function ( WP_Error $error ) { return $error->has_errors(); } ); $coupon_errors = $controller->get_cart_coupon_errors(); return array_values( array_map( [ $this->error_schema, 'get_item_response' ], array_merge( $item_errors, $coupon_errors ) ) ); } } woocommerce-blocks/src/StoreApi/Schemas/CartItemSchema.php 0000644 00000040464 15133551764 0017622 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; use Automattic\WooCommerce\Checkout\Helpers\ReserveStock; /** * CartItemSchema class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. * @since 2.5.0 */ class CartItemSchema extends ProductSchema { /** * The schema item name. * * @var string */ protected $title = 'cart_item'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'cart-item'; /** * Cart schema properties. * * @return array */ public function get_properties() { return [ 'key' => [ 'description' => __( 'Unique identifier for the item within the cart.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'id' => [ 'description' => __( 'The cart item product or variation ID.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'quantity' => [ 'description' => __( 'Quantity of this item in the cart.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'quantity_limit' => [ 'description' => __( 'The maximum quantity than can be added to the cart at once.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'Product name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'short_description' => [ 'description' => __( 'Product short description in HTML format.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'description' => [ 'description' => __( 'Product full description in HTML format.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'sku' => [ 'description' => __( 'Stock keeping unit, if applicable.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'low_stock_remaining' => [ 'description' => __( 'Quantity left in stock if stock is low, or null if not applicable.', 'woocommerce' ), 'type' => [ 'integer', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'backorders_allowed' => [ 'description' => __( 'True if backorders are allowed past stock availability.', 'woocommerce' ), 'type' => [ 'boolean' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'show_backorder_badge' => [ 'description' => __( 'True if the product is on backorder.', 'woocommerce' ), 'type' => [ 'boolean' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'sold_individually' => [ 'description' => __( 'If true, only one item of this product is allowed for purchase in a single order.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'permalink' => [ 'description' => __( 'Product URL.', 'woocommerce' ), 'type' => 'string', 'format' => 'uri', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'images' => [ 'description' => __( 'List of images.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => $this->image_attachment_schema->get_properties(), ], ], 'variation' => [ 'description' => __( 'Chosen attributes (for variations).', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'attribute' => [ 'description' => __( 'Variation attribute name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'value' => [ 'description' => __( 'Variation attribute value.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], 'item_data' => [ 'description' => __( 'Metadata related to the cart item', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'name' => [ 'description' => __( 'Name of the metadata.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'value' => [ 'description' => __( 'Value of the metadata.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'display' => [ 'description' => __( 'Optionally, how the metadata value should be displayed to the user.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], 'prices' => [ 'description' => __( 'Price data for the product in the current line item, including or excluding taxes based on the "display prices during cart and checkout" setting. Provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( $this->get_store_currency_properties(), [ 'price' => [ 'description' => __( 'Current product price.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'regular_price' => [ 'description' => __( 'Regular product price.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'sale_price' => [ 'description' => __( 'Sale product price, if applicable.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'price_range' => [ 'description' => __( 'Price range, if applicable.', 'woocommerce' ), 'type' => [ 'object', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => [ 'min_amount' => [ 'description' => __( 'Price amount.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'max_amount' => [ 'description' => __( 'Price amount.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], 'raw_prices' => [ 'description' => __( 'Raw unrounded product prices used in calculations. Provided using a higher unit of precision than the currency.', 'woocommerce' ), 'type' => [ 'object', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => [ 'precision' => [ 'description' => __( 'Decimal precision of the returned prices.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'price' => [ 'description' => __( 'Current product price.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'regular_price' => [ 'description' => __( 'Regular product price.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'sale_price' => [ 'description' => __( 'Sale product price, if applicable.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ] ), ], 'totals' => [ 'description' => __( 'Item total amounts provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( $this->get_store_currency_properties(), [ 'line_subtotal' => [ 'description' => __( 'Line subtotal (the price of the product before coupon discounts have been applied).', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'line_subtotal_tax' => [ 'description' => __( 'Line subtotal tax.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'line_total' => [ 'description' => __( 'Line total (the price of the product after coupon discounts have been applied).', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'line_total_tax' => [ 'description' => __( 'Line total tax.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ] ), ], 'catalog_visibility' => [ 'description' => __( 'Whether the product is visible in the catalog', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], self::EXTENDING_KEY => $this->get_extended_schema( self::IDENTIFIER ), ]; } /** * Convert a WooCommerce cart item to an object suitable for the response. * * @param array $cart_item Cart item array. * @return array */ public function get_item_response( $cart_item ) { $product = $cart_item['data']; return [ 'key' => $cart_item['key'], 'id' => $product->get_id(), 'quantity' => wc_stock_amount( $cart_item['quantity'] ), 'quantity_limit' => $this->get_product_quantity_limit( $product ), 'name' => $this->prepare_html_response( $product->get_title() ), 'short_description' => $this->prepare_html_response( wc_format_content( wp_kses_post( $product->get_short_description() ) ) ), 'description' => $this->prepare_html_response( wc_format_content( wp_kses_post( $product->get_description() ) ) ), 'sku' => $this->prepare_html_response( $product->get_sku() ), 'low_stock_remaining' => $this->get_low_stock_remaining( $product ), 'backorders_allowed' => (bool) $product->backorders_allowed(), 'show_backorder_badge' => (bool) $product->backorders_require_notification() && $product->is_on_backorder( $cart_item['quantity'] ), 'sold_individually' => $product->is_sold_individually(), 'permalink' => $product->get_permalink(), 'images' => $this->get_images( $product ), 'variation' => $this->format_variation_data( $cart_item['variation'], $product ), 'item_data' => $this->get_item_data( $cart_item ), 'prices' => (object) $this->prepare_product_price_response( $product, get_option( 'woocommerce_tax_display_cart' ) ), 'totals' => (object) $this->prepare_currency_response( [ 'line_subtotal' => $this->prepare_money_response( $cart_item['line_subtotal'], wc_get_price_decimals() ), 'line_subtotal_tax' => $this->prepare_money_response( $cart_item['line_subtotal_tax'], wc_get_price_decimals() ), 'line_total' => $this->prepare_money_response( $cart_item['line_total'], wc_get_price_decimals() ), 'line_total_tax' => $this->prepare_money_response( $cart_item['line_tax'], wc_get_price_decimals() ), ] ), 'catalog_visibility' => $product->get_catalog_visibility(), self::EXTENDING_KEY => $this->get_extended_data( self::IDENTIFIER, $cart_item ), ]; } /** * Get an array of pricing data. * * @param \WC_Product $product Product instance. * @param string $tax_display_mode If returned prices are incl or excl of tax. * @return array */ protected function prepare_product_price_response( \WC_Product $product, $tax_display_mode = '' ) { $tax_display_mode = $this->get_tax_display_mode( $tax_display_mode ); $price_function = $this->get_price_function_from_tax_display_mode( $tax_display_mode ); $prices = parent::prepare_product_price_response( $product, $tax_display_mode ); // Add raw prices (prices with greater precision). $prices['raw_prices'] = [ 'precision' => wc_get_rounding_precision(), 'price' => $this->prepare_money_response( $price_function( $product ), wc_get_rounding_precision() ), 'regular_price' => $this->prepare_money_response( $price_function( $product, [ 'price' => $product->get_regular_price() ] ), wc_get_rounding_precision() ), 'sale_price' => $this->prepare_money_response( $price_function( $product, [ 'price' => $product->get_sale_price() ] ), wc_get_rounding_precision() ), ]; return $prices; } /** * Returns the remaining stock for a product if it has stock. * * This also factors in draft orders. * * @param \WC_Product $product Product instance. * @return integer|null */ protected function get_remaining_stock( \WC_Product $product ) { if ( is_null( $product->get_stock_quantity() ) ) { return null; } $draft_order = wc()->session->get( 'store_api_draft_order', 0 ); $reserve_stock = new ReserveStock(); $reserved_stock = $reserve_stock->get_reserved_stock( $product, $draft_order ); return $product->get_stock_quantity() - $reserved_stock; } /** * Format variation data, for example convert slugs such as attribute_pa_size to Size. * * @param array $variation_data Array of data from the cart. * @param \WC_Product $product Product data. * @return array */ protected function format_variation_data( $variation_data, $product ) { $return = []; if ( ! is_iterable( $variation_data ) ) { return $return; } foreach ( $variation_data as $key => $value ) { $taxonomy = wc_attribute_taxonomy_name( str_replace( 'attribute_pa_', '', urldecode( $key ) ) ); if ( taxonomy_exists( $taxonomy ) ) { // If this is a term slug, get the term's nice name. $term = get_term_by( 'slug', $value, $taxonomy ); if ( ! is_wp_error( $term ) && $term && $term->name ) { $value = $term->name; } $label = wc_attribute_label( $taxonomy ); } else { // If this is a custom option slug, get the options name. $value = apply_filters( 'woocommerce_variation_option_name', $value, null, $taxonomy, $product ); $label = wc_attribute_label( str_replace( 'attribute_', '', $key ), $product ); } $return[] = [ 'attribute' => $this->prepare_html_response( $label ), 'value' => $this->prepare_html_response( $value ), ]; } return $return; } /** * Format cart item data removing any HTML tag. * * @param array $cart_item Cart item array. * @return array */ protected function get_item_data( $cart_item ) { $item_data = apply_filters( 'woocommerce_get_item_data', array(), $cart_item ); return array_map( [ $this, 'format_item_data_element' ], $item_data ); } /** * Remove HTML tags from cart item data and set the `hidden` property to * `__experimental_woocommerce_blocks_hidden`. * * @param array $item_data_element Individual element of a cart item data. * @return array */ protected function format_item_data_element( $item_data_element ) { if ( array_key_exists( '__experimental_woocommerce_blocks_hidden', $item_data_element ) ) { $item_data_element['hidden'] = $item_data_element['__experimental_woocommerce_blocks_hidden']; } return array_map( 'wp_strip_all_tags', $item_data_element ); } } woocommerce-blocks/src/StoreApi/Schemas/ProductSchema.php 0000644 00000066750 15133551764 0017540 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi; /** * ProductSchema class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. * @since 2.5.0 */ class ProductSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'product'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'product'; /** * Image attachment schema instance. * * @var ImageAttachmentSchema */ protected $image_attachment_schema; /** * Constructor. * * @param ExtendRestApi $extend Rest Extending instance. * @param ImageAttachmentSchema $image_attachment_schema Image attachment schema instance. */ public function __construct( ExtendRestApi $extend, ImageAttachmentSchema $image_attachment_schema ) { $this->image_attachment_schema = $image_attachment_schema; parent::__construct( $extend ); } /** * Product schema properties. * * @return array */ public function get_properties() { return [ 'id' => [ 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'Product name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'parent' => [ 'description' => __( 'ID of the parent product, if applicable.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'type' => [ 'description' => __( 'Product type.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'variation' => [ 'description' => __( 'Product variation attributes, if applicable.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'permalink' => [ 'description' => __( 'Product URL.', 'woocommerce' ), 'type' => 'string', 'format' => 'uri', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'short_description' => [ 'description' => __( 'Product short description in HTML format.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'description' => [ 'description' => __( 'Product full description in HTML format.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'on_sale' => [ 'description' => __( 'Is the product on sale?', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'sku' => [ 'description' => __( 'Unique identifier.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'prices' => [ 'description' => __( 'Price data provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( $this->get_store_currency_properties(), [ 'price' => [ 'description' => __( 'Current product price.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'regular_price' => [ 'description' => __( 'Regular product price.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'sale_price' => [ 'description' => __( 'Sale product price, if applicable.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'price_range' => [ 'description' => __( 'Price range, if applicable.', 'woocommerce' ), 'type' => [ 'object', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => [ 'min_amount' => [ 'description' => __( 'Price amount.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'max_amount' => [ 'description' => __( 'Price amount.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ] ), ], 'price_html' => array( 'description' => __( 'Price string formatted as HTML.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'average_rating' => [ 'description' => __( 'Reviews average rating.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'review_count' => [ 'description' => __( 'Amount of reviews that the product has.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'images' => [ 'description' => __( 'List of images.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => $this->image_attachment_schema->get_properties(), ], ], 'categories' => [ 'description' => __( 'List of categories, if applicable.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'description' => __( 'Category ID', 'woocommerce' ), 'type' => 'number', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'Category name', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'slug' => [ 'description' => __( 'Category slug', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'link' => [ 'description' => __( 'Category link', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], 'tags' => [ 'description' => __( 'List of tags, if applicable.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'description' => __( 'Tag ID', 'woocommerce' ), 'type' => 'number', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'Tag name', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'slug' => [ 'description' => __( 'Tag slug', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'link' => [ 'description' => __( 'Tag link', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], 'attributes' => [ 'description' => __( 'List of attributes assigned to the product/variation that are visible or used for variations.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'description' => __( 'The attribute ID, or 0 if the attribute is not taxonomy based.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'The attribute name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'taxonomy' => [ 'description' => __( 'The attribute taxonomy, or null if the attribute is not taxonomy based.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'has_variations' => [ 'description' => __( 'True if this attribute is used by product variations.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'terms' => [ 'description' => __( 'List of assigned attribute terms.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'description' => __( 'The term ID, or 0 if the attribute is not a global attribute.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'The term name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'slug' => [ 'description' => __( 'The term slug.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'default' => [ 'description' => __( 'If this is a default attribute', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], ], ], ], 'variations' => [ 'description' => __( 'List of variation IDs, if applicable.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => [ 'id' => [ 'description' => __( 'The attribute ID, or 0 if the attribute is not taxonomy based.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'attributes' => [ 'description' => __( 'List of variation attributes.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => [ 'name' => [ 'description' => __( 'The attribute name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'value' => [ 'description' => __( 'The assigned attribute.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], ], ], ], 'has_options' => [ 'description' => __( 'Does the product have additional options before it can be added to the cart?', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'is_purchasable' => [ 'description' => __( 'Is the product purchasable?', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'is_in_stock' => [ 'description' => __( 'Is the product in stock?', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'is_on_backorder' => [ 'description' => __( 'Is the product stock backordered? This will also return false if backorder notifications are turned off.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'low_stock_remaining' => [ 'description' => __( 'Quantity left in stock if stock is low, or null if not applicable.', 'woocommerce' ), 'type' => [ 'integer', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'sold_individually' => [ 'description' => __( 'If true, only one item of this product is allowed for purchase in a single order.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'quantity_limit' => [ 'description' => __( 'The maximum quantity than can be added to the cart at once.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'add_to_cart' => [ 'description' => __( 'Add to cart button parameters.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => [ 'text' => [ 'description' => __( 'Button text.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'description' => [ 'description' => __( 'Button description.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'url' => [ 'description' => __( 'Add to cart URL.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ]; } /** * Convert a WooCommerce product into an object suitable for the response. * * @param \WC_Product $product Product instance. * @return array */ public function get_item_response( $product ) { return [ 'id' => $product->get_id(), 'name' => $this->prepare_html_response( $product->get_title() ), 'parent' => $product->get_parent_id(), 'type' => $product->get_type(), 'variation' => $this->prepare_html_response( $product->is_type( 'variation' ) ? wc_get_formatted_variation( $product, true, true, false ) : '' ), 'permalink' => $product->get_permalink(), 'sku' => $this->prepare_html_response( $product->get_sku() ), 'short_description' => $this->prepare_html_response( wc_format_content( wp_kses_post( $product->get_short_description() ) ) ), 'description' => $this->prepare_html_response( wc_format_content( wp_kses_post( $product->get_description() ) ) ), 'on_sale' => $product->is_on_sale(), 'prices' => (object) $this->prepare_product_price_response( $product ), 'price_html' => $this->prepare_html_response( $product->get_price_html() ), 'average_rating' => (string) $product->get_average_rating(), 'review_count' => $product->get_review_count(), 'images' => $this->get_images( $product ), 'categories' => $this->get_term_list( $product, 'product_cat' ), 'tags' => $this->get_term_list( $product, 'product_tag' ), 'attributes' => $this->get_attributes( $product ), 'variations' => $this->get_variations( $product ), 'has_options' => $product->has_options(), 'is_purchasable' => $product->is_purchasable(), 'is_in_stock' => $product->is_in_stock(), 'is_on_backorder' => 'onbackorder' === $product->get_stock_status(), 'low_stock_remaining' => $this->get_low_stock_remaining( $product ), 'sold_individually' => $product->is_sold_individually(), 'quantity_limit' => $this->get_product_quantity_limit( $product ), 'add_to_cart' => (object) $this->prepare_html_response( [ 'text' => $product->add_to_cart_text(), 'description' => $product->add_to_cart_description(), 'url' => $product->add_to_cart_url(), ] ), ]; } /** * Get list of product images. * * @param \WC_Product $product Product instance. * @return array */ protected function get_images( \WC_Product $product ) { $attachment_ids = array_merge( [ $product->get_image_id() ], $product->get_gallery_image_ids() ); return array_filter( array_map( [ $this->image_attachment_schema, 'get_item_response' ], $attachment_ids ) ); } /** * Gets remaining stock amount for a product. * * @param \WC_Product $product Product instance. * @return integer|null */ protected function get_remaining_stock( \WC_Product $product ) { if ( is_null( $product->get_stock_quantity() ) ) { return null; } return $product->get_stock_quantity(); } /** * If a product has low stock, return the remaining stock amount for display. * * @param \WC_Product $product Product instance. * @return integer|null */ protected function get_low_stock_remaining( \WC_Product $product ) { $remaining_stock = $this->get_remaining_stock( $product ); if ( ! is_null( $remaining_stock ) && $remaining_stock <= wc_get_low_stock_amount( $product ) ) { return max( $remaining_stock, 0 ); } return null; } /** * Get the quantity limit for an item in the cart. * * @param \WC_Product $product Product instance. * @return int */ protected function get_product_quantity_limit( \WC_Product $product ) { $limits = [ 99 ]; if ( $product->is_sold_individually() ) { $limits[] = 1; } elseif ( ! $product->backorders_allowed() ) { $limits[] = $this->get_remaining_stock( $product ); } return apply_filters( 'woocommerce_store_api_product_quantity_limit', max( min( array_filter( $limits ) ), 1 ), $product ); } /** * Returns true if the given attribute is valid. * * @param mixed $attribute Object or variable to check. * @return boolean */ protected function filter_valid_attribute( $attribute ) { return is_a( $attribute, '\WC_Product_Attribute' ); } /** * Returns true if the given attribute is valid and used for variations. * * @param mixed $attribute Object or variable to check. * @return boolean */ protected function filter_variation_attribute( $attribute ) { return $this->filter_valid_attribute( $attribute ) && $attribute->get_variation(); } /** * Get variation IDs and attributes from the DB. * * @param \WC_Product $product Product instance. * @returns array */ protected function get_variations( \WC_Product $product ) { if ( ! $product->is_type( 'variable' ) ) { return []; } global $wpdb; $variation_ids = $product->get_visible_children(); if ( ! count( $variation_ids ) ) { return []; } $attributes = array_filter( $product->get_attributes(), [ $this, 'filter_variation_attribute' ] ); $default_variation_meta_data = array_reduce( $attributes, function( $defaults, $attribute ) use ( $product ) { $meta_key = wc_variation_attribute_name( $attribute->get_name() ); $defaults[ $meta_key ] = [ 'name' => wc_attribute_label( $attribute->get_name(), $product ), 'value' => null, ]; return $defaults; }, [] ); // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared $variation_meta_data = $wpdb->get_results( " SELECT post_id as variation_id, meta_key as attribute_key, meta_value as attribute_value FROM {$wpdb->postmeta} WHERE post_id IN (" . implode( ',', array_map( 'esc_sql', $variation_ids ) ) . ") AND meta_key IN ('" . implode( "','", array_map( 'esc_sql', array_keys( $default_variation_meta_data ) ) ) . "') " ); // phpcs:enable $attributes_by_variation = array_reduce( $variation_meta_data, function( $values, $data ) { $values[ $data->variation_id ][ $data->attribute_key ] = $data->attribute_value; return $values; }, array_fill_keys( $variation_ids, [] ) ); $variations = []; foreach ( $variation_ids as $variation_id ) { $attribute_data = $default_variation_meta_data; foreach ( $attributes_by_variation[ $variation_id ] as $meta_key => $meta_value ) { if ( '' !== $meta_value ) { $attribute_data[ $meta_key ]['value'] = $meta_value; } } $variations[] = (object) [ 'id' => $variation_id, 'attributes' => array_values( $attribute_data ), ]; } return $variations; } /** * Get list of product attributes and attribute terms. * * @param \WC_Product $product Product instance. * @return array */ protected function get_attributes( \WC_Product $product ) { $attributes = array_filter( $product->get_attributes(), [ $this, 'filter_valid_attribute' ] ); $default_attributes = $product->get_default_attributes(); $return = []; foreach ( $attributes as $attribute_slug => $attribute ) { // Only visible and variation attributes will be exposed by this API. if ( ! $attribute->get_visible() || ! $attribute->get_variation() ) { continue; } $terms = $attribute->is_taxonomy() ? array_map( [ $this, 'prepare_product_attribute_taxonomy_value' ], $attribute->get_terms() ) : array_map( [ $this, 'prepare_product_attribute_value' ], $attribute->get_options() ); // Custom attribute names are sanitized to be the array keys. // So when we do the array_key_exists check below we also need to sanitize the attribute names. $sanitized_attribute_name = sanitize_key( $attribute->get_name() ); if ( array_key_exists( $sanitized_attribute_name, $default_attributes ) ) { foreach ( $terms as $term ) { $term->default = $term->slug === $default_attributes[ $sanitized_attribute_name ]; } } $return[] = (object) [ 'id' => $attribute->get_id(), 'name' => wc_attribute_label( $attribute->get_name(), $product ), 'taxonomy' => $attribute->is_taxonomy() ? $attribute->get_name() : null, 'has_variations' => true === $attribute->get_variation(), 'terms' => $terms, ]; } return $return; } /** * Prepare an attribute term for the response. * * @param \WP_Term $term Term object. * @return object */ protected function prepare_product_attribute_taxonomy_value( \WP_Term $term ) { return $this->prepare_product_attribute_value( $term->name, $term->term_id, $term->slug ); } /** * Prepare an attribute term for the response. * * @param string $name Attribute term name. * @param int $id Attribute term ID. * @param string $slug Attribute term slug. * @return object */ protected function prepare_product_attribute_value( $name, $id = 0, $slug = '' ) { return (object) [ 'id' => (int) $id, 'name' => $name, 'slug' => $slug ? $slug : $name, ]; } /** * Get an array of pricing data. * * @param \WC_Product $product Product instance. * @param string $tax_display_mode If returned prices are incl or excl of tax. * @return array */ protected function prepare_product_price_response( \WC_Product $product, $tax_display_mode = '' ) { $prices = []; $tax_display_mode = $this->get_tax_display_mode( $tax_display_mode ); $price_function = $this->get_price_function_from_tax_display_mode( $tax_display_mode ); // If we have a variable product, get the price from the variations (this will use the min value). if ( $product->is_type( 'variable' ) ) { $regular_price = $product->get_variation_regular_price(); $sale_price = $product->get_variation_sale_price(); } else { $regular_price = $product->get_regular_price(); $sale_price = $product->get_sale_price(); } $prices['price'] = $this->prepare_money_response( $price_function( $product ), wc_get_price_decimals() ); $prices['regular_price'] = $this->prepare_money_response( $price_function( $product, [ 'price' => $regular_price ] ), wc_get_price_decimals() ); $prices['sale_price'] = $this->prepare_money_response( $price_function( $product, [ 'price' => $sale_price ] ), wc_get_price_decimals() ); $prices['price_range'] = $this->get_price_range( $product, $tax_display_mode ); return $this->prepare_currency_response( $prices ); } /** * WooCommerce can return prices including or excluding tax; choose the correct method based on tax display mode. * * @param string $tax_display_mode Provided tax display mode. * @return string Valid tax display mode. */ protected function get_tax_display_mode( $tax_display_mode = '' ) { return in_array( $tax_display_mode, [ 'incl', 'excl' ], true ) ? $tax_display_mode : get_option( 'woocommerce_tax_display_shop' ); } /** * WooCommerce can return prices including or excluding tax; choose the correct method based on tax display mode. * * @param string $tax_display_mode If returned prices are incl or excl of tax. * @return string Function name. */ protected function get_price_function_from_tax_display_mode( $tax_display_mode ) { return 'incl' === $tax_display_mode ? 'wc_get_price_including_tax' : 'wc_get_price_excluding_tax'; } /** * Get price range from certain product types. * * @param \WC_Product $product Product instance. * @param string $tax_display_mode If returned prices are incl or excl of tax. * @return object|null */ protected function get_price_range( \WC_Product $product, $tax_display_mode = '' ) { $tax_display_mode = $this->get_tax_display_mode( $tax_display_mode ); if ( $product->is_type( 'variable' ) ) { $prices = $product->get_variation_prices( true ); if ( ! empty( $prices['price'] ) && ( min( $prices['price'] ) !== max( $prices['price'] ) ) ) { return (object) [ 'min_amount' => $this->prepare_money_response( min( $prices['price'] ), wc_get_price_decimals() ), 'max_amount' => $this->prepare_money_response( max( $prices['price'] ), wc_get_price_decimals() ), ]; } } if ( $product->is_type( 'grouped' ) ) { $children = array_filter( array_map( 'wc_get_product', $product->get_children() ), 'wc_products_array_filter_visible_grouped' ); $price_function = 'incl' === $tax_display_mode ? 'wc_get_price_including_tax' : 'wc_get_price_excluding_tax'; foreach ( $children as $child ) { if ( '' !== $child->get_price() ) { $child_prices[] = $price_function( $child ); } } if ( ! empty( $child_prices ) ) { return (object) [ 'min_amount' => $this->prepare_money_response( min( $child_prices ), wc_get_price_decimals() ), 'max_amount' => $this->prepare_money_response( max( $child_prices ), wc_get_price_decimals() ), ]; } } return null; } /** * Returns a list of terms assigned to the product. * * @param \WC_Product $product Product object. * @param string $taxonomy Taxonomy name. * @return array Array of terms (id, name, slug). */ protected function get_term_list( \WC_Product $product, $taxonomy = '' ) { if ( ! $taxonomy ) { return []; } $terms = get_the_terms( $product->get_id(), $taxonomy ); if ( ! $terms || is_wp_error( $terms ) ) { return []; } $return = []; $default_category = (int) get_option( 'default_product_cat', 0 ); foreach ( $terms as $term ) { $link = get_term_link( $term, $taxonomy ); if ( is_wp_error( $link ) ) { continue; } if ( $term->term_id === $default_category ) { continue; } $return[] = (object) [ 'id' => $term->term_id, 'name' => $term->name, 'slug' => $term->slug, 'link' => $link, ]; } return $return; } } woocommerce-blocks/src/StoreApi/Schemas/CartCouponSchema.php 0000644 00000006600 15133551764 0020161 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\CartController; /** * CartCouponSchema class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. * @since 2.5.0 * @since 3.9.0 Coupon type (`discount_type`) added. */ class CartCouponSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'cart_coupon'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'cart-coupon'; /** * Cart schema properties. * * @return array */ public function get_properties() { return [ 'code' => [ 'description' => __( 'The coupon\'s unique code.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'arg_options' => [ 'sanitize_callback' => 'wc_format_coupon_code', 'validate_callback' => [ $this, 'coupon_exists' ], ], ], 'discount_type' => [ 'description' => __( 'The discount type for the coupon (e.g. percentage or fixed amount)', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'arg_options' => [ 'validate_callback' => [ $this, 'coupon_exists' ], ], ], 'totals' => [ 'description' => __( 'Total amounts provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( $this->get_store_currency_properties(), [ 'total_discount' => [ 'description' => __( 'Total discount applied by this coupon.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_discount_tax' => [ 'description' => __( 'Total tax removed due to discount applied by this coupon.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ] ), ], ]; } /** * Check given coupon exists. * * @param string $coupon_code Coupon code. * @return bool */ public function coupon_exists( $coupon_code ) { $coupon = new \WC_Coupon( $coupon_code ); return (bool) $coupon->get_id() || $coupon->get_virtual(); } /** * Generate a response from passed coupon code. * * @param string $coupon_code Coupon code from the cart. * @return array */ public function get_item_response( $coupon_code ) { $controller = new CartController(); $cart = $controller->get_cart_instance(); $total_discounts = $cart->get_coupon_discount_totals(); $total_discount_taxes = $cart->get_coupon_discount_tax_totals(); $coupon = new \WC_Coupon( $coupon_code ); return [ 'code' => $coupon_code, 'discount_type' => $coupon->get_discount_type(), 'totals' => (object) $this->prepare_currency_response( [ 'total_discount' => $this->prepare_money_response( isset( $total_discounts[ $coupon_code ] ) ? $total_discounts[ $coupon_code ] : 0, wc_get_price_decimals() ), 'total_discount_tax' => $this->prepare_money_response( isset( $total_discount_taxes[ $coupon_code ] ) ? $total_discount_taxes[ $coupon_code ] : 0, wc_get_price_decimals(), PHP_ROUND_HALF_DOWN ), ] ), ]; } } woocommerce-blocks/src/StoreApi/Schemas/AbstractAddressSchema.php 0000644 00000014645 15133551764 0021165 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; use Automattic\WooCommerce\Blocks\RestApi\Routes; /** * AddressSchema class. * * Provides a generic address schema for composition in other schemas. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. * @since 4.1.0 */ abstract class AbstractAddressSchema extends AbstractSchema { /** * Term properties. * * @internal Note that required properties don't require values, just that they are included in the request. * @return array */ public function get_properties() { return [ 'first_name' => [ 'description' => __( 'First name', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], 'last_name' => [ 'description' => __( 'Last name', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], 'company' => [ 'description' => __( 'Company', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], 'address_1' => [ 'description' => __( 'Address', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], 'address_2' => [ 'description' => __( 'Apartment, suite, etc.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], 'city' => [ 'description' => __( 'City', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], 'state' => [ 'description' => __( 'State/County code, or name of the state, county, province, or district.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], 'postcode' => [ 'description' => __( 'Postal code', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], 'country' => [ 'description' => __( 'Country/Region code in ISO 3166-1 alpha-2 format.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], 'phone' => [ 'description' => __( 'Phone', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => true, ], ]; } /** * Sanitize and format the given address object. * * @param array $address Value being sanitized. * @param \WP_REST_Request $request The Request. * @param string $param The param being sanitized. * @return array */ public function sanitize_callback( $address, $request, $param ) { $address = array_merge( array_fill_keys( array_keys( $this->get_properties() ), '' ), (array) $address ); $address['country'] = wc_strtoupper( wc_clean( wp_unslash( $address['country'] ) ) ); $address['first_name'] = wc_clean( wp_unslash( $address['first_name'] ) ); $address['last_name'] = wc_clean( wp_unslash( $address['last_name'] ) ); $address['company'] = wc_clean( wp_unslash( $address['company'] ) ); $address['address_1'] = wc_clean( wp_unslash( $address['address_1'] ) ); $address['address_2'] = wc_clean( wp_unslash( $address['address_2'] ) ); $address['city'] = wc_clean( wp_unslash( $address['city'] ) ); $address['state'] = $this->format_state( wc_clean( wp_unslash( $address['state'] ) ), $address['country'] ); $address['postcode'] = $address['postcode'] ? wc_format_postcode( wc_clean( wp_unslash( $address['postcode'] ) ), $address['country'] ) : ''; $address['phone'] = wc_clean( wp_unslash( $address['phone'] ) ); return $address; } /** * Format a state based on the country. If country has defined states, will return an upper case state code. * * @param string $state State name or code (sanitized). * @param string $country Country code. * @return string */ protected function format_state( $state, $country ) { $states = $country ? array_filter( (array) wc()->countries->get_states( $country ) ) : []; if ( count( $states ) ) { $state = wc_strtoupper( $state ); $state_values = array_map( 'wc_strtoupper', array_flip( array_map( 'wc_strtoupper', $states ) ) ); if ( isset( $state_values[ $state ] ) ) { // Convert to state code if a state name was provided. return $state_values[ $state ]; } } return $state; } /** * Validate the given address object. * * @see rest_validate_value_from_schema * * @param array $address Value being sanitized. * @param \WP_REST_Request $request The Request. * @param string $param The param being sanitized. * @return true|\WP_Error */ public function validate_callback( $address, $request, $param ) { $errors = new \WP_Error(); $address = $this->sanitize_callback( $address, $request, $param ); if ( empty( $address['country'] ) ) { $errors->add( 'missing_country', __( 'Country is required', 'woocommerce' ) ); return $errors; } if ( ! in_array( $address['country'], array_keys( wc()->countries->get_countries() ), true ) ) { $errors->add( 'invalid_country', sprintf( /* translators: %s valid country codes */ __( 'Invalid country code provided. Must be one of: %s', 'woocommerce' ), implode( ', ', array_keys( wc()->countries->get_countries() ) ) ) ); return $errors; } $states = array_filter( array_keys( (array) wc()->countries->get_states( $address['country'] ) ) ); if ( ! empty( $address['state'] ) && count( $states ) && ! in_array( $address['state'], $states, true ) ) { $errors->add( 'invalid_state', sprintf( /* translators: %s valid states */ __( 'The provided state is not valid. Must be one of: %s', 'woocommerce' ), implode( ', ', $states ) ) ); } if ( ! empty( $address['postcode'] ) && ! \WC_Validation::is_postcode( $address['postcode'], $address['country'] ) ) { $errors->add( 'invalid_postcode', __( 'The provided postcode / ZIP is not valid', 'woocommerce' ) ); } if ( ! empty( $address['phone'] ) && ! \WC_Validation::is_phone( $address['phone'] ) ) { $errors->add( 'invalid_phone', __( 'The provided phone number is not valid', 'woocommerce' ) ); } return $errors->has_errors( $errors ) ? $errors : true; } } woocommerce-blocks/src/StoreApi/Schemas/CartFeeSchema.php 0000644 00000004453 15133551764 0017421 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi; /** * CartFeeSchema class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class CartFeeSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'cart_fee'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'cart-fee'; /** * Cart schema properties. * * @return array */ public function get_properties() { return [ 'id' => [ 'description' => __( 'Unique identifier for the fee within the cart.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'Fee name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'totals' => [ 'description' => __( 'Fee total amounts provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( $this->get_store_currency_properties(), [ 'total' => [ 'description' => __( 'Total amount for this fee.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'total_tax' => [ 'description' => __( 'Total tax amount for this fee.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ] ), ], ]; } /** * Convert a WooCommerce cart fee to an object suitable for the response. * * @param array $fee Cart fee data. * @return array */ public function get_item_response( $fee ) { return [ 'key' => $fee->id, 'name' => $this->prepare_html_response( $fee->name ), 'totals' => (object) $this->prepare_currency_response( [ 'total' => $this->prepare_money_response( $fee->total, wc_get_price_decimals() ), 'total_tax' => $this->prepare_money_response( $fee->tax, wc_get_price_decimals(), PHP_ROUND_HALF_DOWN ), ] ), ]; } } woocommerce-blocks/src/StoreApi/Schemas/ProductCollectionDataSchema.php 0000644 00000010471 15133551764 0022333 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; /** * ProductCollectionDataSchema class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. * @since 2.5.0 */ class ProductCollectionDataSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'product-collection-data'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'product-collection-data'; /** * Product collection data schema properties. * * @return array */ public function get_properties() { return [ 'price_range' => [ 'description' => __( 'Min and max prices found in collection of products, provided using the smallest unit of the currency.', 'woocommerce' ), 'type' => [ 'object', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( $this->get_store_currency_properties(), [ 'min_price' => [ 'description' => __( 'Min price found in collection of products.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'max_price' => [ 'description' => __( 'Max price found in collection of products.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ] ), ], 'attribute_counts' => [ 'description' => __( 'Returns number of products within attribute terms.', 'woocommerce' ), 'type' => [ 'array', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'term' => [ 'description' => __( 'Term ID', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'count' => [ 'description' => __( 'Number of products.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], 'rating_counts' => [ 'description' => __( 'Returns number of products with each average rating.', 'woocommerce' ), 'type' => [ 'array', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'rating' => [ 'description' => __( 'Average rating', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'count' => [ 'description' => __( 'Number of products.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], 'stock_status_counts' => [ 'description' => __( 'Returns number of products with each stock status.', 'woocommerce' ), 'type' => [ 'array', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'status' => [ 'description' => __( 'Status', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'count' => [ 'description' => __( 'Number of products.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], ]; } /** * Format data. * * @param array $data Collection data to format and return. * @return array */ public function get_item_response( $data ) { return [ 'price_range' => ! is_null( $data['min_price'] ) && ! is_null( $data['max_price'] ) ? (object) $this->prepare_currency_response( [ 'min_price' => $this->prepare_money_response( $data['min_price'], wc_get_price_decimals() ), 'max_price' => $this->prepare_money_response( $data['max_price'], wc_get_price_decimals() ), ] ) : null, 'attribute_counts' => $data['attribute_counts'], 'rating_counts' => $data['rating_counts'], 'stock_status_counts' => $data['stock_status_counts'], ]; } } woocommerce-blocks/src/StoreApi/Schemas/ErrorSchema.php 0000644 00000002411 15133551764 0017171 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; /** * ErrorSchema class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. * @since 2.5.0 */ class ErrorSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'error'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'error'; /** * Product schema properties. * * @return array */ public function get_properties() { return [ 'code' => [ 'description' => __( 'Error code', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'message' => [ 'description' => __( 'Error message', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ]; } /** * Convert a WP_Error into an object suitable for the response. * * @param \WP_Error $error Error object. * @return array */ public function get_item_response( \WP_Error $error ) { return [ 'code' => $this->prepare_html_response( $error->get_error_code() ), 'message' => $this->prepare_html_response( $error->get_error_message() ), ]; } } woocommerce-blocks/src/StoreApi/Schemas/ImageAttachmentSchema.php 0000644 00000005234 15133551764 0021141 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; /** * ImageAttachmentSchema class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class ImageAttachmentSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'image'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'image'; /** * Product schema properties. * * @return array */ public function get_properties() { return [ 'id' => [ 'description' => __( 'Image ID.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], ], 'src' => [ 'description' => __( 'Full size image URL.', 'woocommerce' ), 'type' => 'string', 'format' => 'uri', 'context' => [ 'view', 'edit' ], ], 'thumbnail' => [ 'description' => __( 'Thumbnail URL.', 'woocommerce' ), 'type' => 'string', 'format' => 'uri', 'context' => [ 'view', 'edit' ], ], 'srcset' => [ 'description' => __( 'Thumbnail srcset for responsive images.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'sizes' => [ 'description' => __( 'Thumbnail sizes for responsive images.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'name' => [ 'description' => __( 'Image name.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'alt' => [ 'description' => __( 'Image alternative text.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], ]; } /** * Convert a WooCommerce product into an object suitable for the response. * * @param int $attachment_id Image attachment ID. * @return array|null */ public function get_item_response( $attachment_id ) { if ( ! $attachment_id ) { return null; } $attachment = wp_get_attachment_image_src( $attachment_id, 'full' ); if ( ! is_array( $attachment ) ) { return []; } $thumbnail = wp_get_attachment_image_src( $attachment_id, 'woocommerce_thumbnail' ); return [ 'id' => (int) $attachment_id, 'src' => current( $attachment ), 'thumbnail' => current( $thumbnail ), 'srcset' => (string) wp_get_attachment_image_srcset( $attachment_id, 'full' ), 'sizes' => (string) wp_get_attachment_image_sizes( $attachment_id, 'full' ), 'name' => get_the_title( $attachment_id ), 'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ), ]; } } woocommerce-blocks/src/StoreApi/Schemas/CartShippingRateSchema.php 0000644 00000026476 15133551764 0021330 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; use WC_Shipping_Rate as ShippingRate; /** * CartShippingRateSchema class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class CartShippingRateSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'cart-shipping-rate'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'cart-shipping-rate'; /** * Cart schema properties. * * @return array */ public function get_properties() { return [ 'package_id' => [ 'description' => __( 'The ID of the package the shipping rates belong to.', 'woocommerce' ), 'type' => [ 'integer', 'string' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'Name of the package.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'destination' => [ 'description' => __( 'Shipping destination address.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => [ 'address_1' => [ 'description' => __( 'First line of the address being shipped to.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'address_2' => [ 'description' => __( 'Second line of the address being shipped to.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'city' => [ 'description' => __( 'City of the address being shipped to.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'state' => [ 'description' => __( 'ISO code, or name, for the state, province, or district of the address being shipped to.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'postcode' => [ 'description' => __( 'Zip or Postcode of the address being shipped to.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'country' => [ 'description' => __( 'ISO code for the country of the address being shipped to.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], 'items' => [ 'description' => __( 'List of cart items the returned shipping rates apply to.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => [ 'key' => [ 'description' => __( 'Unique identifier for the item within the cart.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'Name of the item.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'quantity' => [ 'description' => __( 'Quantity of the item in the current package.', 'woocommerce' ), 'type' => 'number', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], 'shipping_rates' => [ 'description' => __( 'List of shipping rates.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ 'type' => 'object', 'properties' => $this->get_rate_properties(), ], ], ]; } /** * Schema for a single rate. * * @return array */ protected function get_rate_properties() { return array_merge( [ 'rate_id' => [ 'description' => __( 'ID of the shipping rate.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'name' => [ 'description' => __( 'Name of the shipping rate, e.g. Express shipping.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'description' => [ 'description' => __( 'Description of the shipping rate, e.g. Dispatched via USPS.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'delivery_time' => [ 'description' => __( 'Delivery time estimate text, e.g. 3-5 business days.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'price' => [ 'description' => __( 'Price of this shipping rate using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'taxes' => [ 'description' => __( 'Taxes applied to this shipping rate using the smallest unit of the currency.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'method_id' => [ 'description' => __( 'ID of the shipping method that provided the rate.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'instance_id' => [ 'description' => __( 'Instance ID of the shipping method that provided the rate.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'meta_data' => [ 'description' => __( 'Meta data attached to the shipping rate.', 'woocommerce' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'items' => [ 'type' => 'object', 'properties' => [ 'key' => [ 'description' => __( 'Meta key.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'value' => [ 'description' => __( 'Meta value.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], ], ], 'selected' => [ 'description' => __( 'True if this is the rate currently selected by the customer for the cart.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], ], $this->get_store_currency_properties() ); } /** * Convert a shipping rate from WooCommerce into a valid response. * * @param array $package Shipping package complete with rates from WooCommerce. * @return array */ public function get_item_response( $package ) { return [ 'package_id' => $package['package_id'], 'name' => $package['package_name'], 'destination' => $this->prepare_package_destination_response( $package ), 'items' => $this->prepare_package_items_response( $package ), 'shipping_rates' => $this->prepare_package_shipping_rates_response( $package ), ]; } /** * Gets and formats the destination address of a package. * * @param array $package Shipping package complete with rates from WooCommerce. * @return object */ protected function prepare_package_destination_response( $package ) { return (object) $this->prepare_html_response( [ 'address_1' => $package['destination']['address_1'], 'address_2' => $package['destination']['address_2'], 'city' => $package['destination']['city'], 'state' => $package['destination']['state'], 'postcode' => $package['destination']['postcode'], 'country' => $package['destination']['country'], ] ); } /** * Gets items from a package and creates an array of strings containing product names and quantities. * * @param array $package Shipping package complete with rates from WooCommerce. * @return array */ protected function prepare_package_items_response( $package ) { $items = array(); foreach ( $package['contents'] as $item_id => $values ) { $items[] = [ 'key' => $item_id, 'name' => $values['data']->get_name(), 'quantity' => $values['quantity'], ]; } return $items; } /** * Prepare an array of rates from a package for the response. * * @param array $package Shipping package complete with rates from WooCommerce. * @return array */ protected function prepare_package_shipping_rates_response( $package ) { $rates = $package['rates']; $selected_rates = wc()->session->get( 'chosen_shipping_methods', array() ); $selected_rate = isset( $selected_rates[ $package['package_id'] ] ) ? $selected_rates[ $package['package_id'] ] : ''; if ( empty( $selected_rate ) && ! empty( $package['rates'] ) ) { $selected_rate = wc_get_chosen_shipping_method_for_package( $package['package_id'], $package ); } $response = []; foreach ( $package['rates'] as $rate ) { $response[] = $this->get_rate_response( $rate, $selected_rate ); } return $response; } /** * Response for a single rate. * * @param WC_Shipping_Rate $rate Rate object. * @param string $selected_rate Selected rate. * @return array */ protected function get_rate_response( $rate, $selected_rate = '' ) { return $this->prepare_currency_response( [ 'rate_id' => $this->get_rate_prop( $rate, 'id' ), 'name' => $this->prepare_html_response( $this->get_rate_prop( $rate, 'label' ) ), 'description' => $this->prepare_html_response( $this->get_rate_prop( $rate, 'description' ) ), 'delivery_time' => $this->prepare_html_response( $this->get_rate_prop( $rate, 'delivery_time' ) ), 'price' => $this->prepare_money_response( $this->get_rate_prop( $rate, 'cost' ), wc_get_price_decimals() ), 'taxes' => $this->prepare_money_response( array_sum( $this->get_rate_prop( $rate, 'taxes' ) ), wc_get_price_decimals() ), 'instance_id' => $this->get_rate_prop( $rate, 'instance_id' ), 'method_id' => $this->get_rate_prop( $rate, 'method_id' ), 'meta_data' => $this->get_rate_meta_data( $rate ), 'selected' => $selected_rate === $this->get_rate_prop( $rate, 'id' ), ] ); } /** * Gets a prop of the rate object, if callable. * * @param WC_Shipping_Rate $rate Rate object. * @param string $prop Prop name. * @return string */ protected function get_rate_prop( $rate, $prop ) { $getter = 'get_' . $prop; return \is_callable( array( $rate, $getter ) ) ? $rate->$getter() : ''; } /** * Converts rate meta data into a suitable response object. * * @param WC_Shipping_Rate $rate Rate object. * @return array */ protected function get_rate_meta_data( $rate ) { $meta_data = $rate->get_meta_data(); return array_reduce( array_keys( $meta_data ), function( $return, $key ) use ( $meta_data ) { $return[] = [ 'key' => $key, 'value' => $meta_data[ $key ], ]; return $return; }, [] ); } } woocommerce-blocks/src/StoreApi/Schemas/ProductCategorySchema.php 0000644 00000007302 15133551764 0021222 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi; /** * ProductCategorySchema class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class ProductCategorySchema extends TermSchema { /** * The schema item name. * * @var string */ protected $title = 'product-category'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'product-category'; /** * Image attachment schema instance. * * @var ImageAttachmentSchema */ protected $image_attachment_schema; /** * Constructor. * * @param ExtendRestApi $extend Rest Extending instance. * @param ImageAttachmentSchema $image_attachment_schema Image attachment schema instance. */ public function __construct( ExtendRestApi $extend, ImageAttachmentSchema $image_attachment_schema ) { $this->image_attachment_schema = $image_attachment_schema; parent::__construct( $extend ); } /** * Term properties. * * @return array */ public function get_properties() { $schema = parent::get_properties(); $schema['image'] = [ 'description' => __( 'Category image.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit', 'embed' ], 'readonly' => true, 'properties' => $this->image_attachment_schema->get_properties(), ]; $schema['review_count'] = [ 'description' => __( 'Number of reviews for products in this category.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ]; $schema['permalink'] = [ 'description' => __( 'Category URL.', 'woocommerce' ), 'type' => 'string', 'format' => 'uri', 'context' => [ 'view', 'edit', 'embed' ], 'readonly' => true, ]; return $schema; } /** * Convert a term object into an object suitable for the response. * * @param \WP_Term $term Term object. * @return array */ public function get_item_response( $term ) { $response = parent::get_item_response( $term ); $count = get_term_meta( $term->term_id, 'product_count_product_cat', true ); if ( $count ) { $response['count'] = (int) $count; } $response['image'] = $this->image_attachment_schema->get_item_response( get_term_meta( $term->term_id, 'thumbnail_id', true ) ); $response['review_count'] = $this->get_category_review_count( $term ); $response['permalink'] = get_term_link( $term->term_id, 'product_cat' ); return $response; } /** * Get total number of reviews for products in a category. * * @param \WP_Term $term Term object. * @return int */ protected function get_category_review_count( $term ) { global $wpdb; $children = get_term_children( $term->term_id, 'product_cat' ); if ( ! $children || is_wp_error( $children ) ) { $terms_to_count_str = absint( $term->term_id ); } else { $terms_to_count = array_unique( array_map( 'absint', array_merge( array( $term->term_id ), $children ) ) ); $terms_to_count_str = implode( ',', $terms_to_count ); } // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $products_of_category_sql = $wpdb->prepare( "SELECT SUM(comment_count) as review_count FROM {$wpdb->posts} AS posts INNER JOIN {$wpdb->term_relationships} AS term_relationships ON posts.ID = term_relationships.object_id WHERE term_relationships.term_taxonomy_id IN ({$terms_to_count_str})" ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $review_count = $wpdb->get_var( $products_of_category_sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared return (int) $review_count; } } woocommerce-blocks/src/StoreApi/Schemas/ProductAttributeSchema.php 0000644 00000005173 15133551764 0021414 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; /** * ProductAttributeSchema class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. * @since 2.5.0 */ class ProductAttributeSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'product_attribute'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'product-attribute'; /** * Term properties. * * @return array */ public function get_properties() { return [ 'id' => array( 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'name' => array( 'description' => __( 'Attribute name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'taxonomy' => array( 'description' => __( 'The attribute taxonomy name.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'type' => array( 'description' => __( 'Attribute type.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'order' => array( 'description' => __( 'How terms in this attribute are sorted by default.', 'woocommerce' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'has_archives' => array( 'description' => __( 'If this attribute has term archive pages.', 'woocommerce' ), 'type' => 'boolean', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'count' => array( 'description' => __( 'Number of terms in the attribute taxonomy.', 'woocommerce' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), ]; } /** * Convert an attribute object into an object suitable for the response. * * @param object $attribute Attribute object. * @return array */ public function get_item_response( $attribute ) { return [ 'id' => (int) $attribute->id, 'name' => $this->prepare_html_response( $attribute->name ), 'taxonomy' => $attribute->slug, 'type' => $attribute->type, 'order' => $attribute->order_by, 'has_archives' => $attribute->has_archives, 'count' => (int) \wp_count_terms( $attribute->slug ), ]; } } woocommerce-blocks/src/StoreApi/Schemas/CheckoutSchema.php 0000644 00000016331 15133551764 0017653 0 ustar 00 <?php namespace Automattic\WooCommerce\Blocks\StoreApi\Schemas; use Automattic\WooCommerce\Blocks\Payments\PaymentResult; use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi; /** * CheckoutSchema class. * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ class CheckoutSchema extends AbstractSchema { /** * The schema item name. * * @var string */ protected $title = 'checkout'; /** * The schema item identifier. * * @var string */ const IDENTIFIER = 'checkout'; /** * Billing address schema instance. * * @var BillingAddressSchema */ protected $billing_address_schema; /** * Shipping address schema instance. * * @var ShippingAddressSchema */ protected $shipping_address_schema; /** * Constructor. * * @param ExtendRestApi $extend Rest Extending instance. * @param BillingAddressSchema $billing_address_schema Billing address schema instance. * @param ShippingAddressSchema $shipping_address_schema Shipping address schema instance. */ public function __construct( ExtendRestApi $extend, BillingAddressSchema $billing_address_schema, ShippingAddressSchema $shipping_address_schema ) { $this->billing_address_schema = $billing_address_schema; $this->shipping_address_schema = $shipping_address_schema; parent::__construct( $extend ); } /** * Checkout schema properties. * * @return array */ public function get_properties() { return [ 'order_id' => [ 'description' => __( 'The order ID to process during checkout.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'status' => [ 'description' => __( 'Order status. Payment providers will update this value after payment.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'order_key' => [ 'description' => __( 'Order key used to check validity or protect access to certain order data.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'customer_note' => [ 'description' => __( 'Note added to the order by the customer during checkout.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], 'customer_id' => [ 'description' => __( 'Customer ID if registered. Will return 0 for guests.', 'woocommerce' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'billing_address' => [ 'description' => __( 'Billing address.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'properties' => $this->billing_address_schema->get_properties(), 'arg_options' => [ 'sanitize_callback' => [ $this->billing_address_schema, 'sanitize_callback' ], 'validate_callback' => [ $this->billing_address_schema, 'validate_callback' ], ], 'required' => true, ], 'shipping_address' => [ 'description' => __( 'Shipping address.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'properties' => $this->shipping_address_schema->get_properties(), 'arg_options' => [ 'sanitize_callback' => [ $this->shipping_address_schema, 'sanitize_callback' ], 'validate_callback' => [ $this->shipping_address_schema, 'validate_callback' ], ], 'required' => true, ], 'payment_method' => [ 'description' => __( 'The ID of the payment method being used to process the payment.', 'woocommerce' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], 'enum' => wc()->payment_gateways->get_payment_gateway_ids(), ], 'create_account' => [ 'description' => __( 'Whether to create a new user account as part of order processing.', 'woocommerce' ), 'type' => 'boolean', 'context' => [ 'view', 'edit' ], ], 'payment_result' => [ 'description' => __( 'Result of payment processing, or false if not yet processed.', 'woocommerce' ), 'type' => 'object', 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => [ 'payment_status' => [ 'description' => __( 'Status of the payment returned by the gateway. One of success, pending, failure, error.', 'woocommerce' ), 'readonly' => true, 'type' => 'string', ], 'payment_details' => [ 'description' => __( 'An array of data being returned from the payment gateway.', 'woocommerce' ), 'readonly' => true, 'type' => 'array', 'items' => [ 'type' => 'object', 'properties' => [ 'key' => [ 'type' => 'string', ], 'value' => [ 'type' => 'string', ], ], ], ], 'redirect_url' => [ 'description' => __( 'A URL to redirect the customer after checkout. This could be, for example, a link to the payment processors website.', 'woocommerce' ), 'readonly' => true, 'type' => 'string', ], ], ], self::EXTENDING_KEY => $this->get_extended_schema( self::IDENTIFIER ), ]; } /** * Return the response for checkout. * * @param object $item Results from checkout action. * @return array */ public function get_item_response( $item ) { return $this->get_checkout_response( $item->order, $item->payment_result ); } /** * Get the checkout response based on the current order and any payments. * * @param \WC_Order $order Order object. * @param PaymentResult $payment_result Payment result object. * @return array */ protected function get_checkout_response( \WC_Order $order, PaymentResult $payment_result = null ) { return [ 'order_id' => $order->get_id(), 'status' => $order->get_status(), 'order_key' => $order->get_order_key(), 'customer_note' => $order->get_customer_note(), 'customer_id' => $order->get_customer_id(), 'billing_address' => $this->billing_address_schema->get_item_response( $order ), 'shipping_address' => $this->shipping_address_schema->get_item_response( $order ), 'payment_method' => $order->get_payment_method(), 'payment_result' => [ 'payment_status' => $payment_result->status, 'payment_details' => $this->prepare_payment_details_for_response( $payment_result->payment_details ), 'redirect_url' => $payment_result->redirect_url, ], self::EXTENDING_KEY => $this->get_extended_data( self::IDENTIFIER ), ]; } /** * This prepares the payment details for the response so it's following the * schema where it's an array of objects. * * @param array $payment_details An array of payment details from the processed payment. * * @return array An array of objects where each object has the key and value * as distinct properties. */ protected function prepare_payment_details_for_response( array $payment_details ) { return array_map( function( $key, $value ) { return (object) [ 'key' => $key, 'value' => $value, ]; }, array_keys( $payment_details ), $payment_details ); } } woocommerce-blocks/global.d.ts 0000644 00000000171 15133551764 0012401 0 ustar 00 // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore declare let __webpack_public_path__: string; woocommerce-blocks/LICENSE 0000644 00000104515 15133551764 0011363 0 ustar 00 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. <one line to give the program's name and a brief idea of what it does.> Copyright (C) <year> <name of author> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: <program> Copyright (C) <year> <name of author> This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see <https://www.gnu.org/licenses/>. The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read <https://www.gnu.org/licenses/why-not-lgpl.html>. woocommerce-blocks/images/payment-methods/unionpay.svg 0000644 00000000754 15133551764 0017324 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" fill="none" aria-hidden="true" viewBox="0 0 42 16"> <path fill="#E21836" d="M13.8 1h5.8c.8 0 1.3.7 1 1.5L18 14a2 2 0 01-1.8 1.5h-5.8c-.8 0-1.3-.6-1.1-1.5L12 2.5A2 2 0 0113.8 1z"/> <path fill="#00447C" d="M19 1h6.7c.8 0 .5.7.3 1.5L23.3 14c-.2.9-.1 1.5-1 1.5h-6.6c-.8 0-1.3-.6-1.1-1.5l2.7-11.5A2 2 0 0119 1z"/> <path fill="#007B84" d="M25.5 1h5.8c.8 0 1.3.7 1 1.5L29.8 14a2 2 0 01-1.8 1.5H22c-.8 0-1.3-.6-1.1-1.5l2.7-11.5A2 2 0 0125.5 1z"/> </svg> woocommerce-blocks/images/payment-methods/alipay.svg 0000644 00000012431 15133551764 0016734 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" preserveAspectRatio="xMidYMid meet" viewBox="0 0 750 374"> <defs/> <path d="M325.8 287.4l-7.3 1.4-7.3 2.8-5.6 4.2a10.2 10.2 0 00-2.1 5.6l1.7 4.5c1.4 2.1 4.6 2.1 4.6 2.1l5.9-1.7 2-3.9-1.3-7.7 3.8-2 5.6-.8 6.7 1.8 3.4 3.8 1.4 3.9.7 3.1v5.6l-12.2 4.2L314 318l-5.2 2.5-4.2 3.1a16.4 16.4 0 00-3.1 4.6l-1 6.2c0 6.3 4.8 10.5 4.8 10.5a19.8 19.8 0 0011.9 4.2l8.7-1.4 2.8-1 9.4-6.3a9.7 9.7 0 004.2 6c2.8 2.4 8 2.4 8 2.4l5.3-.7 5.2-1.8V343h-4.2l-3.1-1-2.8-2.1-1-3.8.7-29.3-1.8-9.5-5.2-5.9-7.4-3.1-10-.7zM338 315l-.3 20.6-6 4.9-6 2h-2.4a10.3 10.3 0 01-7-2.4c-2.8-2.4-2.8-7.3-2.8-7.3a13.2 13.2 0 011.8-6.3l5.2-4.9 7.7-3.5 9.8-3.1zm23-25.8h29.3v4.2l-4.5.7s-2.8.6-2.8 1.7l.3 1 16.1 35 15.4-34.3-2.8-2.7-5.9-1.8v-3.8H430v3.8l-4.9 1.7a13.6 13.6 0 00-4.9 5.3l-13.6 29.7-16.8 32.8-6.2 7.3-5.3 3.5-5.2.7s-4.5 0-7.7-2.4a6.3 6.3 0 01-2.8-5l1.4-4.5a7.7 7.7 0 015-2l3.4.6 2.4 1.8 3.9 4.9 8-8s6.6-10.2 4.2-18.2l-19.2-41.9-4.6-4.2-6-2.1V289m-99.8-1.4l-8.4 2.2-8.4 5.9h-.7v-6.6l-1-.7-21.7 1.4v3.8l4.2.7 3.2 1 2.4 2.5 1 3.5v62.1l-.7 3.5-2.8 2.1-3.5.7H221v4.2h36v-4.2l-9.4-1.4-2.5-2-1.4-3.6v-17.4h.7l6.3 2.4 9.4 1.4h1l10.5-2 9.4-6.7 7-9.4 2.5-13-2.1-12.2a33 33 0 00-6.3-9.8 27.5 27.5 0 00-8.4-6.3l-10.7-2zm-.4 8.8h.4a20 20 0 0113.3 7.3c4.1 5.6 3.8 14 3.8 14a25.6 25.6 0 01-4.5 14.3c-4.6 6.7-12.6 7.7-12.6 7.7-7.7.7-17.5-4.5-17.5-4.5v-34.6a42.5 42.5 0 0117.1-4.2zM95 262.3l-30.4 70.5-2.8 4.9-4.2 3.5-4.2 1.7-4.2.4v4.2h35v-4.2l-9.1-1.4c-4.6-1.4-4.6-3.9-4.6-3.9l.4-2.4 6.3-17.5h32.4l7.7 19.2.4 1.4v1.4c0 1.4-3.2 1.8-3.2 1.8l-8.3 1.4v4.2h38.4v-4.2l-3.9-.4-4.2-1.4-3.1-3.1a6.7 6.7 0 01-2.4-3.5l-30.7-72.6zm-1.8 18.5h.4l13.6 32.4H79.6zm101.3 0a8 8 0 01-5.6-2.5 7.8 7.8 0 01-2.4-5.6 9.3 9.3 0 012.4-5.5 8.9 8.9 0 015.6-2.5 9.9 9.9 0 016.3 2.5l2.1 5.2-2 6a9.5 9.5 0 01-6.4 2.4m11.5 8.7V337l1 3.5 2.8 1.7 6 1.4v3.9h-31v-3.9l5.5-.7 2.8-1.7 1-3.5v-36l-1-3.8-2.8-2.5-3.5-1-4.2-.7v-3.9l22.7-1.4.7 1M166 255.7l-22.7 1.4-1.4.3v3.5h1.4l3.5.7 4.2 1.4 2.8 2.8 1 4.2v67.8l-1 3.5-2.8 1.7-6 .7v3.8h31.5v-3.8l-3.2-.3-2.8-1-2.8-1.8-.7-3.5v-80.3l-1-1m424.3 34.2l21.6-1.4 1 1v8h.4l3.8-3.4 4.6-3.2a22.8 22.8 0 015.5-2.4l7.4-.7 10.8 2.8 6.6 7.3 3.9-3.5 4.9-3.1a23.7 23.7 0 015.6-2.8l7.6-.7s8.4 0 13 4.9c0 0 4.9 4.8 4.9 14.6V337l.6 3.5 2.5 2 3.1.8 3.5.3v3.9h-31.4v-3.9l5.6-.7 2.8-1.7.7-3.5v-30s0-6.3-2.8-9.8a11.9 11.9 0 00-7.6-3.1l-6.3.7-5.3 2.4-3.1 2.8-1.8 2.8V337l.7 3.5 2.8 2 2.8.8 3.2.3v3.9H631v-3.9l5.2-.7 2.8-1.7.7-3.5v-30s0-6.3-2.8-9.8a10.8 10.8 0 00-7.6-3.1l-6 .7-5.2 2.4-3.1 3.1-2.1 2.5V337l.7 3.5 2.8 1.7 2.8 1 3.1.4v3.8h-31v-3.8l5.9-.7 2.8-1.8.7-3.4v-36.3l-1-3.5-2.5-2.5-3.2-1-3.9-.7v-3.8m-125.6 30.7s0 11.8 5.9 18.8c0 0 5.6 7.4 15 7.4l11.5-3.2 8.7-8.7 4.2 2.8a37.3 37.3 0 01-11.8 11.5 36 36 0 01-16 4.2s-14.7 0-23.5-8.7a35.4 35.4 0 01-8.7-23.8 35 35 0 012.4-11.8 35.8 35.8 0 016.7-10.5 38.5 38.5 0 0110.5-7 40 40 0 0113.6-2.8s10.1 0 16.8 4.2c0 0 6.2 4.9 6.2 11.9l-1.7 5.6-5.2 2-6.7-1.7s-2.4-1.4-2.4-3.8l1-5.6 1.1-4.5-3.9-2.5-5.5-.7-6.3 1-6 5-4.2 8.3a43.8 43.8 0 00-1.7 12.6m84.8-31.8a39.8 39.8 0 00-24.4 9c-9.1 9.1-9.1 23.5-9.1 23.5a37 37 0 002.4 13.6l7.4 10.5a38.7 38.7 0 0010.8 6.2l12.6 2.1h.3l14.4-2.4a34.1 34.1 0 0010.8-7.3l7-10.5 2-12.9a33.4 33.4 0 00-9.4-23c-9.4-8.8-24.1-8.8-24.1-8.8h-.7zm-.2 4.9h.6l9 2 6 6.4 3.1 8.7 1 10.1c0 13-4.9 20.6-4.9 20.6a19 19 0 01-14.3 7.7h-.3l-8.8-2.4-5.9-6.3-3.5-9.1a61.3 61.3 0 01-1-11.2l.7-9.4 3.1-8.4a26 26 0 016.7-6.6 20.6 20.6 0 018.5-2.1zm-118.2 55.5a5.2 5.2 0 005.3-5.6s0-5.2-5.3-5.2c0 0-5.6 0-5.6 5.2 0 0 0 5.6 5.6 5.6" class="cls-1"/> <path d="M700.5 0v2.4h6v15.8h2V2.4h6V0zM717 0v18.2h2.5v-15l5.2 15h2.1l4.9-14.7v14.7h2.5V0h-3.2l-5.2 15.4L720.4 0z"/> <path d="M585.6 0l-9 .7S567 2.7 567 7v14l-40.8 1.4v-7.7h-18.9s-18.8 1.4-18.8 7v51H567v34.9H513v14h54.1v80l40.2-4.2v-75.9h57.6v-14h-57.6V72.6H675v-7h1.8s5.2 0 8.3-8.3l3.2-23S688 17.4 670 17.4l-62.8 2V0zm62.2 32.1a5.6 5.6 0 016 5.6L650.8 59H526.3V36l40.8-1v.3l40.2-1.7z" class="cls-2"/> <path d="M341.5 107.6l13 15 15 13.2s4.1 4.2 8.3 0l14.7-13.2 13-15s4.5-3.9 0-8.8l-13-14.6-14.6-13s-4.2-4.2-8.4 0l-14.7 13-13.3 14.6s-3.8 5 0 8.8m335.3 46a5.7 5.7 0 000-6.6l-10.1-11.2-11.2-9.7a4.7 4.7 0 00-6.7 0l-11.1 9.7-9.8 11.2s-2.8 3.5 0 6.6l9.8 11.2 11.1 9.8s3.5 3.5 6.7 0l11.1-9.8 10.2-11.2" class="cls-1"/> <path d="M77.9 90.8h119.8a166.4 166.4 0 01-12.3 30.7s-15 29.4-38 51.4c0 0-43.3 40.5-71.3 40.5 0 0-20.6 0-33.8-7.7 0 0-16.5-10.1-16.5-31 0 0 0-27.7 42.3-35.7 0 0-27.2-1.8-41.6 12.6 0 0-13.2 12.5-13.2 34.5 0 0 0 21 19.2 33.6A78.6 78.6 0 0074 230.8l5.6.4S127 228.7 171 192c0 0 37.7-31.5 56.6-76.5a187.8 187.8 0 009-28l2.1-10.8v.4h-64.6V46.4h81.4V32.9h-81.3V1.1H153l-9 .7s-9.6 2-9.6 6.2v24.8H56v13.7h78.5v30.7H78v13.6M346.4 0l-.7 6.6-4.5 16.4A94.5 94.5 0 01329 43v157.5l-40.2-4.9v-125s-15 7.7-27.9 12.6l-5.2-8.4A238.3 238.3 0 00282.9 52s26.9-26.9 26.9-45c0 0 0-4.2 9.4-6.3l9-.7h18.2" class="cls-2"/> <path d="M335.3 60.4h75.4v145.3l40.1.7v-146h21v-14h-21V0h-21.6l-9 .7s-9.5 2-9.5 6.3v39.5h-75.4v14" class="cls-2"/> <path d="M152.6 145s-51-22.4-82.4-23.5c0 0-25.8-1.4-47.1 13.6 0 0-22.4 15.8-23 39.2 0 0 0 27.2 21.6 42.6 0 0 20 14.3 52 14.3h3.2l-5.3-.4s-23.4 0-39.8-11.5c0 0-18.2-13-18.2-34.2 0 0 0-23 16.1-35 0 0 13.6-9.7 35.6-10l31.1 3.4 24.1 9.5 57.6 29.3s39.5 20.6 77.2 33.2c0 0 131.3 43.6 425 15l15.7-3.2s5.6-2.4 10.1-9.7c0 0 3.9-6 30-29.4 0 0 18.9-17 12.6-22.7l-9 1.4s-96 21.7-242.5 27.6c0 0-164.4 6.7-245.8-16.7 0 0-56.2-16.1-98.8-32.9" class="cls-1"/> </svg> woocommerce-blocks/images/payment-methods/wechat.svg 0000644 00000005550 15133551764 0016734 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" preserveAspectRatio="xMidYMid meet" viewBox="0 0 750 212"> <defs> <style> .cls-1{fill:#2e3233}.cls-2{fill:#9de60b} </style> </defs> <path d="M492.4 67.2c16.2-4 36-.3 46.7 13.6a46.8 46.8 0 018.1 19.2l-21.5.1c-3-15.4-24.2-21.6-35.5-11-7.9 8.6-9.3 21.4-7.7 32.6 1.8 8.5 5.2 18.5 14 22 12.6 4.7 27.5-3.7 29.2-17.2l22 .1c-1.6 13-7.9 26.7-20.7 32-19.8 8.6-48.2 6-59.7-14.5 1.9-8.3 4-17 2-25.6-1-6.3-4.3-11.9-6.3-17.9 0-16.3 14-30.2 29.4-33.4z" class="cls-1"/> <path d="M274 69.6l23.6-.1c4.7 20.5 7.3 41.6 13 62 5.6-16.9 8.3-34.6 13.1-51.7 1.5-5.2 5.5-10.7 11.5-10.3 6.4-.8 10.8 5.2 12.1 10.9 4.7 16.8 7.7 34 12.9 50.7 6-20.2 9.2-41 13.5-61.5h23c-6.4 25.8-13.7 51.4-20.2 77.1-1.6 6.4-4.5 13.7-11.3 16.1-7.8 1.8-14.8-4.4-16.7-11.6-4.9-15.5-8.4-31.3-13.1-46.8-3.9 15.4-7.8 30.7-12 46-1.6 6.9-7.6 14-15.3 12.5-7.5-.8-11.1-8.2-12.8-14.6l-21.2-78.7z" class="cls-2"/> <path d="M552.4 69.5h21.1c0 7.8-.1 15.5.5 23.2 11.3.8 23.7-2.4 34.1 3.3 9 4.8 14.2 15 14.1 25v42l-20.5.1c-.1-13.4.2-26.8 0-40.3.2-4.5-3-9.3-7.7-9.8-6.8-.8-13.7-.4-20.5-.3v50.4h-21.1V69.7zm161.6 0h20v21h16v15.6h-16.2c.4 11.9-1 23.9.7 35.7 3.5 4.4 9.9 3.8 15 5.2l-.1 16c-11.5.2-27.2 1.7-33-10.8-5.1-14.7-1.3-30.8-2.5-46l-15.4-.1V90.7l15.6-.1-.1-21z" class="cls-1"/> <path d="M462.4 131.7c.3-13.4-2.6-28.6-14.5-36.6-15.6-10-40.5-8.6-51 8.1-8.7 14.7-9.2 35.6 2.7 48.9 11.4 12.8 31.5 13.5 46.4 7 8.2-3.3 12.7-11.3 15.8-19q-9.8-.4-19.6-.2a20 20 0 01-24.1 5.4c-5.2-2.5-6.3-8.6-7.9-13.5 17.4-.2 34.8 0 52.2-.1zm-52.7-14c2.3-7.2 8.2-13.4 16.4-12.1 8.1-1.3 13.6 5.2 15.9 12.1q-16.1.5-32.3 0z" class="cls-2"/> <path d="M695 114c-.4-12.4-11.4-22.3-23.4-23.5s-25.3-1.3-35 6.6c-5.3 3.8-7 10.3-8.3 16.2 6.1.2 12.3.2 18.4.1 3.5-10.4 17.1-11.7 25.3-6.3 3 2.6 2.8 7 3.7 10.5-12.8.5-27-2-38.3 5.4-10.8 6.4-12.8 22-6.5 32.2 4.4 6.6 13 7.8 20.3 7.8h44c-.2-16.3.3-32.7-.2-49zm-47 30c-1-3.7 1-7 2.3-10.1 8.1-1.9 16.5-2.1 24.8-2v17.2c-9-.6-20.1 2.2-27.1-5.2z" class="cls-1"/> <path d="M145 13.2a107.7 107.7 0 00-88.3-6.8 89.9 89.9 0 00-46.2 36.4A68.3 68.3 0 002 95a80.1 80.1 0 0034.7 47c-3 9-6.2 18-9 27.2 10.4-5.4 20.7-11 31.2-16.6a115.9 115.9 0 0039 5.5 67 67 0 01-2.7-31.4 67 67 0 0121.3-37.9 92 92 0 0169.1-22.7 80.7 80.7 0 00-40.6-53zM73.8 54.4c-1.8 9-13.8 13.2-20.7 7.2C45 56 47.2 41.8 56.7 39c9.3-3.6 19.9 5.8 17.1 15.4zm64.5-2.6c0 10.3-13.6 16.7-21.2 9.7-8-5.7-5.7-19.5 3.6-22.4 8.3-3.3 18.2 3.7 17.6 12.7z" class="cls-2"/> <path d="M250.6 114a70.4 70.4 0 00-32.9-33.5 90.6 90.6 0 00-81.2.3 67.5 67.5 0 00-36 44.3 57 57 0 007.2 42.2 77.6 77.6 0 0053 34.5 97.4 97.4 0 0046.1-3c9 3.5 17 9.1 25.7 13.3q-3.4-11.3-7.2-22.4c9.7-7 18.6-15.6 23.9-26.4a57.2 57.2 0 001.4-49.3zm-87.2 5.6c-2.2 7-12.3 9-17.2 3.7-5.4-4.8-3.3-15 3.8-17.2 7.8-3.3 16.8 5.7 13.4 13.5zm51 1c-2.7 6.3-12 7.7-16.6 2.9-2.3-2-2.8-5.1-3.7-7.8 1.2-4.9 4.4-10 10-10.2 7.6-1 14.3 8.2 10.3 15z" class="cls-1"/> </svg> woocommerce-blocks/images/payment-methods/ideal.svg 0000644 00000001621 15133551764 0016532 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="0 0 750 661.2"> <path fill="#fff" d="M0 0v661.2h385.5c254.2 0 364.5-142.4 364.5-331.3C750 142 639.7 0 385.5 0z"/> <path d="M41.7 41.7h343.8c233.4 0 322.8 127.5 322.8 288.2 0 192.8-124.9 289.5-322.8 289.5H41.7zM75 75v511.1h310.5C573 586.1 675 498.8 675 330 675 156.4 564.4 75 385.5 75H75z"/> <path d="M116.7 367.5H216v177h-99.4z"/> <circle cx="166.3" cy="278.4" r="61.8"/> <path fill="#d50072" d="M424.5 307.3v28.6h-70.8V221h68.6v28.6h-40v13.3h37.8v28.6h-37.8v15.8zm12.4 28.6l34.7-115h40.7L547 336H517l-6.5-22.2h-37.4l-6.5 22.2zm44.7-50.8h20.7l-9.5-32.5h-1.7l-9.5 32.5zm79.8-64.2H590v86.4h42.4c-11.6-156.7-134.8-190.6-246.9-190.6H266.1V221h17.7c32.2 0 52.2 21.8 52.2 57 0 36.3-19.5 58-52.2 58H266v208.5h119.4c182.1 0 245.5-84.6 247.7-208.6h-71.8V221zM266 249.6v57.7h17.7c12.3 0 23.6-3.6 23.6-29.3 0-25.2-12.6-28.4-23.6-28.4z"/> </svg> woocommerce-blocks/images/payment-methods/mastercard.svg 0000644 00000005613 15133551764 0017606 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 471"> <g fill="none" fill-rule="evenodd"> <rect width="750" height="471" fill="#f4f4f4" rx="40"/> <path fill="#000" d="M618.6 422.1c-1.2 0-2.2.5-3 1.2a4 4 0 000 5.9 4.2 4.2 0 006 0 4 4 0 000-5.8 4.2 4.2 0 00-3-1.3zm0 7.4a3.2 3.2 0 110-6.4 3.2 3.2 0 010 6.4zm.2-5.2h-1.7v3.9h.8v-1.5h.3l1.2 1.5h1l-1.3-1.5c.4 0 .7-.2 1-.4.1-.2.3-.4.3-.7 0-.4-.2-.7-.4-1-.3-.2-.7-.3-1.2-.3zm0 .7c.2 0 .4 0 .5.2a.4.4 0 01.2.4.4.4 0 01-.2.3l-.5.2h-1v-1zm-458.6 2.8h-8.7v-41h8.5v5s7.6-6 12-6c8.8.2 14 7.6 14 7.6s4.2-7.6 13.7-7.6c14 0 16.1 13 16.1 13v28.8h-8.4v-25.4s0-7.8-9-7.8c-9.5 0-10.4 7.8-10.4 7.8v25.4h-8.7V402s-.9-8-8.8-8c-10.3 0-10.5 8.2-10.5 8.2zm266.3-42c-4.5 0-12 6-12 6v-5h-8.6v41h8.7l-.2-25.6s.2-8.3 10.5-8.3c2 0 3.4.5 4.6 1.2l2.8-8c-1.7-.7-3.6-1.2-5.8-1.3zm123.3 0c-4.5 0-12 6-12 6v-5h-8.6v41h8.7l-.1-25.6s.2-8.3 10.4-8.3c2 0 3.4.5 4.6 1.2l2.9-8c-1.8-.7-3.7-1.2-6-1.3zm-305.7-.1c-13 0-20 11.7-20 21.6 0 10 7.9 21.7 20.4 21.7 7.3 0 13.3-5.4 13.3-5.4v4.2h8.6v-41h-8.6v5.2s-5.7-6.3-13.7-6.3zm1.7 8.3c7 0 12.8 6.1 12.8 13.7 0 7.5-5.7 13.6-12.8 13.6-7 0-12.7-6-12.7-13.6s5.7-13.7 12.7-13.7zm249.7-8.3c-13.1 0-20 11.7-20.1 21.6 0 10 7.9 21.7 20.4 21.7 7.3 0 13.4-5.4 13.4-5.4v4.2h8.6v-41H509v5.2s-5.6-6.3-13.6-6.3zm1.6 8.3c7 0 12.8 6.1 12.8 13.7 0 7.5-5.7 13.6-12.8 13.6-7 0-12.7-6-12.7-13.6s5.7-13.7 12.7-13.7zm81.1-8.3c-13.1 0-20 11.7-20 21.6-.1 10 7.8 21.7 20.4 21.7 7.3 0 13.3-5.4 13.3-5.4v4.2h8.6v-57.1H592V392s-5.7-6.3-13.7-6.3zm1.7 8.3c7 0 12.7 6.1 12.7 13.7 0 7.5-5.7 13.6-12.7 13.6s-12.7-6-12.7-13.6 5.6-13.7 12.7-13.7zm-287.2 35.1c-8.9 0-17.1-5.5-17.1-5.5l3.7-5.8s7.8 3.6 13.4 3.6c3.7 0 9.7-1.2 9.8-4.8.1-3.9-10.2-5-10.2-5s-15.4-.2-15.4-12.9c0-8 7.7-13 17.6-13 5.6 0 16.3 5 16.3 5l-4.3 6.6s-8.2-3.2-12.6-3.4c-3.6-.1-8 1.6-8 4.8 0 8.7 25.5-.7 25.5 16.9 0 11.4-10.4 13.5-18.7 13.5zm33-54v11.8H318v8.6h7.7v20.6S325 430 339.9 430c4.2 0 12.2-3 12.2-3l-3.4-9s-3.2 2.7-6.9 2.6c-6.9-.1-6.7-4.6-6.7-4.6v-20.5h14.3v-8.6H335V375h-9.4zm51.8 11c-14 0-21 11.6-21 21.7 0 10.3 6.4 22 21.9 22 6.6 0 15.9-5.8 15.9-5.8l-4-7s-6.4 4.5-12 4.5c-11.1 0-11.8-11-11.8-11h29.9s2.2-24.3-18.9-24.3zm-1.3 8.1h1c10.6 0 10.5 10 10.5 10h-21.2s-.5-9.4 9.7-10zm90.2 22.7l4 8s-6.4 4.1-13.5 4.1c-14.7 0-23-11-23-21.6C434 391 447 386 456 386c8 0 14.8 4.7 14.8 4.7l-4.4 8s-2.8-4.3-10.7-4.3c-8 0-12.2 6.9-12.2 13.4 0 7.3 5 13.5 12.3 13.5 5.8 0 10.7-4.4 10.7-4.4z"/> <path fill="#f79f1a" d="M624.5 278.6v-5.5h-1.4l-1.7 3.8-1.6-3.8h-1.5v5.5h1v-4.1l1.6 3.6h1l1.6-3.6v4.1zm-9.1 0v-4.5h1.8v-1h-4.7v1h1.9v4.5zm9.4-82c0 85.4-69 154.6-154.3 154.6-85.2 0-154.3-69.2-154.3-154.6S385.3 41.9 470.5 41.9c85.2 0 154.3 69.2 154.3 154.7z"/> <path fill="#ea001b" d="M434.5 196.6c0 85.4-69.1 154.6-154.3 154.6-85.2 0-154.3-69.2-154.3-154.6s69-154.7 154.3-154.7c85.2 0 154.3 69.2 154.3 154.7z"/> <path fill="#ff5f01" d="M375.3 74.8c-36 28.3-59 72.3-59 121.7s23 93.5 59 121.8c36-28.3 59.1-72.3 59.1-121.8s-23-93.4-59-121.7z"/> </g> </svg> woocommerce-blocks/images/payment-methods/laser.svg 0000644 00000005703 15133551764 0016567 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" viewBox="-45 62.3 750 471"> <path id="Rectangle-1" d="M-5 62.3h670a40 40 0 0140 40v391a40 40 0 01-40 40H-5a40 40 0 01-40-40v-391a40 40 0 0140-40z" class="st0"/> <path d="M160.2 477.3h340v-359h-340z" class="st0"/> <path d="M500.3 127.6l-10.1 17.1-330.5-7.6v-1.7zm-11.2 19.3l-10.4 17-313.2-7.9-.8-1.2zm-11.7 19.4l-10.2 16.9-290.2-7.9-.7-1.1zm-11.7 19.3l-9.9 17-266.6-7.9-.8-1.2zm-11.2 19.2l-10.1 17.1-243.7-7.9-.8-1.3zm-11.7 19.5l-10.1 16.9-220-7.9-.7-1.1zm-11.5 19.2l-10.1 17.1-196.1-7.9-.8-1.3zm-11.4 19.3l-10.2 17.1-173.1-7.9-.8-1.3zm-11.3 19.3l-10.5 17-150-7.9-.8-1.1zM397 301.5l-10.2 17-126.5-8-.8-1.1zm-11.5 19.3l-10.3 17-102.9-7.9-.8-1.2zM374 340l-10.3 17.1-79.4-7.9-.8-1.3zm-11.5 19.5l-10.2 16.8-56.3-7.8-.8-1.2zm-11.4 19.2l-10.4 17.1-33.2-7.9-.8-1.3zm-12 19.9l-10.4 17.7-11.1-9.2-.3-1.2zm-179.4 7.9h340.6-340.6z" class="st1"/> <path d="M159.7 406.5h340.6" class="st2"/> <path d="M159.7 387.2h340.6-340.6z" class="st1"/> <path d="M159.7 387.2h340.6" class="st2"/> <path d="M159.7 368h340.6-340.6z" class="st1"/> <path d="M159.7 368h340.6" class="st2"/> <path d="M159.7 348.6h340.6-340.6z" class="st1"/> <path d="M159.7 348.6h340.6" class="st2"/> <path d="M159.7 329.3h340.6-340.6z" class="st1"/> <path d="M159.7 329.3h340.6" class="st2"/> <path d="M159.7 310h340.6-340.6z" class="st1"/> <path d="M159.7 310h340.6" class="st2"/> <path d="M159.7 290.8h340.6-340.6z" class="st1"/> <path d="M159.7 290.8h340.6" class="st2"/> <path d="M159.7 271.3h340.6-340.6z" class="st1"/> <path d="M159.7 271.3h340.6" class="st2"/> <path d="M159.7 252.1h340.6-340.6z" class="st1"/> <path d="M159.7 252.1h340.6" class="st2"/> <path d="M159.7 232.8h340.6-340.6z" class="st1"/> <path d="M159.7 232.8h340.6" class="st2"/> <path d="M159.7 213.5h340.6-340.6z" class="st1"/> <path d="M159.7 213.5h340.6" class="st2"/> <path d="M159.7 194.1h340.6-340.6z" class="st1"/> <path d="M159.7 194.1h340.6" class="st2"/> <path d="M159.7 174.8h340.6-340.6z" class="st1"/> <path d="M159.7 174.8h340.6" class="st2"/> <path d="M159.7 155.6h340.6-340.6z" class="st1"/> <path d="M159.7 155.6h340.6" class="st2"/> <path fill="#fff" d="M364.5 427.4v37.4h25.4l3.2-7.1c-4.6.2-7.9.3-12.6.3h-5.4v-9.3h.6l8.8.3 3.2-7.3c-1.6.2-1.9.2-4.1.2-3.9.2-4.9.2-8.5.2v-7.7h1.3l12.8.3 3-7.3h-27.7zm-97.1 0l2.7 4-14.4 33.5h10.4l2.4-5.7h14.8l2.4 5.7h10.9l-16.4-37.4-12.8-.1zm12.9 24.9h-9l4.4-10.7 4.6 10.7zM221.4 425h-10.6v39.8h25.6l3-7.1c-3.3.2-7.6.3-12.2.3h-5.8v-33zm118.5 4.3a26.3 26.3 0 00-11.4-2.7c-8.1 0-13.6 4.4-13.6 10.7 0 4 1.7 6.6 6.3 9.6l5.4 3.6c2.5 1.7 3.5 3 3.5 4.9 0 2.4-2.2 3.8-5.5 3.8-2.8 0-4.6-.8-7.7-3.3l-2.8 6.6c4.7 2.5 7.1 3.2 11.2 3.2 9.3 0 15.2-4.6 15.2-11.7 0-4.1-1.9-6.8-6.6-10.1l-5.5-4c-2.4-1.6-3-2.4-3-3.6 0-2.1 1.6-3.3 4.3-3.3 2.4 0 4.4.8 7.6 2.8l2.6-6.5zm75.6-1.9v37.4h10.6v-30.5h2.3c4.7 0 7.3 1.9 7.3 5.4 0 3.6-3 6-7.7 6l-1.6-.2 12.8 19.3h11.7l-10.6-15.5c4.6-2.7 6.5-5.7 6.5-10.3 0-7.9-5.5-11.7-16.7-11.7l-14.6.1z"/> </svg> woocommerce-blocks/images/payment-methods/discover.svg 0000644 00000002670 15133551764 0017277 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 780 501"> <g fill="none" fill-rule="evenodd"> <path fill="#4d4d4d" d="M55 0A55 55 0 000 55v391a55 55 0 0055 55h670a55 55 0 0055-55V55a55 55 0 00-55-55z"/> <path fill="#fff" d="M415.1 161.2c31 0 56 23.6 56 52.7 0 29.2-25 52.8-56 52.8s-56-23.6-56-52.7c0-29.2 25-52.8 56-52.8zm-88 .7a55 55 0 0125.3 6v22.8a35.4 35.4 0 00-25.7-11.1 34 34 0 00-34.5 34c0 20.1 14.7 34.2 35.4 34.2 9.3 0 16.6-3 24.8-10.8v22.7c-9.3 4.2-16.9 5.8-25.7 5.8-31.3 0-55.6-22.6-55.6-51.7 0-28.9 25-52 56-52zm-97 .6c11.5 0 22 3.7 30.9 11l-10.8 13.3c-5.3-5.7-10.4-8-16.5-8-8.9 0-15.3 4.7-15.3 11 0 5.3 3.6 8.1 16 12.4 23.3 8 30.2 15.2 30.2 31 0 19.1-15 32.5-36.3 32.5-15.6 0-27-5.8-36.5-18.9l13.3-12a24.5 24.5 0 0022.4 13.2c9.2 0 16-6 16-14 0-4.1-2-7.7-6.2-10.2-2-1.2-6.2-3-14.2-5.7-19.3-6.5-26-13.5-26-27.2 0-16.2 14.3-28.4 33-28.4zm234.7 1.7h22.4l28 66.6 28.5-66.6H566L520.5 266h-11zm-397.4.2h30.2c33.3 0 56.5 20.4 56.5 49.6 0 14.6-7.1 28.7-19.1 38.1a57 57 0 01-37.6 11.4h-30zm96.1 0h20.6v99.1h-20.6zm411.8 0h58.2v16.8h-37.7v22h36.3V220h-36.3v26.8h37.7v16.7h-58.2v-99.1zm71.8 0h30.5c23.7 0 37.3 10.7 37.3 29.3 0 15.2-8.6 25.1-24 28l33.1 41.8h-25.2l-28.5-39.8h-2.6v39.8H647zm20.6 15.6v30h6c13 0 20-5.3 20-15.3 0-9.6-7-14.7-19.7-14.7zm-579.8 1.2v65.6h5.6c13.2 0 21.6-2.4 28-8a32.5 32.5 0 000-49.7c-6.7-5.7-14.8-7.9-28-7.9z"/> <path fill="#f47216" d="M780 288.4C754 306.7 558.9 437.7 221.2 501H725a55 55 0 0055-55z"/> </g> </svg> woocommerce-blocks/images/payment-methods/maestro.svg 0000644 00000010450 15133551764 0017126 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 471"> <g fill="none" fill-rule="evenodd"> <rect width="750" height="471" fill="#000" rx="40"/> <path fill="#d9222a" d="M675 235.5a179.5 179.5 0 11-359 0 179.5 179.5 0 01359 0"/> <path fill="#0097d0" d="M356.9 349.5a178 178 0 01-13.7-19h63.6a178.6 178.6 0 0010.4-19h-84.3a178.4 178.4 0 01-7.6-19h99.4a179.3 179.3 0 005.2-95H320.1a178.2 178.2 0 015.2-19h99.4a181.5 181.5 0 00-7.6-19H333a181 181 0 0110.3-19h63.6a181.4 181.4 0 00-13.7-19H357a181.1 181.1 0 0118.1-19 179.5 179.5 0 100 266 180.4 180.4 0 0018.1-19z"/> <path fill="#000" d="M651 335.5a5.8 5.8 0 115.9 5.8 5.8 5.8 0 01-5.8-5.8zm5.9 4.5a4.4 4.4 0 004.4-4.5 4.4 4.4 0 00-4.4-4.4 4.4 4.4 0 00-4.4 4.4 4.4 4.4 0 004.4 4.5zm-.8-2h-1.2v-5h2.2c.4 0 .9 0 1.3.3.4.2.6.7.6 1.2 0 .6-.3 1.1-.9 1.3l1 2.3h-1.4l-.7-2h-1zm0-2.8h1.4a.7.7 0 00.3-.7.7.7 0 00-.3-.5c-.2-.1-.6 0-.8 0h-.6zM372.4 284c-7.6 2-15 3-22.9 3-25 0-38-11.4-38-33.2 0-25.4 16.6-44.2 39-44.2 18.5 0 30.2 10.5 30.2 27a70 70 0 01-2.7 18.3h-44.5c-1.6 10.6 6.2 15.3 19.4 15.3 8 0 15.1-1.4 23-4.6zm-12-44.1c0-1.7 2.6-13-10.3-13.3-7.1 0-12.2 4.7-14.3 13.3zm27.1-5c0 9.4 5.3 15.9 17.3 20.7 9.2 3.8 10.7 4.9 10.7 8.2 0 4.7-4 6.8-13.1 6.8-6.8 0-13-.9-20.3-3l-3.2 17.2c6.5 1.5 15.6 2 23.7 2.2 24 0 35-7.9 35-24.9 0-10.2-4.5-16.2-16-20.7-9.5-3.8-10.6-4.6-10.6-8 0-4.1 3.8-6.1 11.1-6.1 4.5 0 10.6.4 16.4 1l3.3-17.2c-6-.8-15-1.5-20.1-1.5-25.5 0-34.3 11.5-34.2 25.3m-88.2 50.9h-18.7l.5-7.8c-5.7 6.1-13.3 9-23.6 9-12.2 0-20.5-8.3-20.5-20.3 0-18.2 14.5-28.7 39.4-28.7 2.6 0 5.8.2 9.2.6.7-2.5.9-3.5.9-4.8 0-5-4-6.8-14.5-6.8a86.1 86.1 0 00-23.8 3.3l3.2-16.7a102 102 0 0126.9-4c19.3 0 29.5 7.6 29.5 21.8.1 3.8-1.2 11.4-1.9 14.8-.7 4.8-6 33-6.6 39.6zm-16.4-33.2l-5-.3c-12.7 0-19.2 3.8-19.2 11.2 0 4.7 3.1 7.7 8 7.7 9.2 0 15.8-7.7 16.2-18.6zm194.1 32a58 58 0 01-16 2.4c-11.5 0-17.7-5.8-17.7-16.2-.4-2.9 2.4-16 3-19.8l10.6-57.5H479l-3.4 17.8h11.4l-3 18.2h-11.5s-6.3 31.5-6.3 34c0 3.8 2.3 5.4 7.6 5.4 2.6 0 4.5-.2 6-.7l-2.9 16.4m99.2-75c-16.2 0-29 6.7-36.3 18l6.4-16.7c-11.8-4.3-19.5 1.9-26.4 10.7l-2.3 2.8v-13h-20.8c-2.8 23-7.8 46.3-11.7 69.4l-1 5h22.5c2-11.7 3.8-21.2 5.6-28.8 4.7-20.8 12.8-27.1 24.8-24.3-2.8 6-4.3 12.9-4.3 20.5 0 18.6 10 33.8 35.1 33.8 25.3 0 43.6-13.5 43.6-44.3 0-18.6-12.2-33-35.2-33zm-6.5 59.3c-7.9.2-12.7-6.5-12.7-16.4 0-11.8 7-25.1 18.3-25.1 9 0 12.2 7.2 12.2 14.8 0 16.8-7 26.7-17.8 26.7zm-343.2 16.9h-22.3l13.3-70-30.6 70h-20.4l-3.7-69.6-13.3 69.6h-20.3l17.3-91h34.9l2.9 50.7 22.1-50.7h37.7l-17.6 91"/> <path fill="#fff" d="M613.1 274.4a5.8 5.8 0 015.8-5.8c3.3 0 5.8 2.6 5.8 5.8a5.8 5.8 0 11-11.5 0zm5.8 4.4a4.4 4.4 0 004.5-4.4 4.4 4.4 0 00-4.5-4.4 4.4 4.4 0 00-4.4 4.4 4.4 4.4 0 004.4 4.4zm-.7-1.9H617v-5h2.1c.5 0 1 0 1.3.2.4.3.7.8.7 1.3s-.4 1-1 1.3l1 2.2h-1.3l-.8-2h-.8zm0-2.9H619.5a.7.7 0 00.3-.6.6.6 0 00-.2-.6h-1.4zM378 278.4c-7.7 2-15.1 3-23 3-25 0-38-11.4-38-33.2 0-25.4 16.6-44.2 39-44.2 18.5 0 30.3 10.5 30.3 27 0 5.4-.8 10.7-2.8 18.3h-44.5c-1.6 10.6 6.2 15.3 19.4 15.3 8 0 15.1-1.5 23-4.6zm-12-44.2c0-1.6 2.5-13-10.4-13.2-7.1 0-12.2 4.7-14.3 13.2h24.7zm27-5c0 9.5 5.3 16 17.4 20.8 9.2 3.8 10.6 4.9 10.6 8.2 0 4.7-4 6.8-13 6.8-6.9 0-13-1-20.3-3l-3.3 17.2a128 128 0 0023.7 2.2c24 0 35-7.9 35-24.9 0-10.2-4.5-16.2-16-20.7-9.5-3.8-10.6-4.6-10.6-8 0-4.1 3.8-6.2 11.1-6.2 4.5 0 10.6.5 16.4 1.2l3.3-17.3c-6-.8-14.9-1.5-20-1.5-25.5 0-34.3 11.5-34.3 25.3M305 280.2h-18.7l.5-7.8c-5.7 6.1-13.3 9-23.6 9-12.2 0-20.5-8.3-20.5-20.3 0-18.2 14.5-28.7 39.4-28.7 2.6 0 5.8.2 9.2.6.7-2.5.9-3.5.9-4.8 0-5-4-6.8-14.4-6.8a86.3 86.3 0 00-23.8 3.3L257 208c11.2-2.9 18.6-4 26.9-4 19.3 0 29.5 7.6 29.5 21.8.1 3.8-1.2 11.4-1.8 14.8-.8 4.8-6.2 33-6.7 39.6zM288.5 247c-2.4-.3-3.4-.3-5-.3-12.7 0-19.2 3.8-19.2 11.2 0 4.7 3.2 7.7 8 7.7 9.2 0 15.8-7.7 16.2-18.6zm194.1 32a58.1 58.1 0 01-16 2.4c-11.5 0-17.7-5.8-17.7-16.2-.4-2.9 2.4-16.1 3-19.8l10.6-57.5h22.2l-3.4 17.8h11.4l-3 18.2h-11.5s-6.3 31.5-6.3 34c0 3.8 2.3 5.4 7.6 5.4 2.6 0 4.6-.2 6-.7zm110.5-42.4c0 16.8-6.9 26.7-17.8 26.7-7.9.2-12.7-6.5-12.7-16.4 0-11.8 7-25.1 18.3-25.1 9 0 12.2 7.2 12.2 14.8zm24 .5c0-18.6-12.3-33-35.2-33-26.5 0-43.6 17.5-43.6 43.5 0 18.6 10.1 33.8 35.2 33.8 25.2 0 43.6-13.5 43.6-44.3zm-114.7-31.4c-2.8 23-7.8 46.4-11.7 69.5l-1 5h22.5c8-44.6 10.8-57.3 27.7-52.8l8.2-21c-11.9-4.4-19.5 1.8-26.4 10.6.7-4 1.8-7.8 1.6-11.3zM232 280.2h-22.3l13.3-70-30.6 70h-20.4l-3.7-69.6-13.3 69.6h-20.3l17.3-91H187l1.8 56.3 24.6-56.3h36.3z"/> </g> </svg> woocommerce-blocks/images/payment-methods/amex.svg 0000644 00000006210 15133551764 0016405 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 752 471"> <g fill="none" fill-rule="evenodd"> <rect width="750" height="471" x="1" fill="#2557d6" rx="40"/> <path fill="#fff" d="M1 221.2h36l8.2-19.5h18.1l8.1 19.5h71v-15l6.2 15h36.8l6.4-15.2v15.2h176.1v-32h3.3c2.4 0 3.1.3 3.1 4.2v27.8h91.1v-7.5c7.4 4 18.8 7.5 33.9 7.5h38.3l8.2-19.5H564l8 19.5h73.8v-18.5l11.2 18.5h59.2V98.7h-58.6V113l-8.2-14.4h-60V113l-7.6-14.4h-81.2a73.2 73.2 0 00-35.2 7.1v-7.1h-56v7.1c-6.1-5.4-14.5-7.1-23.8-7.1H180.9l-13.7 31.6L153 98.7H88.6V113l-7-14.4h-55L1 156.9v64.3zm227.4-17.7h-21.6l-.1-68.8-30.6 68.8h-18.5L127 134.7v68.8H84L76 184H32L24 203.5H1l37.8-87.8H70l35.8 83.1v-83.1h34.5l27.5 59.6 25.4-59.6h35.1v87.8zM68.8 165.7l-14.5-35-14.3 35zm245.6 37.8H244v-87.8h70.4V134h-49.3v15.8h48.1v18h-48.1v17.5h49.3v18.2zm99.3-64.2c0 14-9.4 21.3-14.9 23.4 4.6 1.8 8.6 4.9 10.4 7.4 3 4.4 3.5 8.3 3.5 16.2v17.2h-21.2l-.1-11c0-5.3.5-13-3.3-17.2-3.1-3-7.8-3.7-15.4-3.7h-22.6v32H329v-88h48.5c10.8 0 18.7.4 25.5 4.3 6.7 4 10.7 9.6 10.7 19.4zm-26.7 13c-2.9 1.8-6.3 1.9-10.4 1.9H351v-19.5h26c3.6 0 7.5.1 10 1.6 2.7 1.2 4.4 4 4.4 7.7 0 3.9-1.6 7-4.4 8.4zm60.5 51.2H426v-87.8h21.5zm249.7 0h-29.9l-40-66v66h-42.9l-8.2-19.6h-43.8l-8 19.6h-24.6c-10.2 0-23.2-2.2-30.6-9.7-7.4-7.5-11.2-17.6-11.2-33.5 0-13 2.3-25 11.3-34.4 6.9-7 17.5-10.2 32-10.2h20.5v18.8h-20c-7.7 0-12 1.1-16.2 5.2-3.6 3.7-6.1 10.7-6.1 19.9 0 9.4 1.9 16.2 5.8 20.6 3.2 3.5 9.1 4.6 14.7 4.6h9.5l29.7-69.1h31.6l35.7 83v-83h32l37.1 61.2v-61.2h21.6v87.8zm-128.1-37.8l-14.6-35-14.5 35h29zm181.9 178c-5.2 7.5-15.1 11.3-28.7 11.3h-40.7v-18.8h40.6c4 0 6.8-.6 8.5-2.2a7.7 7.7 0 002.5-5.7c0-2.6-1-4.6-2.6-5.8-1.5-1.4-3.7-2-7.4-2-19.8-.7-44.5.6-44.5-27.2 0-12.7 8.1-26.1 30.3-26.1h42v-17.5h-39a44.1 44.1 0 00-26.5 7.1v-7.1h-57.7c-9.2 0-20 2.2-25.2 7.1v-7.1h-103v7.1a55 55 0 00-28.5-7.1h-68v7.1c-6.4-6.2-20.9-7.1-29.7-7.1h-76L280 268.4l-16.4-18.7H150v122.6h111.5l18-19.1 16.8 19 68.8.1v-28.8h6.7a72 72 0 0029.4-4.3v33H458v-31.9h2.7c3.5 0 3.9.1 3.9 3.6v28.4h172.2c11 0 22.4-2.8 28.7-7.9v7.9H720c11.4 0 22.5-1.6 31-5.7zm-341.5-47c0 24.3-18.3 29.4-36.8 29.4h-26.3v29.4h-41l-26-29-26.9 29H169v-87.8h84.8l26 28.8 26.7-28.8H374c16.8 0 35.6 4.6 35.6 29zM241.8 337H190v-17.4h46.3v-18H190v-16h52.8l23.1 25.7zm83.6 10.1L293 311.3l32.4-34.6zm47.8-39H346v-22.4h27.5c7.6 0 12.9 3 12.9 10.7 0 7.6-5 11.6-13.2 11.6zM516 267.6h70.3v18.1H537v16H585v18H537v17.4l49.3.1v18.2H516v-87.8zm-27 47a23 23 0 0110.2 7.4c3 4.3 3.5 8.3 3.5 16v17.4h-21.1v-11c0-5.3.5-13-3.4-17.2-3.1-3.1-7.8-3.9-15.5-3.9h-22.5v32.1H419v-87.8h48.7c10.6 0 18.4.4 25.3 4.1 6.7 4 10.9 9.5 10.9 19.5 0 14-9.4 21.2-15 23.4zm-12-11.1c-2.8 1.7-6.3 1.8-10.4 1.8H441v-19.7h26c3.7 0 7.4 0 10 1.6 2.7 1.4 4.4 4.1 4.4 7.9 0 3.7-1.7 6.7-4.4 8.4zm190.3 5.6c4.1 4.2 6.3 9.6 6.3 18.6 0 18.9-11.8 27.7-33 27.7h-41.1v-18.8h40.9c4 0 6.8-.5 8.6-2.2a7.8 7.8 0 002.5-5.7c0-2.6-1.2-4.6-2.6-5.8-1.6-1.4-3.9-2-7.5-2-19.7-.7-44.4.6-44.4-27.2 0-12.7 8-26.1 30.1-26.1h42.3v18.7h-38.7c-3.8 0-6.3.1-8.4 1.6-2.4 1.4-3.2 3.5-3.2 6.3 0 3.3 2 5.5 4.6 6.5 2.2.8 4.6 1 8.2 1l11.4.3c11.4.3 19.3 2.3 24 7zm83.7-23.5h-38.4c-3.9 0-6.4.1-8.6 1.6-2.2 1.4-3 3.5-3 6.3 0 3.3 1.8 5.5 4.6 6.5 2.2.8 4.6 1 8.1 1l11.4.3c11.6.3 19.3 2.3 24 7 .8.8 1.3 1.5 1.9 2.3z"/> </g> </svg> woocommerce-blocks/images/payment-methods/diners.svg 0000644 00000001127 15133551764 0016741 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 471"> <g fill="none" fill-rule="evenodd"> <rect width="750" height="471" fill="#0079BE" rx="40"/> <path fill="#FFF" d="M585 238c0-99.5-83-168.2-174-168.2h-78.2c-92 0-167.7 68.8-167.7 168.1 0 91 75.7 165.7 167.7 165.3H411c91 .4 174-74.3 174-165.3z"/> <path fill="#0079BE" d="M333.3 84c-84 0-152.2 68.2-152.2 152.5S249.2 389 333.3 389.1c84 0 152.2-68.3 152.2-152.6S417.4 84 333.3 84z"/> <path fill="#FFF" d="M237 236.1a96.9 96.9 0 0162-90.3v180.5a96.8 96.8 0 01-62-90.2zm131 90.3V145.8a96.8 96.8 0 010 180.6z"/> </g> </svg> woocommerce-blocks/images/payment-methods/giropay.svg 0000644 00000003615 15133551764 0017133 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 740 318.1"> <path fill="#000268" d="M0 57A57.3 57.3 0 0157.4 0h625.2A57.3 57.3 0 01740 57v204a57.2 57.2 0 01-57.4 57H57.4A57.1 57.1 0 010 261.2z" data-name="92653320"/> <path fill="#fff" d="M23.6 59v200.2a35.7 35.7 0 0035.8 35.7h327.5V23.4H59.4A35.7 35.7 0 0023.6 59zm448 99.1c0 12.8-6.3 21.5-16.7 21.5-9.2 0-16.9-8.8-16.9-20.5 0-12 6.8-20.9 17-20.9 10.6.1 16.6 9.3 16.6 20zm-61.3 73.1H438v-43.4h.3c5.3 9.5 15.7 13 25.3 13 23.8 0 36.5-19.7 36.5-43.3 0-19.4-12-40.3-34.2-40.3-12.6 0-24.2 5-29.8 16.2h-.4V119h-25.5v112.2zm127.6-57.6c0-7.7 7.3-10.6 16.7-10.6 4.1 0 8 .2 11.7.4 0 9.3-6.6 18.9-17 18.9-6.5 0-11.4-3.1-11.4-8.7zm55.8 25.4a96.1 96.1 0 01-1.6-18.9v-29.8c0-24.4-17.6-33.2-38-33.2a82.2 82.2 0 00-32 5.7l.5 18.7c7.7-4.3 16.6-6 25.5-6 10 0 18.2 3 18.3 13.9a84.7 84.7 0 00-12.8-1.1c-14.7 0-41.3 2.9-41.3 27.3 0 17.4 14.1 25.1 29.9 25.1 11.4 0 19-4.4 25.3-14.4h.3c0 4.2.5 8.3.6 12.6h25.2zm12.3 32.2a78.8 78.8 0 0017.2 1.9c25.3 0 31.3-19.6 39.2-39.6l29.3-74.5h-27.6l-16.5 52.2h-.2L630.2 119h-29.8l32.4 81.9c-2 7-7.2 11-14 11a35.1 35.1 0 01-11-1.7l-1.8 21z" data-name="92186184"/> <path fill="#ff0007" d="M91 158.3c0-11 5.4-20 15.7-20 12.5 0 17.7 10 17.7 18.9 0 12.2-7.9 20.5-17.7 20.5-8.3 0-15.7-7-15.7-19.5zm60.1-39.3h-25v14.4h-.3a31.2 31.2 0 00-27.1-16.3c-25 0-36.1 17.8-36.1 41.6 0 23.7 13.7 40.3 35.6 40.3 11 0 20.3-4.4 26.9-13.7h.3v4.3c0 15.6-8.6 23.2-24.6 23.2a54.7 54.7 0 01-27-6.7L72.3 228a92.4 92.4 0 0030.6 5c32.2 0 48.2-10.5 48.2-43.3zm46.7-33.4h-27.7V106h27.7V85.6zM170.1 199h27.7v-80h-27.7zm104.6-81a64.1 64.1 0 00-9.3-1c-12 0-18.9 6.5-23.7 16.7h-.3V119h-25.1v80h27.6v-33.7c0-15.7 7.3-25 20.1-25a31.8 31.8 0 019.4.9l1.3-23.2zm47.7 63.4c-12.8 0-18-10.6-18-22.4 0-12 5.1-22.5 18-22.5s17.9 10.6 17.9 22.5c0 11.8-5.3 22.4-18 22.4zm0 19.4c26.3 0 46.5-15.4 46.5-41.8 0-26.6-20.2-42-46.5-42s-46.6 15.5-46.6 42c0 26.4 20 41.8 46.6 41.8z" data-name="47303032"/> </svg> woocommerce-blocks/images/payment-methods/sofort.svg 0000644 00000023632 15133551764 0016776 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 232.5"> <path fill="#ee7f00" d="M248 0a69 69 0 00-29.6 6.8 92.5 92.5 0 00-35.3 30.8A133.8 133.8 0 00167 69c-8.1 22.2-8 40.2.3 52.1 4.7 6.7 14.4 14.8 34.3 14.8h.1c36.2 0 63.2-23.8 80.3-70.6 4.6-12.3 10.5-35.4-.4-51C275 4.8 263.7 0 248 0zm218.5 0a69 69 0 00-29.6 6.8 92.4 92.4 0 00-35.3 30.8A133.4 133.4 0 00385.4 69c-8.1 22.2-8 40.2.3 52.1 4.7 6.7 14.4 14.8 34.3 14.8 36.3 0 63.3-23.8 80.4-70.6 4.6-12.4 10.5-35.5-.4-51C493.4 4.7 482.2 0 466.5 0zM131.8 1.9c-12 0-23.5 0-33.9 5.2S78.4 23 71.3 44A42.1 42.1 0 0069 55.8a17.8 17.8 0 001.8 8.8A25.7 25.7 0 0078 73a36.3 36.3 0 0011.3 5.7l5 1.4 7.6 2.2a16 16 0 013.6 1.5 7.2 7.2 0 012.8 2.9 6.2 6.2 0 01.6 2.7 9 9 0 01-.5 3.1 8.6 8.6 0 01-5.2 5.9A38 38 0 0190 99.9H12.3L0 133.7h83.2c7.9 0 22.2 0 36-5.9s27-17.5 32.5-40.9c2-8.8 1.4-16-2-21.6S140 55.6 130.9 53l-4.4-1.3-6.6-1.9a8.7 8.7 0 01-5.2-3.7 7.3 7.3 0 01-.7-5.9 8.6 8.6 0 014.3-5.2 17.5 17.5 0 018.4-1.9h48.4l1-1.5a107 107 0 0131.2-29.7zm217.4 0a45.3 45.3 0 00-45 31.3l-36.7 100.5h40.1L326.4 82h35.4l10-27.7h-35.2l5.7-16a8.5 8.5 0 018.1-5.1h41.8l1-1.6a112.2 112.2 0 0133-29.7zm186 .1l-48 131.8h40.7l17.4-46.4c.9 2 11.1 46.4 11.1 46.4h42.9s-7.8-32.7-13.5-45.9a73.2 73.2 0 00-5.1-10.4c12.1-4.5 29.6-14.1 37-33.7 4.4-12 4.1-21.7-.9-28.9-6-8.7-18.2-12.9-37.3-12.9zm83.5 0a32.6 32.6 0 016.7 7c4.7 6.8 6.5 15 5.4 24.3h24.3l-36.6 100.5h36.6l36.6-100.6h47L750 2zM562 29.2h6.7c12.3 0 17 3.6 12.7 14.8-3.9 10.5-10.9 17-24.2 17h-7L562 29.2zm-323.5.6c12.5 0 15.4 7.3 4.4 37.4-11.3 30.9-19.5 38.6-31.9 38.6-12 0-16.2-7.6-5-38.3 8.4-23 19.3-37.7 32.5-37.7zm218.4 0c12.5 0 15.4 7.3 4.4 37.4-11.3 30.9-19.5 38.6-31.8 38.6-12 0-16.2-7.6-5-38.3 8.4-23 19.3-37.7 32.4-37.7z"/> <path fill="#383a41" d="M39.6 147.6A9.9 9.9 0 0034 149a10.4 10.4 0 00-3.3 5c-.8 2.3-1 3.9-.4 4.8s2.2 1.4 4.8 1.4 4.5-.5 5.8-1.5a10.4 10.4 0 003.2-5c.8-2.2 1-3.8.4-4.7s-2.1-1.4-4.8-1.4zm22.1 0c-2.6 0-4.5.5-5.8 1.4a10.4 10.4 0 00-3.2 5c-.8 2.3-1 3.9-.4 4.8s2.2 1.4 4.8 1.4 4.5-.5 5.8-1.5a10.5 10.5 0 003.2-5c.8-2.2 1-3.8.4-4.7s-2.2-1.4-4.8-1.4zm412.5 17.2a35.5 35.5 0 00-9 1.1 30.3 30.3 0 00-8.2 3.5 29 29 0 00-6.9 6 26.9 26.9 0 00-4.7 8.2 18.3 18.3 0 00-1.4 7 11.8 11.8 0 001.3 5 12 12 0 003.1 3.8 28 28 0 004.1 2.7l4.3 2.3a19 19 0 013.6 2.4 6.9 6.9 0 012.1 2.8 5.3 5.3 0 01-.2 3.8 12.2 12.2 0 01-2 3.5 12 12 0 01-3 2.6 14.5 14.5 0 01-3.8 1.7 17.2 17.2 0 01-4.6.6 19.6 19.6 0 01-6.2-.9 20.3 20.3 0 01-6.8-3.7 3 3 0 00-1.7-.8 1.9 1.9 0 00-1 .3 3 3 0 00-1 1 10.1 10.1 0 00-.9 1.6c-.3.7-.7 1.6-1 2.7a18.7 18.7 0 00-1 3.5 2.8 2.8 0 00.2 2 6.2 6.2 0 001.6 1.4 14.8 14.8 0 003.1 1.7 27.1 27.1 0 004.7 1.3 30.6 30.6 0 006 .6 38 38 0 0010-1.3 34.3 34.3 0 009-4 31.8 31.8 0 007.6-6.5 29 29 0 005.2-9 17.5 17.5 0 001.3-6.8 11.6 11.6 0 00-1.3-5.1 12 12 0 00-3.2-3.7 32 32 0 00-4.1-2.8 154 154 0 00-4.4-2.3 20.5 20.5 0 01-3.6-2.3 6.7 6.7 0 01-2.2-2.8 5.4 5.4 0 01.2-3.8 10.7 10.7 0 011.5-2.8 9.6 9.6 0 012.5-2.2 12.4 12.4 0 013.2-1.5 14.4 14.4 0 014-.5 15.5 15.5 0 014.9.7 18 18 0 013.5 1.5 22.6 22.6 0 012.4 1.5 3 3 0 001.5.7 1.6 1.6 0 001-.3 3.2 3.2 0 00.9-1 13.4 13.4 0 00.8-1.6 51.1 51.1 0 001.8-4.7 10.1 10.1 0 00.4-1.5 3.8 3.8 0 000-1 2.1 2.1 0 00-.4-.8 5 5 0 00-1.6-1.2 17.4 17.4 0 00-3.1-1.4 25.7 25.7 0 00-4-1 28 28 0 00-4.5-.3zm210 0a46.8 46.8 0 00-14.9 2.3A47.4 47.4 0 00656 174a52 52 0 00-10.8 10.9 56.7 56.7 0 00-7.7 14.4 40.4 40.4 0 00-2.8 14 19 19 0 002.8 10.3 17.2 17.2 0 008.1 6.6 34.2 34.2 0 0013.2 2.2 58.4 58.4 0 0016.4-2.2l4.6-1.4a6.5 6.5 0 002.8-1.8 8.8 8.8 0 001.5-2.6l9.5-26a6 6 0 00.4-1.8 2.6 2.6 0 00-.2-1.4 1.8 1.8 0 00-.8-.8 2.8 2.8 0 00-1.4-.3h-22a1.7 1.7 0 00-1 .3 3.8 3.8 0 00-.9.9 9.1 9.1 0 00-1 1.5 22.5 22.5 0 00-1 2.4 10.8 10.8 0 00-.9 3.8c.1.8.5 1.2 1 1.2h12.4l-5.5 15.2a23 23 0 01-4.6 1.5 22.3 22.3 0 01-4.6.5 18.9 18.9 0 01-7.8-1.5 10.3 10.3 0 01-4.8-4.3 13.8 13.8 0 01-1.6-7.2 30.7 30.7 0 012.1-9.9 39 39 0 014.9-9.3 34.9 34.9 0 016.8-7.2 30.4 30.4 0 018.3-4.7 28.5 28.5 0 0116.7-.7 29.4 29.4 0 015 2 21 21 0 013.2 2 3.4 3.4 0 001.8.9 1.8 1.8 0 00.9-.3 3.5 3.5 0 00.8-.8 11.7 11.7 0 001-1.7 23 23 0 001-2.6 19.3 19.3 0 001.1-3.8 3 3 0 00-.2-2 6.4 6.4 0 00-1.8-1.7 16.4 16.4 0 00-3.7-1.8 33.8 33.8 0 00-5.6-1.4 43.2 43.2 0 00-7.5-.6zm-413.3.8l-4.1.1a5 5 0 00-2.3.7 2.8 2.8 0 00-1.2 1.6 15.7 15.7 0 00-.5 2.8l-6 57.2a5.5 5.5 0 000 2 1.7 1.7 0 001.2 1 8.5 8.5 0 002.6.5l4.6.1h4.5a11.5 11.5 0 002.9-.6 4.7 4.7 0 001.8-1.2 9.5 9.5 0 001.5-1.9l25-41.4-4.6 41.5a4.8 4.8 0 000 2 1.7 1.7 0 001 1.1 8.5 8.5 0 002.6.5l4.6.1h4.3a13.3 13.3 0 002.9-.6 5.3 5.3 0 002-1.1 9 9 0 001.5-2l35.8-57a27.8 27.8 0 001.6-3 1.8 1.8 0 00.1-1.6 2 2 0 00-1.5-.7 33 33 0 00-3.4-.2c-1.6 0-2.9 0-3.8.2a8.3 8.3 0 00-2.2.4 3 3 0 00-1.3 1 11.6 11.6 0 00-.9 1.3l-29.6 50h-.1l5.8-49.7a5 5 0 000-1.7 1.4 1.4 0 00-.9-.9 6.1 6.1 0 00-2-.4l-3.9-.1-3.6.1a9.5 9.5 0 00-2.4.5 3.6 3.6 0 00-1.4.9 9.1 9.1 0 00-1.1 1.6l-30 49.7 6.7-50a4.5 4.5 0 00.1-1.6 1 1 0 00-.6-.8 5.7 5.7 0 00-2-.3l-3.8-.1zm157.8 0a29.5 29.5 0 00-3.3.1 14.6 14.6 0 00-2.2.4 3.7 3.7 0 00-1.3.7 2.2 2.2 0 00-.7.9l-22.5 61.8a1.1 1.1 0 000 .9 1.3 1.3 0 00.9.6 8 8 0 001.8.5 27.1 27.1 0 003.2.1 30 30 0 003.3-.1 11.4 11.4 0 002.2-.5 3.5 3.5 0 001.3-.6 2.2 2.2 0 00.7-1l22.4-61.7a1.1 1.1 0 000-1 1.4 1.4 0 00-.8-.6 8.9 8.9 0 00-1.9-.4 27 27 0 00-3.2-.1zm-404.5 0a29.9 29.9 0 00-3.3.1 14.5 14.5 0 00-2.2.4 3.4 3.4 0 00-1.3.7 2.3 2.3 0 00-.7.9L2.2 207.5a31 31 0 00-2.2 11 13.5 13.5 0 002.4 7.8 13.2 13.2 0 006.5 4.7 31.5 31.5 0 0010.5 1.5 42.3 42.3 0 0012.3-1.7 35.1 35.1 0 0018.4-13 42.6 42.6 0 005.7-10.8l14.3-39.2a1.2 1.2 0 000-1 1.3 1.3 0 00-.8-.6 8.7 8.7 0 00-1.8-.4 25.3 25.3 0 00-3.2-.2 29.9 29.9 0 00-3.2.2 14 14 0 00-2.2.4 3.4 3.4 0 00-1.3.7 2.2 2.2 0 00-.6.9L42.6 207a26.2 26.2 0 01-3.1 6.2 20.6 20.6 0 01-4.4 4.5 18 18 0 01-5.2 2.8 18.3 18.3 0 01-6 1 13.3 13.3 0 01-5.2-1 7 7 0 01-3.2-2.7 8.7 8.7 0 01-1-4.8 21.9 21.9 0 011.5-6.7l14-38.7a1.2 1.2 0 000-.9 1.4 1.4 0 00-.8-.6 9.1 9.1 0 00-2-.4 26.3 26.3 0 00-3-.2zm490.5 0a29.7 29.7 0 00-3.3.1 12.4 12.4 0 00-2.2.4 3.5 3.5 0 00-1.3.7 2.3 2.3 0 00-.7.9l-14.4 39.8a30.8 30.8 0 00-2.2 11 13.4 13.4 0 002.3 7.7 13.3 13.3 0 006.6 4.7 31.4 31.4 0 0010.5 1.6 42.4 42.4 0 0012.3-1.7 35.1 35.1 0 0018.4-13 42.5 42.5 0 005.6-10.8l14.3-39.2a1.1 1.1 0 00-.8-1.6 7.8 7.8 0 00-1.8-.4 25.5 25.5 0 00-3.1-.2 29.9 29.9 0 00-3.3.2 12.2 12.2 0 00-2.2.4 3.4 3.4 0 00-1.3.7 2.3 2.3 0 00-.6.9l-14.3 39.4a26.6 26.6 0 01-3.2 6.1 21 21 0 01-4.3 4.5 17.6 17.6 0 01-5.3 2.8 18.4 18.4 0 01-6 1 13.2 13.2 0 01-5.2-.9 6.9 6.9 0 01-3.3-2.8 8.5 8.5 0 01-1-4.7 21.8 21.8 0 011.5-6.8l14-38.6a1.1 1.1 0 000-1 1.4 1.4 0 00-.8-.6 8 8 0 00-1.8-.4 26.1 26.1 0 00-3.2-.1zm113.7 0c-1.2 0-2.3 0-3 .2a8.5 8.5 0 00-2 .4 4 4 0 00-1.4.8 2.2 2.2 0 00-.6 1l-11.2 30.6-2.6 7.3-2.5 7.4c0-1.3-.2-2.7-.4-4l-.4-4.1-.6-4.2-.7-4.3-4-23.9a18.8 18.8 0 00-.6-3.3 5.2 5.2 0 00-1.2-2.1 4.3 4.3 0 00-2-1.1 12 12 0 00-3.1-.3H585a7 7 0 00-3.9 1.2 6.9 6.9 0 00-2.7 3.5L557 229.5a1.5 1.5 0 000 .9 1.2 1.2 0 00.6.7 5.8 5.8 0 001.6.4 24.2 24.2 0 003 .1 28.3 28.3 0 003-.1 9.9 9.9 0 002-.4 3.4 3.4 0 001.1-.7 2.4 2.4 0 00.6-1l12.5-34.2a344 344 0 005-14.7c0 2 .2 4 .4 6.2l.8 5.9 5.1 30.6a35.1 35.1 0 00.8 4 7.2 7.2 0 001.2 2.6 3.9 3.9 0 001.9 1.3 10.1 10.1 0 003 .3h5.6a7.5 7.5 0 002-.2 6.6 6.6 0 002-1 8 8 0 001.5-1.4 7.2 7.2 0 001.2-2.1l21.4-58.8a1.4 1.4 0 000-1 1.2 1.2 0 00-.5-.6 4.3 4.3 0 00-1.7-.5 26.7 26.7 0 00-2.8 0zm-472 .3a5.7 5.7 0 00-3.2 1 6.3 6.3 0 00-2.3 3.2L130 227c-.5 1.5-.5 2.6 0 3.2a3 3 0 002.5 1h32.8a2 2 0 001-.3 3.5 3.5 0 001-.9 9 9 0 001-1.6 23.7 23.7 0 001-2.4 23 23 0 00.7-2.5 6.2 6.2 0 00.2-1.6 1.3 1.3 0 00-.3-.8 1 1 0 00-.7-.3h-23.7l6.7-18.5H172a2 2 0 001-.2 3.2 3.2 0 001-.9 7.7 7.7 0 00.9-1.5 22.9 22.9 0 001-2.4 20 20 0 00.7-2.4 5.2 5.2 0 00.2-1.6 1.2 1.2 0 00-.3-.8 1 1 0 00-.8-.3H156l5.8-16h23.4a1.7 1.7 0 00.9-.3 3.7 3.7 0 001-.9 10 10 0 00.9-1.6 23.1 23.1 0 001-2.4 21.7 21.7 0 00.8-2.5 6.3 6.3 0 00.2-1.6 1.3 1.3 0 00-.3-.9 1 1 0 00-.7-.2zm54.8 0a5.6 5.6 0 00-3.1 1 6.2 6.2 0 00-2.3 3.2L184 229.5a1.3 1.3 0 000 1 1.3 1.3 0 00.9.6 8.3 8.3 0 001.9.4 26.4 26.4 0 003.1.1 29.7 29.7 0 003.3-.1 12.7 12.7 0 002.2-.4 3.8 3.8 0 001.3-.7 2.2 2.2 0 00.7-.9l9-25h4.2a8.2 8.2 0 013.5.7 5 5 0 012.1 2 9.2 9.2 0 011.1 3.2 43.2 43.2 0 01.5 4.4l.5 14.6a3 3 0 00.2 1 1.3 1.3 0 00.7.7 6 6 0 001.9.4l3.4.1 4-.1a11.3 11.3 0 002.3-.4 2.6 2.6 0 001.2-.6 2.9 2.9 0 00.5-1 5.7 5.7 0 00.3-1.3 27.5 27.5 0 000-2.8l-.9-12.9a34 34 0 00-.5-4.1 15.4 15.4 0 00-1-3.3 8.2 8.2 0 00-1.5-2.5 8.5 8.5 0 00-2.1-1.7 32.2 32.2 0 006.3-2.5 27.4 27.4 0 005.4-3.7 25.3 25.3 0 004.2-4.8 27.8 27.8 0 003-6 19.5 19.5 0 001.3-7 10 10 0 00-1.4-5.1 10.1 10.1 0 00-4.1-3.6 20.3 20.3 0 00-6.4-2 59.6 59.6 0 00-7.2-.3zm159.8 0a5.7 5.7 0 00-3.2 1 6.3 6.3 0 00-2.3 3.2l-20.8 57c-.5 1.5-.5 2.6 0 3.2a3 3 0 002.5 1h32.9a2 2 0 00.9-.3 3.3 3.3 0 001-.9 8 8 0 001-1.6 21.5 21.5 0 001-2.4 23 23 0 00.7-2.5 5.7 5.7 0 00.2-1.6 1.2 1.2 0 00-.3-.8 1 1 0 00-.7-.3h-23.7l6.7-18.5h19.8a2 2 0 00.9-.2 3.1 3.1 0 001-.9 8.2 8.2 0 00.9-1.5 22.9 22.9 0 001-2.4 21.8 21.8 0 00.7-2.4 5.2 5.2 0 00.2-1.6 1.3 1.3 0 00-.3-.8 1 1 0 00-.7-.3h-19.8l5.8-16h23.4a1.8 1.8 0 00.9-.3 3.7 3.7 0 00.9-.9 8.1 8.1 0 001-1.6 22.8 22.8 0 001-2.4 23 23 0 00.7-2.5 6 6 0 00.2-1.6 1.3 1.3 0 00-.3-.9 1 1 0 00-.7-.2zm-277.4 0a5.7 5.7 0 00-3.1 1 6.3 6.3 0 00-2.3 3.2l-20.8 57c-.5 1.5-.6 2.5 0 3.2a3 3 0 002.4 1h17.4a52.2 52.2 0 007.4-.5 41.4 41.4 0 006.6-1.5 36.7 36.7 0 006-2.5 29.6 29.6 0 005.5-3.8 29.1 29.1 0 004.5-5 27 27 0 003.2-6.3 17.2 17.2 0 001.2-6 10.5 10.5 0 00-1-4.7 8.5 8.5 0 00-3-3.2 12.2 12.2 0 00-4.6-1.8 22.8 22.8 0 004.7-2.1 22.2 22.2 0 004-3 23.1 23.1 0 003.1-3.8 22.6 22.6 0 002.2-4.5 16.3 16.3 0 001.2-7.4 8.3 8.3 0 00-2.5-5.2 13.1 13.1 0 00-5.9-3 38.9 38.9 0 00-9.8-1zm5.5 10h6.7a15.5 15.5 0 014.8.5 5.3 5.3 0 012.4 1.6 4.1 4.1 0 01.9 2.6 10 10 0 01-.7 3.5 13.8 13.8 0 01-1.8 3.4 12.8 12.8 0 01-2.7 2.8 12.3 12.3 0 01-3.5 1.8 15.1 15.1 0 01-4.9.7h-7.5zm117.8.2h6a30.9 30.9 0 013.5.1 14.8 14.8 0 012.2.4c2 .7 3.2 1.7 3.6 3.2a8.8 8.8 0 01-.5 5.4 14.2 14.2 0 01-2.1 3.9 13.3 13.3 0 01-3.2 3 15.2 15.2 0 01-4.4 1.9 18.5 18.5 0 01-5.2.7H210zM89.3 202.6h7.9a19.6 19.6 0 015.7.6 6.6 6.6 0 013 1.9 5 5 0 011.1 3 10.1 10.1 0 01-.7 4 13 13 0 01-2.1 4 13.6 13.6 0 01-3.3 2.8 14.2 14.2 0 01-3.9 1.7 18.3 18.3 0 01-4.9.6h-9.6z"/> </svg> woocommerce-blocks/images/payment-methods/jcb.svg 0000644 00000004007 15133551764 0016213 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 471"> <defs> <linearGradient id="a" x1="0%" x2="100%" y1="50%" y2="50%"> <stop offset="0%" stop-color="#007B40"/> <stop offset="100%" stop-color="#55B330"/> </linearGradient> <linearGradient id="b" x1=".5%" x2="100%" y1="50%" y2="50%"> <stop offset="0%" stop-color="#1D2970"/> <stop offset="100%" stop-color="#006DBA"/> </linearGradient> <linearGradient id="c" x1=".1%" x2="100%" y1="50%" y2="50%"> <stop offset="0%" stop-color="#6E2B2F"/> <stop offset="100%" stop-color="#E30138"/> </linearGradient> </defs> <g fill="none" fill-rule="evenodd"> <rect width="750" height="471" fill="#0E4C96" rx="40"/> <path fill="#FFF" d="M617.2 346.8A75.4 75.4 0 01542 422H132.8V124.2A75.4 75.4 0 01208 49h409.1v297.9z"/> <path fill="url(#a)" d="M483.9 242c11.6.3 23.4-.5 35 .4 11.8 2.2 14.7 20 4.2 26-7.1 3.8-15.6 1.4-23.4 2H484V242zm41.8-32.1c2.6 9.2-6.2 17.4-15 16.1h-26.8c.1-8.6-.4-18 .2-26.2 10.8.3 21.6-.6 32.2.5 4.6 1.2 8.5 5 9.4 9.6zM590 74c.5 17.5 0 36 .2 53.8v217.8a54 54 0 01-51.6 51.4h-81.2V287.3c29.5-.2 59 .3 88.5-.3 13.6-.8 28.6-9.9 29.2-24.9 1.6-15.1-12.6-25.5-26.1-27.2-5.2-.1-5-1.5 0-2.1 12.9-2.8 23-16.1 19.2-29.5-3.2-14-18.8-19.5-31.7-19.5-26.4-.2-52.7 0-79 0 .1-20.5-.4-41 .2-61.5A54 54 0 01511.3 74H590z"/> <path fill="url(#b)" d="M159.7 125a54 54 0 0152-51h80.8c-.1 90.9.1 181.8-.2 272.7a54 54 0 01-51.6 50.3h-81V283.6c26.2 6.1 53.7 8.8 80.4 4.7 16-2.6 33.5-10.5 39-27 4-14.2 1.7-29.2 2.3-43.7v-33.9H235c-.2 22.4.4 44.8-.4 67.2-1.2 13.7-14.8 22.4-27.8 22-16 .1-47.9-11.7-47.9-11.7 0-41.9.5-94.4.7-136.2z"/> <path fill="url(#c)" d="M309.7 197.4c-2.4.5-.5-8.3-1-11.7 0-21.1-.4-42.3.2-63.4A54 54 0 01362.6 74h78.8c0 90.9.1 181.8-.1 272.7a54 54 0 01-51.7 50.3h-81V272.7c18.4 15.1 43.5 17.5 66.5 17.5a223 223 0 0051.3-6.6v-22.8c-19 9.4-41.2 15.4-62.2 10a33.9 33.9 0 01-25-33c-1.8-15.7 7.5-32.3 23-37 19.1-6 40-1.4 58 6.5 3.9 2 7.8 4.5 6.2-2v-17.9c-30-7.1-62-9.8-92.3-2a67.7 67.7 0 00-24.4 12z"/> </g> </svg> woocommerce-blocks/images/payment-methods/sepa.svg 0000644 00000005476 15133551764 0016420 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" id="svg10908" x="0" y="0" version="1.1" viewBox="0 0 750 205" xml:space="preserve"> <path id="path5689" d="M166.6 65.4h-51.3c0-8.4-1-14.1-3-17-3-4.3-11.4-6.4-25.2-6.4-13.4 0-22.3 1.2-26.6 3.7S54 53.6 54 61.8c0 7.5 1.9 12.4 5.7 14.7 3.4 1.8 7.1 2.8 10.9 2.9l10.4.8c22.2 1.5 36 2.5 41.6 3.1 17.5 1.8 30.3 6.4 38.1 14a37.2 37.2 0 0111.1 23.1 156 156 0 011 19.3 103 103 0 01-4.7 36.2c-5.7 14.1-18.5 22.9-38.5 26.6-8.3 1.6-21.1 2.4-38.3 2.4-28.7 0-48.8-1.7-60.1-5.1a39.4 39.4 0 01-27.5-25.6c-2.5-7.1-3.7-19-3.7-35.7h51.3v4.3c0 8.9 2.6 14.6 7.7 17.2 4 1.9 8.4 2.8 12.8 2.8h18.9c9.6 0 15.8-.5 18.4-1.5 4.7-1.9 7.8-4.9 9.3-9 .9-3.2 1.3-6.5 1.2-9.9 0-9-3.3-14.5-9.9-16.5-2.5-.8-13.9-1.9-34.3-3.2a421.8 421.8 0 01-34.2-3.4c-16.8-3.1-28-9.6-33.6-19.4C2.7 91.5.2 78.9.2 62c0-12.8 1.3-23.1 4-30.9S11.1 17.5 17 13.5A64.8 64.8 0 0150.2 2.6c11.2-1 23.7-1.5 37.3-1.5 21.5 0 36.9 1.2 46 3.7 22.3 6 33.5 22.8 33.5 50.4.1 2.3-.1 5.7-.4 10.2" class="st0"/> <path id="path5693" d="M392.3 205V0h102.8c14.1 0 24.8 1.1 32.2 3.5a50 50 0 0134 32.5c3 8.6 4.5 21.5 4.5 38.8 0 20.8-1.7 35.7-5 44.8-6.6 18-20.1 28.3-40.6 31-2.4.4-12.7.8-30.8 1.1l-9.2.3h-32.8v53h-55.1zm55.2-100.5h34.4a62.5 62.5 0 0019.9-2.5c3.2-1.8 5.5-5.4 6.5-10.8.8-5.3 1.2-10.6 1-16 0-8.7-.7-15.1-2.1-19.3-2-5.9-6.8-9.5-14.2-10.8-1.5-.2-5.1-.3-10.7-.3h-34.8v59.7z" class="st0"/> <path id="path5697" d="M683.6 169.5h-73.8l-9.9 35.5h-57.1L604.5 0h83.2L750 205h-55.9l-10.5-35.5zm-11-39.9l-25.8-88.9-25.1 88.9h50.9z" class="st0"/> <path id="path5701" fill="#ffbe00" d="M316.2 28.8c21.9 0 42.6 9.9 56.4 26.9l11.9-25.5a104.5 104.5 0 00-71.9-28.4c-43.5 0-80.6 26.2-95.9 63.4h-23.2l-13.7 29.3h29.8c-.2 2.7-.4 5.4-.4 8.2 0 3.3.2 6.7.5 10h-15L181 142.1h36.3a103.5 103.5 0 0095.2 61.7c20.7 0 40.9-6 58.1-17.5v-36a72.5 72.5 0 01-115.4-8.2h76l13.7-29.4H244.2a78.5 78.5 0 01-.4-18.2h109.8l13.7-29.3h-114a72.7 72.7 0 0162.9-36.4"/> <path id="path5705" d="M316.2 29.7c21.6 0 42.1 9.8 55.7 26.6l.9 1.1.6-1.3 11.9-25.5.3-.6-.5-.4a106 106 0 00-131.5-10.7 102.7 102.7 0 00-37.7 46l.8-.6H193l-.2.5-13.7 29.4-.6 1.3h31.2l-.9-1c-.2 3-.4 5.8-.4 8.3 0 3.4.2 6.7.5 10.1l.9-1h-15.5l-.2.5-13.7 29.4-.6 1.2h37.7l-.8-.5c16.4 37.8 54 62.2 96 62.2a105 105 0 0058.6-17.6l.4-.3V148l-1.5 1.8a71.7 71.7 0 01-114-8.1l-.7 1.4h76.6l.2-.5 13.7-29.4.6-1.2H244.2l.9.8a76 76 0 01-.4-18l-.9.8h110.4l.2-.5L368 65.6l.6-1.3H253.2l.8 1.3a72.2 72.2 0 0162.2-35.9m-63 36.4h114l-.8-1.2-13.7 29.4.8-.5H242.9l-.1.8c-.2 2.5-.4 5-.4 7.5 0 3.7.3 7.4.8 11l.1.8H345l-.8-1.3-13.7 29.4.8-.5h-77.7l.9 1.4a73.5 73.5 0 00116.9 8.2l-1.5-.6v36l.4-.7a105.4 105.4 0 01-57.7 17.2c-41.2 0-78.3-24-94.4-61.1l-.2-.5h-36.9l.8 1.3 13.7-29.4-.8.5h16l-.1-1a111.2 111.2 0 01-.1-18.1l.1-1h-30.8l.8 1.2 13.7-29.3-.8.5h23.8l.2-.5a100.5 100.5 0 0137.1-45.2c40.3-27 93.9-22.6 129.2 10.5l-.2-1-11.9 25.5 1.5-.2a73.5 73.5 0 00-120.8 9.6l-.8 1.3h1.5z" class="st0"/> </svg> woocommerce-blocks/images/payment-methods/eps.svg 0000644 00000010452 15133551764 0016245 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="Layer_1" x="0" y="0" version="1.1" viewBox="0 0 750 576.8" xml:space="preserve"> <symbol id="EPS-Logo" viewBox="-385 -236.3 770 472.8"> <path id="XMLID_367_" d="M219 0h-61.3c-6.8 0-12.4 5.4-12.4 12.2S150.9 25 157.7 25H251v46h-93.3a59.1 59.1 0 010-118h60.4c6.8 0 12.4-5.4 12.4-12.2 0-6.8-5.5-11.8-12.4-11.8H88.6c-11-21-21.7-39-43.4-48H219a59.5 59.5 0 010 119z" class="st0"/> <path id="XMLID_201_" d="M-15.8 71A94.6 94.6 0 01-110-23.8V-221h47v102h47.1a95 95 0 0194 95.4c0 52.2-42 94.6-93.9 94.6zm0-142H-63v47.4c0 26.5 21.1 48 47.2 48a47.8 47.8 0 000-95.4z" class="st0"/> <g id="XMLID_196_"> <g id="XMLID_198_"> <path id="XMLID_200_" d="M-258.8-119a95.6 95.6 0 00-91.9 73s-2.9 13.7-2.9 22.7 2.8 22.8 2.8 22.8a94.8 94.8 0 00186.9-22.6V-46h-138.6c8.2-16 24.7-25 43.8-25h124.6l.2 135.3a36.8 36.8 0 01-36.7 36.7h-176.6c-20.2 0-36.7-16-36.7-36.2v-176.6a37.2 37.2 0 0136.7-37.2h176.6c18.1 0 33.2 13 36.1 30h-124.3z" class="st2"/> <path id="XMLID_199_" d="M-259 26.6A51 51 0 01-302.8 0h87.6A51 51 0 01-259 26.6z" class="st2"/> </g> <path id="XMLID_197_" d="M-179 158.4a79 79 0 01-80 78.1 79.1 79.1 0 01-80-75.9v-24c0-2.8 2.3-5.6 5.2-5.6h29.4c2.9 0 5.4 2.8 5.4 5.6v21.9c0 21.5 17.9 39 40 39s40-17.5 40-39v-21.9c0-2.8 2.3-5.6 5.2-5.6h29.4c2.9 0 5.3 2.8 5.3 5.6v21.8h.1z" class="st2"/> </g> <g id="XMLID_13_"> <path id="XMLID_192_" d="M36.9-180l-4.3-24.1c-2.1-12.2-10.1-17.8-20.9-17.8-9.2 0-16.3 6-14.2 17.7L1.8-180h6.6l-4.3-24.1c-1.3-7.5 2.1-11.8 8.5-11.8 6.6 0 12.2 3.9 13.5 11.8l4.3 24.1h6.5zm-25.7 7.5c0-2 1.5-3 3.2-3 2.1 0 4.3 1.5 4.3 4.3 0 1.9-1.3 3-3 3-2.2 0-4.5-1.4-4.5-4.3zm14.1.1c0-2.1 1.6-3 3.3-3 1.8 0 4.3 1.5 4.3 4.2 0 1.9-1.4 3-3 3-2.1 0-4.6-1.4-4.6-4.2z" class="st0"/> <path id="XMLID_189_" d="M52.8-180l-3-16.7a15 15 0 0011.2 5.2c7.3 0 12.4-4.3 12.4-12.3 0-11.3-7.9-18.1-17.9-18.1-3.9 0-7.2 1.4-9.2 5.1l-1.1-4.3h-5.7l7.3 41h6zm-4.6-28.1c-.2-4.8 2.9-8 7.4-8 5.7 0 11.3 4.4 11.6 11.2.2 4.9-3 7.8-7.4 7.8-5.6 0-11.3-4.6-11.6-11z" class="st0"/> <path id="XMLID_186_" d="M85.4-209c-.2-4 2.9-7.3 8.2-7.3 3 0 6.9 1.2 9 3.2l3.3-3.8c-3.6-3.3-8.9-5-13.4-5-8.4 0-13.3 5-13.3 12.7 0 10.2 8 17.8 18.3 17.8 9.4 0 14.7-5.7 11.5-17.7H85.4zm18.7 5c.5 5-2.5 7-7.4 7-4.4 0-8.5-2-10.3-7h17.7z" class="st0"/> <path id="XMLID_184_" d="M125.3-192l-.3-3.9a11 11 0 009.4 4.5c2.8 0 5.4-1 6.7-2.5l-3.7-5.3a6.3 6.3 0 01-4.8 1.8 9.2 9.2 0 01-9.3-7.9l-2.8-15.7h-6.1l5.1 29h5.8z" class="st0"/> <path id="XMLID_182_" d="M170.5-192l3.1-22.9 11.1 22.9h6.8l-15.3-29H169l-2.9 19.1-4.7-9.5-5.1-9.6h-7.1l-5.1 29h6.9l3-22.9 11.2 22.9h5.3z" class="st0"/> <path id="XMLID_179_" d="M199.3-209c-.2-4 2.9-7.3 8.2-7.3 3 0 6.9 1.2 9 3.2l3.3-3.8c-3.6-3.3-8.9-5-13.4-5-8.4 0-13.3 5-13.3 12.7 0 10.2 8 17.8 18.3 17.8 9.4 0 14.7-5.7 11.5-17.7h-23.6zm18.6 5c.5 5-2.5 7-7.4 7-4.4 0-8.5-2-10.3-7h17.7z" class="st0"/> <path id="XMLID_168_" d="M239.6-192l-5.1-29h-6.1l5.1 29h6.1zm-5.4 7.9c0-2.1 1.5-3.1 3.2-3.1 2.4 0 4.6 1.5 4.6 4.4a3 3 0 01-3.1 3.1c-2.2 0-4.7-1.4-4.7-4.4z" class="st0"/> <path id="XMLID_60_" d="M266.6-199.2c-1.9 2.1-4.3 2.7-7.2 2.7-4 0-6.9-1.4-6.9-3.9 0-2.1 2.3-3 5.7-3.3 5.3-.5 12.1-2.2 10.7-9.8-1-5.4-6.4-8.6-14-8.6-4.7 0-9.3 1.1-12.4 5.4l4 4.3c2.2-3 6-4.3 9.5-4.3 2.9 0 6.3 1.1 6.9 3.8.5 2.6-1.8 3.6-6 4-5 .5-10.4 2.3-10.4 7.8 0 7.3 7.9 9.8 13.8 9.8 4.5 0 7.8-1 10.5-3.9l-4.2-4z" class="st0"/> <path id="XMLID_57_" d="M285.8-192l-2.8-15.6c-.9-5 1.4-8.4 6.4-8.4 4.8 0 8.9 4 9.7 8.8l2.7 15.2h6.1l-5.1-29h-5.5l.4 4.2a15 15 0 00-10.9-4.8c-7.2 0-11.4 5.1-9.9 13.9l2.8 15.6h6.1z" class="st0"/> <path id="XMLID_55_" d="M334.7-221l2.8 15.5c.9 5-.8 8.4-6.4 8.4-4.8 0-8.9-3.9-9.8-8.7l-2.7-15.2h-6.1l5.1 29h5.6l-.4-4.2c3.4 3.1 6.9 4.7 10.6 4.7 7.1 0 11.7-4.9 10.1-13.9l-2.8-15.6h-6z" class="st0"/> <path id="XMLID_15_" d="M355.3-225c-.4-4 2.7-5.6 7.8-5.6 4.5 0 9.3 2.5 10.5 8.9l.9 5a14 14 0 00-11.1-5.2c-7.5 0-12.7 4.4-12.7 12.5 0 11.5 8.5 18.1 18.1 18.1 4.1 0 7.7-1.9 9-5.2l1 4.6h5.9l-5.1-29.9c-1.9-11.1-10.1-14.5-17.6-14.5-9 0-14 4.3-12.7 11.3h6zm1.7 16.7c0-5 3.3-8 7.9-8 12.6 0 16 19.3 3.4 19.3a11 11 0 01-11.3-11.3z" class="st0"/> </g> </symbol> <use id="XMLID_202_" width="770" height="472.8" x="-385" y="-236.3" overflow="visible" transform="matrix(.9728 0 0 -.9728 375.5 285.5)" xlink:href="#EPS-Logo"/> </svg> woocommerce-blocks/images/payment-methods/multibanco.svg 0000644 00000012056 15133551764 0017615 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" preserveAspectRatio="xMidYMid meet" viewBox="0 0 750 885.9"> <defs/> <g id="g14"> <g id="g20"> <path id="path22" fill="#2c6cae" d="M371.2 713.9h307c70.2 0 70.9-74.5 63.8-111.3-4-24.8-46-24.6-50.6 0v28.7a24 24 0 01-23.9 23.8H78.7A24 24 0 0155 631.3v-28.7c-4.6-24.6-46.7-24.8-50.6 0C-2.8 639.4-2 713.9 68 713.9h303.2zM154 0h473.6C660.9 0 688 28.8 688 64v30.6c0 43.3-58.6 43-58.6.3V78.2A19.4 19.4 0 00610 58.9H135.6a19.4 19.4 0 00-19.3 19.3v16.4c0 43-55.8 42.7-55.8.9V64c0-35.2 27.2-64 60.5-64z"/> </g> <g id="g24"> <path id="path26" d="M676.1 351.7a93.6 93.6 0 0155.5 83.8c0 51.6-45 93.7-100 93.7H477.3c-14.4 0-26.2-11-26.2-24.5V211.3a25.8 25.8 0 0125.7-25.8h128.5c54 0 98.3 44.2 98.3 98.2a97.8 97.8 0 01-27.5 68m-115.1-24h49v-.5a46.7 46.7 0 0039.5-46 46.7 46.7 0 00-46.6-46.5h-98.6V478h125a48 48 0 100-96.2H561a27 27 0 110-54.1" class="cls-2"/> </g> <g id="g28"> <path id="path30" d="M173.6 804a8.3 8.3 0 0116.6 0v44.7a37.2 37.2 0 01-37.2 37.1 37.2 37.2 0 01-37.1-37V804a8.3 8.3 0 0116.5 0v44.8a20.7 20.7 0 0020.6 20.5 20.7 20.7 0 0020.6-20.6z" class="cls-2"/> </g> <g id="g32"> <path id="path34" d="M251.8 869.3a8.3 8.3 0 110 16.6h-22.9a31.1 31.1 0 01-31-31V804a8.3 8.3 0 1116.5 0v50.8a14.7 14.7 0 0014.6 14.6h22.8z" class="cls-2"/> </g> <g id="g36"> <path id="path38" d="M426.6 500a31 31 0 11-61.6 6.5L339.7 273l-89.8 228.2-.1.2-.3.6-.2.5v.1l-.2.5-.1.3-.2.4-.1.2-.3.5v.2a30.7 30.7 0 01-6.4 8.2h-.1l-.5.5a30.5 30.5 0 01-5.3 3.8h-.1l-.6.4-.6.3h-.2l-.4.2-.3.2-.4.2-.3.1-.4.2-.3.1-.3.1h-.1l-.3.2-.4.1-.3.1a31 31 0 01-9 1.7h-2.3a30.8 30.8 0 01-10.1-2l-.4-.2h-.1l-.5-.2-.4-.2-.3-.1-.3-.2-.4-.2h-.2l-.6-.4-.5-.2-.3-.2a31 31 0 01-4.7-3.3h-.1l-1-1-.8-.7-.8-.8-.9-1v-.1a30.9 30.9 0 01-3.4-4.7l-.1-.3-.3-.5-.3-.6v-.2l-.2-.4-.2-.3-.2-.4-.1-.4-.2-.4-.2-.5-90-228.6-25.4 233.4a31 31 0 11-61.5-6.6l29.9-275v-.2a51.5 51.5 0 0144.2-45.2l1.8-.2a52.5 52.5 0 017.8-.3h.1a52.7 52.7 0 019.4 1.3 50.8 50.8 0 0136 31L221 405.7l76.5-194.4a50.8 50.8 0 0136-31 52.4 52.4 0 019.4-1.3h.1a52.6 52.6 0 017.8.2l1.7.2a51.3 51.3 0 0138.5 26.7 52.1 52.1 0 013.8 9 50.7 50.7 0 012 9.5v.2z" class="cls-2"/> </g> <g id="g40"> <path id="path42" d="M112.8 876.6a8.3 8.3 0 11-16.4 2L89.3 819 64 877.6a8.3 8.3 0 01-15.2 0L23.6 819l-7.1 59.5A8.3 8.3 0 110 876.6l8.2-69.1a13.3 13.3 0 017.2-10.3 15.5 15.5 0 011.6-.7l.7-.2a15.3 15.3 0 012.4-.6h.1a14.3 14.3 0 0111.6 3.7 13.6 13.6 0 012.6 3.5 6.6 6.6 0 01.5.9l21.4 49.7 21.4-49.7a13.6 13.6 0 019.7-7.8 15 15 0 012.4-.3 15.6 15.6 0 011.8 0h.7a14 14 0 0110.4 6.6 13.2 13.2 0 011 2.4 12 12 0 01.5 1.6 8 8 0 01.2 1v.2z" class="cls-2"/> </g> <g id="g44"> <path id="path46" d="M287.9 877.6a8.3 8.3 0 01-16.5 0v-65.4h-23.6a8.3 8.3 0 010-16.5h63.6a8.3 8.3 0 010 16.6H288z" class="cls-2"/> </g> <g id="g48"> <path id="path50" d="M339.5 877.6a8.3 8.3 0 01-16.6 0V804a8.3 8.3 0 1116.6 0z" class="cls-2"/> </g> <g id="g52"> <path id="path54" d="M509.1 876.6a8.3 8.3 0 11-16.4 2l-2.9-23.4h-28.7a8.3 8.3 0 110-16.5h26.6l-.7-6v-.1-.6a32 32 0 00-1-4.2 27.7 27.7 0 00-1.7-4.4c-3.3-6.6-9.2-11.2-16.7-11.2h-.1a17.8 17.8 0 00-4.2.5 16.9 16.9 0 00-3.7 1.4 23.1 23.1 0 00-11.5 18.4l-5.1 46a8.3 8.3 0 11-16.5-1.8l5.2-46c1.5-13.5 9-25.5 20.5-31.3a33.3 33.3 0 017.3-2.7 34.5 34.5 0 018-1c14.4 0 25.5 8.4 31.5 20.3a43.8 43.8 0 012.8 7 46.8 46.8 0 011.4 6.8 6.7 6.7 0 01.2.8z" class="cls-2"/> </g> <g id="g56"> <path id="path58" d="M534.4 877.6a8.3 8.3 0 01-16.5 0v-68.4a8 8 0 01.1-1.4 11.3 11.3 0 01.7-2.7 14.3 14.3 0 01.6-1.5 11.6 11.6 0 016.5-5.4 9.7 9.7 0 011.1-.3l1-.2a13.2 13.2 0 012 0h.2a11.6 11.6 0 018.7 4.4l44 56.1V804a8.3 8.3 0 1116.6 0v68a11.6 11.6 0 01-4.3 9.1 12 12 0 01-1.7 1.2l-1 .5h-.1a7 7 0 01-.7.3 11.6 11.6 0 01-8.9-.5 7.1 7.1 0 01-1-.6 11.7 11.7 0 01-1.7-1.2 10.3 10.3 0 01-1-1.1l-.2-.3-.1-.2-44-56z" class="cls-2"/> </g> <g id="g60"> <path id="path62" d="M663.6 869.3a8.3 8.3 0 110 16.6h-24.4a32.7 32.7 0 01-32.6-32.6v-25a32.6 32.6 0 0132.6-32.5h24.4a8.3 8.3 0 110 16.5h-24.4a16.2 16.2 0 00-16 16.1v25a16.2 16.2 0 0016 16h24.4z" class="cls-2"/> </g> <g id="g64"> <path id="path66" d="M711.5 812.2a22.6 22.6 0 00-15.7 6.3 20.3 20.3 0 00-6.4 14.6v15.4a20.3 20.3 0 006.4 14.6 22.6 22.6 0 0015.7 6.3A22.5 22.5 0 00727 863a20.2 20.2 0 006.4-14.6V833a20.2 20.2 0 00-6.4-14.6 22.6 22.6 0 00-15.7-6.3m0-16.5a39.1 39.1 0 0127.2 10.8 36.7 36.7 0 0111.4 26.6v15.4a36.7 36.7 0 01-11.5 26.5 39 39 0 01-27 10.8 39.1 39.1 0 01-27.2-10.8 36.7 36.7 0 01-11.4-26.5v-15.4a36.7 36.7 0 0111.4-26.5 39 39 0 0127.1-10.8z" class="cls-2"/> </g> <g id="g68"> <path id="path70" d="M363 812.2v57.1h32a10.5 10.5 0 0010.4-10.4 10.3 10.3 0 00-2.8-7l-.3-.3a10.3 10.3 0 00-7.3-3.1h-11a8.3 8.3 0 110-16.5h3.8a10 10 0 006.2-3 9.9 9.9 0 002.9-7 10 10 0 00-9.9-9.8h-24zm-16.5 28v-35.9a8.3 8.3 0 01.6-3.3 8.4 8.4 0 011.9-2.8h.1a8.5 8.5 0 015.4-2.5 2.7 2.7 0 01.5 0h32a26.4 26.4 0 0126.3 26.4 26.3 26.3 0 01-4 14 27.4 27.4 0 014.6 3.8l.5.5a26.9 26.9 0 017.4 18.4 27 27 0 01-26.9 27h-40.2a8.3 8.3 0 01-8.3-8.3z" class="cls-2"/> </g> </g> </svg> woocommerce-blocks/images/payment-methods/visa.svg 0000644 00000001706 15133551764 0016422 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 750 471"> <g fill="none" fill-rule="evenodd"> <rect width="750" height="471" fill="#0e4595" rx="40"/> <path fill="#fff" d="M278.2 334.2l33.4-195.7h53.3l-33.4 195.7zm246.1-191.5c-10.6-4-27.1-8.2-47.8-8.2-52.7 0-89.9 26.5-90.2 64.6-.3 28.1 26.5 43.8 46.8 53.2 20.7 9.5 27.7 15.7 27.6 24.2-.1 13.2-16.6 19.2-32 19.2-21.3 0-32.6-3-50.1-10.3l-7-3.1-7.4 43.8a169 169 0 0059.4 10.4c56.1 0 92.5-26.2 93-66.8.2-22.3-14-39.3-44.8-53.2-18.7-9-30.1-15.1-30-24.3 0-8.1 9.7-16.8 30.6-16.8 17.4-.3 30 3.5 39.9 7.5l4.8 2.2 7.2-42.4m137.3-4.2h-41.2c-12.8 0-22.3 3.5-28 16.2l-79.2 179.4h56l11.3-29.4h68.3a3753 3753 0 016.5 29.4h49.5zm-65.4 126.4l21.3-54.8c-.4.6 4.3-11.3 7-18.6l3.6 16.8 12.4 56.6zM232.9 138.5L180.7 272l-5.6-27.2c-9.7-31.2-40-65.1-73.9-82L149 333.8h56.4l84-195.4z"/> <path fill="#f2ae14" d="M132 138.5H45.8l-.7 4c67 16.2 111.2 55.4 129.6 102.5l-18.7-90c-3.2-12.4-12.6-16.1-24.2-16.5"/> </g> </svg> woocommerce-blocks/images/payment-methods/p24.svg 0000644 00000005335 15133551764 0016067 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" preserveAspectRatio="xMidYMid meet" viewBox="0 0 750 256.9"> <defs/> <path d="M0 223.3l19-106.9h48.9c12 0 19.8 2.3 23.4 7s4.2 13.4 1.9 26.1c-2.2 12.2-6 20.6-11.3 25s-14.4 6.6-26.9 6.6H19.3L12 223.4zM21.3 171h28.6c11.9 0 19.8-1.2 23.6-3.7s6.5-8.1 8-16.9c1.8-10.3 1.8-16.8 0-19.6s-7-4-15.4-4l-4.6-.1H29.1zM104 148.5h11l-2.6 8.6.2.2c5.6-7 13.3-10.6 23.3-10.6q20.7 0 17 21.3l-1 5.2h-11l.6-1.8 1-4c1.3-7.7-2.1-11.5-10.5-11.5-12.2 0-19.6 7.5-22.3 22.6l-8 44.8h-11zM162.3 148.5h54l-2 10.6-54.3 55.1h44.6l-1.6 9h-57.7L147 213l54.6-55.4h-41zM270.1 200.6h11.3l-.4 2.8c-1.4 7.8-4.7 13.2-10 16.3s-13.8 4.7-25.7 4.7c-13.8 0-22.4-2.5-25.8-7.6s-3.7-15.6-.8-31.7c2.7-15 6.6-25 11.9-30s14.1-7.7 26.7-7.7c13.8 0 22.4 2.3 26 6.7s3.9 13.7 1.4 28l-1 5.8h-53.8c-2.2 11.8-2.2 19.3-.2 22.5s7.5 4.9 16.8 4.9q13.2 0 17.6-2.3t5.7-10zm3.8-21.2l.6-3.5c1.5-8.1 1-13.3-1.2-15.8s-7.8-3.6-16.6-3.6-14.9 1.4-18 4.3-5.5 9-7.3 18.7H274zM320.1 116.5l-19 106.8h-11l19-106.9zM372.1 200.6h11.2l-.3 2.8c-1.4 7.8-4.7 13.2-10 16.3s-13.9 4.7-25.7 4.7c-13.8 0-22.4-2.5-25.8-7.6s-3.7-15.6-.8-31.7c2.7-15 6.5-25 11.8-30s14.2-7.7 26.8-7.7c13.8 0 22.4 2.3 26 6.7s4 13.7 1.4 28l-1 5.8h-53.9c-2 11.8-2.1 19.3-.2 22.5s7.6 4.9 16.9 4.9q13.2 0 17.6-2.3t5.7-10zm3.8-21.2l.6-3.5c1.4-8.1 1-13.3-1.2-15.8s-7.8-3.6-16.6-3.6-14.9 1.4-18 4.3-5.6 9-7.3 18.7h42.5zM501.3 148.5l-34.2 74.8h-16.3l-2.6-41.6-.8-12.7-.3-6.3-.4-6.3h-.2l-2.6 6.3-2.6 6.3-5.3 12.7-17.5 41.6H402l-7-74.8h11.5l3.2 41.4 1 13 .4 6.5.3 6.5h.4l2.6-6.5 2.7-6.5 5.4-13 17.6-41.5h16l2.8 41.6.8 13 .4 6.4.3 6.5h.2l2.8-6.5 2.8-6.5 5.6-13 17.9-41.4h11.7zM556.5 148.5l-14.3 33.3-7.2 16.6-3.4 8.4-3.6 8.2h-.3l-1.4-8.2-1.3-8.4-2.9-16.6-5.6-33.3H505l15 81.3-1.8 3.7c-4.6 9.9-9.9 14.6-15.7 14.3a25.3 25.3 0 01-3.7-.5l-1.6 9a28.8 28.8 0 005.2.6c6.7 0 12.3-2.1 16.6-6.3s9-11.8 14-23l35.3-79.1z" class="cls-1"/> <path d="M638.3 213.8l-1.8 10h-75l3.4-20q3-16.7 10.3-21.9c4.9-3.4 15.5-6.1 31.9-8q19.6-2.2 24.3-6c3.1-2.4 5.6-8.9 7.4-19.3 1.6-9 1.1-15-1.4-17.7s-8.8-4-18.8-4c-12.5 0-20.6 1-24.3 3.3s-6.2 7.4-7.7 15.8l-1.2 8h-11.5l1-5.5c2.2-12.7 6.3-21.1 12.3-25.3s17-6.3 32.8-6.3c14 0 23 2.3 27.2 6.8s4.9 13.5 2.5 26.6q-3.3 19-11.2 25.4t-31 8.7c-13.4 1.4-21.6 3.2-24.6 5.6s-5.4 9.1-7.4 20.3l-.6 3.6h63.4zM737.2 118l-12.5 71.4h15.5l-1.7 10h-15.6l-4.3 24.5h-11.8l4.3-24.5h-58.4l2.5-14L719 118zM713 189.3l11.2-63.8h-.3l-59.6 63.8zM248 84a386.4 386.4 0 0173.2-33l-4.7-26.3a516.1 516.1 0 00-94 40zM538 69.7l90.4-49.2A491 491 0 00483.7 0h-.6l-20.7 41.3a254.3 254.3 0 0175.7 28.4zM199.5 78.2c-39.7 24.7-60.5 45.6-60.5 45.6h50.3c12.2-9.5 24.3-18 36-25.8zM453.9.6a547.1 547.1 0 00-117.5 18.3l3.5 26.8c37.8-9.9 72-11.2 101.7-7.7zM676.5 39.6l-117 43.8A148.6 148.6 0 01576 97h174s-19.7-30.5-73.5-57.3z" class="cls-2"/> </svg> woocommerce-blocks/images/payment-methods/bancontact.svg 0000644 00000014026 15133551764 0017573 0 ustar 00 <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" preserveAspectRatio="xMidYMid meet" viewBox="0 0 750 530.1"> <defs/> <path fill="#fff" d="M750 484.6a45.6 45.6 0 01-45.5 45.5H45.8A45.6 45.6 0 01.2 484.6v-439A45.6 45.6 0 0145.8 0h658.7A45.6 45.6 0 01750 45.5v439z"/> <path d="M602.6 148.1H422.5L404 168.8l-59.3 67-18.4 20.6H148.8l18-20.9 8.4-9.9 18-21h-81.6c-15 0-27.2 12.7-27.2 28v58.8a27.5 27.5 0 0027.1 27.8h313.3c15 0 35.4-9.2 45.3-20.7l47.4-53.8z" class="cls-2"/> <path fill="#ffd800" d="M638.7 85.3c15 0 27.1 12.7 27.1 27.9v58.6a27.5 27.5 0 01-27.1 27.8h-81.4l18.1-20.7 9-10.3 18.2-20.7H422.5l-96 108.1H148L276 111l4.8-5.4a70.3 70.3 0 0145.6-20.7h312.3z"/> <path d="M665.8 442.5V434c0-1.1-.7-1.8-2.3-1.8H658c-1.6 0-2.8-.3-3-1-.5-.6-.5-2-.5-4.3v-32.2h9a2.4 2.4 0 002.3-2.3v-9a2.4 2.4 0 00-2.3-2.3h-9v-11.7a2 2 0 00-.4-1.4 3.3 3.3 0 00-1.4-.5h-.2l-13.4 2.3a11.2 11.2 0 00-1.6.7 2 2 0 00-.7 1.4v9h-9a2.4 2.4 0 00-2.3 2.3v7.4a1.4 1.4 0 00.7 1.3 3.7 3.7 0 001.6 1l9 1.3v32.2a37 37 0 001.2 9.7 15.3 15.3 0 003.4 5.8 12.2 12.2 0 005.8 2.7 42 42 0 008 .7 26.4 26.4 0 004.3-.2c1.2-.3 2.8-.5 4.4-.7a1.7 1.7 0 001.9-1.9m-47.8-1.1v-10a1.4 1.4 0 00-.7-1.3 3.3 3.3 0 00-1.4-.5h-.2a56.6 56.6 0 01-6 .5 69.5 69.5 0 01-7.6.2 8.4 8.4 0 01-3.4-.7 8.8 8.8 0 01-3-2 8.8 8.8 0 01-1.9-4 23.1 23.1 0 01-.7-6.2v-9.2a23.1 23.1 0 01.7-6.2 12.4 12.4 0 011.9-3.9 8.8 8.8 0 013-2 9.2 9.2 0 013.4-.7 69.5 69.5 0 017.6.2l6 .4h.2a2 2 0 001.4-.4 1.4 1.4 0 00.7-1.4v-9.9a2.2 2.2 0 00-.5-1.6 6.7 6.7 0 00-1.8-1c-1.6-.4-3.5-.6-6-1a50 50 0 00-8.7-.5c-7.9 0-14 2.3-18.9 7.1a29.1 29.1 0 00-7.1 21v9.1c0 9 2.3 16.1 7.1 21a25 25 0 0018.9 7c3.2 0 6.2-.1 8.7-.4a55.9 55.9 0 006-1.1 3.5 3.5 0 001.8-1c.5-.2.5-.9.5-1.5m-72-11.8a24.5 24.5 0 01-4.4 1.6 17.5 17.5 0 01-4.8.7c-2.3 0-4.1-.2-5.3-.9s-1.6-2-1.6-4.6v-1a14 14 0 01.5-3.4 6 6 0 011.6-2.5 6 6 0 013-1.4 24.4 24.4 0 014.8-.4h6.2v12zm17.5-26.9a26.9 26.9 0 00-1.9-10.3 19.2 19.2 0 00-5-7 18.8 18.8 0 00-8-3.8 39.7 39.7 0 00-10.9-1.4 82.6 82.6 0 00-10.8.7 48 48 0 00-8.3 1.6c-1.3.4-2 1.1-2 2.5v9a3.3 3.3 0 00.4 1.6 3.3 3.3 0 001.4.5h.5c.9 0 2-.3 3.2-.3l4.4-.2 5.2-.2h5.3a11 11 0 016 1.4c1.4.9 2.3 3 2.3 6.2v3.9h-6c-9.4 0-16.6 1.4-20.7 4.4-4.4 3-6.4 7.8-6.4 14.2v1c0 3.6.4 6.6 1.6 9a16.8 16.8 0 004.3 6 18.8 18.8 0 006 3.2 24.3 24.3 0 007.2.9 23.6 23.6 0 008.5-1.4 33.6 33.6 0 006.9-3.7v1.9a2.4 2.4 0 002.3 2.3h12.4a2.4 2.4 0 002.3-2.3v-39.7zm-59.1 39.8V434c0-1.1-.7-1.8-2.3-1.8h-5.6c-1.6 0-2.7-.3-3-1-.4-.6-.4-2-.4-4.3v-32.2h9a2.4 2.4 0 002.3-2.3v-9a2.4 2.4 0 00-2.3-2.3h-9v-11.7a2 2 0 00-.5-1.4 3.3 3.3 0 00-1.3-.5h-.3l-13.3 2.3a11.2 11.2 0 00-1.6.7 2 2 0 00-.7 1.4v9h-9.2a2.4 2.4 0 00-2.3 2.3v7.4a1.4 1.4 0 00.7 1.3 3.7 3.7 0 001.6 1l9 1.3v32.2a37 37 0 001.1 9.7 15.3 15.3 0 003.5 5.8 12.2 12.2 0 005.7 2.7 42 42 0 008 .7 26.4 26.4 0 004.4-.2c1.2-.3 2.8-.5 4.4-.7 1.4 0 2-.7 2-1.9m-48.2-.2v-36.8a46.5 46.5 0 00-1-9.9 22.5 22.5 0 00-3-8 13.4 13.4 0 00-6-5.3 20.4 20.4 0 00-9.8-2 30.1 30.1 0 00-9.5 1.3 32.3 32.3 0 00-8.7 4.6v-2.8a2.4 2.4 0 00-2.3-2.3h-12.4a2.4 2.4 0 00-2.3 2.3v59a2.4 2.4 0 002.3 2.2h13.3a2.4 2.4 0 002.3-2.3v-43.5a58.8 58.8 0 015.6-2.5 13.1 13.1 0 015-1 20.6 20.6 0 014.2.5 4.8 4.8 0 012.5 1.6 6.7 6.7 0 011.1 3.2 30.9 30.9 0 01.3 4.9v36.8a2.4 2.4 0 002.3 2.3h13.3a2 2 0 001.6-.7 1.8 1.8 0 001.2-1.6m-83.7-25.6c0 9-3.3 13.6-10 13.6-3.2 0-5.7-1.1-7.3-3.4s-2.5-5.8-2.5-10.2V409c0-4.6.9-7.8 2.5-10.1s4.1-3.5 7.4-3.5c6.4 0 9.9 4.7 9.9 13.6zm17.9-7.8a38.4 38.4 0 00-1.9-12 25 25 0 00-5.2-9 23.2 23.2 0 00-8.8-5.7 35.6 35.6 0 00-23.9 0 23.2 23.2 0 00-8.7 5.7 25 25 0 00-5.3 9 38.4 38.4 0 00-1.9 12v7.8a38.4 38.4 0 001.9 12 25 25 0 005.3 9 23.2 23.2 0 008.7 5.8 35.6 35.6 0 0024 0 23.2 23.2 0 008.7-5.8 25 25 0 005.2-9 38.4 38.4 0 001.9-12zm-61.4 32.5v-10a1.4 1.4 0 00-.7-1.3 3.3 3.3 0 00-1.6-.5h-.2a56.6 56.6 0 01-6 .5 69.5 69.5 0 01-7.6.2 8.4 8.4 0 01-3.5-.7 8.8 8.8 0 01-3-2 8.8 8.8 0 01-1.8-4 23.1 23.1 0 01-.7-6.2v-9.2a23.1 23.1 0 01.7-6.2 12.4 12.4 0 011.8-3.9 8.8 8.8 0 013-2 9.2 9.2 0 013.5-.7 69.5 69.5 0 017.6.2l6 .4h.2a3.3 3.3 0 001.6-.4 1.4 1.4 0 00.7-1.4v-9.9a2.2 2.2 0 00-.5-1.6 6.7 6.7 0 00-1.8-1c-1.6-.4-3.5-.6-6-1a50 50 0 00-8.8-.5c-7.8 0-14 2.3-18.8 7.1a29.1 29.1 0 00-7.1 21v9.1c0 9 2.3 16.1 7 21a25 25 0 0019 7c3.2 0 6.2-.1 8.7-.4 2.5-.4 4.4-.7 6-1.1a3.5 3.5 0 001.8-1 2.7 2.7 0 00.5-1.5m-54.5.9v-36.8a46.5 46.5 0 00-1-10 22.5 22.5 0 00-3-8 13.4 13.4 0 00-6-5.3 20.4 20.4 0 00-9.8-2 30.1 30.1 0 00-9.5 1.4 32.3 32.3 0 00-8.7 4.6v-2.8a2.4 2.4 0 00-2.3-2.3h-12.4a2.4 2.4 0 00-2.3 2.3v58.9a2.4 2.4 0 002.3 2.3h13.4a2.4 2.4 0 002.3-2.3v-43.5a58.8 58.8 0 015.5-2.5 13.1 13.1 0 015-1 20.6 20.6 0 014.2.5 4.8 4.8 0 012.5 1.6 6.7 6.7 0 011.2 3.2 30.9 30.9 0 01.2 4.9v36.8a2.4 2.4 0 002.3 2.3h13.3a2 2 0 001.6-.7 1.8 1.8 0 001.2-1.6m-85.6-12.7a24.5 24.5 0 01-4.4 1.6 17.5 17.5 0 01-4.8.7c-2.3 0-4.1-.2-5.3-.9s-1.6-2-1.6-4.6v-1a14 14 0 01.5-3.4 6 6 0 011.6-2.5 6 6 0 013-1.4 24.4 24.4 0 014.8-.4h6.2zm17.7-26.9a26.9 26.9 0 00-1.8-10.3 19.2 19.2 0 00-5-7 18.8 18.8 0 00-8-3.8 39.7 39.7 0 00-10.9-1.4 82.6 82.6 0 00-10.8.7 48 48 0 00-8.3 1.6c-1.3.4-2 1.1-2 2.5v9a3.3 3.3 0 00.4 1.6 3.3 3.3 0 001.4.5h.5c.9 0 2-.3 3.2-.3l4.6-.2 5.3-.2h5.3a11 11 0 016 1.4c1.3.9 2.3 3 2.3 6.2v3.9h-6c-9.5 0-16.6 1.4-20.7 4.3-4.4 3-6.5 7.9-6.5 14.3v1c0 3.6.5 6.6 1.6 9a16.8 16.8 0 004.4 6 18.8 18.8 0 006 3.1 24.3 24.3 0 007.1 1 23.6 23.6 0 008.5-1.4 33.6 33.6 0 007-3.7v1.8a2.4 2.4 0 002.2 2.3h12.5a2.4 2.4 0 002.3-2.3v-39.6zm-79 17.5a8.8 8.8 0 01-3.3 7.1c-2 1.6-6.2 2.6-11.7 2.6h-9.2v-20.7h12.4c4.4 0 7.6.9 9.2 3a11.1 11.1 0 012.5 6.9zm-.5-32.9a17.8 17.8 0 01-.5 3.5 6 6 0 01-1.8 2.7 12.2 12.2 0 01-3.4 1.8 18 18 0 01-5.6.7h-12.4v-19h8.5c5.7 0 9.6.7 12 2a7.3 7.3 0 013.4 6.7l-.2 1.6zm18.8 31.7a17.2 17.2 0 00-2.8-9.9 17.5 17.5 0 00-7-6.4 15.6 15.6 0 006.8-6.5 20.2 20.2 0 002.6-9.6v-2.1a23 23 0 00-2.6-11 19.3 19.3 0 00-7.1-7.1 34.6 34.6 0 00-11-4 89.5 89.5 0 00-14.3-1H105l-5.5.1a34.5 34.5 0 00-5 .5c-1.7.2-2.8.2-3.7.5-2.1.4-3.7.9-4.6 2-1 1-1.4 2.8-1.4 5.3v67.4c0 2.5.4 4.1 1.4 5.3a9.5 9.5 0 004.6 2 20.3 20.3 0 003.9.5c1.6.2 3.2.2 5 .5a44.3 44.3 0 005.5.2h5.8a83 83 0 0013.6-1.1 33.1 33.1 0 0011-4 21.8 21.8 0 007.6-7.5 24.5 24.5 0 002.7-12.2V419z" class="cls-2"/> <path fill="none" d="M0 0h749.8v530.1H0z"/> </svg> woocommerce-blocks/images/previews/beanie.jpg 0000644 00000130473 15133551764 0015416 0 ustar 00 ��� JFIF H H �� @Exif MM * �i � � �� 8Photoshop 3.0 8BIM 8BIM% ��ُ �� ��B~�� "